Source: Code-grounded adversarial audit of all autonomous content-creation agents in the Oshun ecosystem (2026-06-17), 13 parallel sub-audits across bellona, V6/V7 UE, metis, isis, euterpe, yemaya, maya/neith, veritas, calliope, iris, galatea, saraswati/nisaba/seshat, and the oshun orchestration layer.
Mission: Close every gap and result-faking stub the audit found, and build the missing connective tissue, to industry-leading SOTA — so the ecosystem can autonomously generate
.uasset(and all other applicable content types) end to end.
How to use this file#
- One task. One verification. One mark. Process tasks sequentially. The
checkbox
[ ]/[x]is the sole source of truth. Never batch-mark. Read the actual code for each task before marking. (See rootCLAUDE.md→ Quality Standards.) - Definition of done for every task: real, domain-specific implementation; no stubs, no fabricated success; honest fail-loud seams where a real integration is absent; tests that assert real bytes / real computed values against known-correct answers (not shape/truthiness); build, lint, and type check pass.
- Anchors (
file:line) are the audit's findings at time of writing; treat the symbol name as authoritative if lines have drifted. - Tags:
[STUB]= fixes a result-faking stub (highest priority — violates the zero-stub rule).[GAP]= missing capability.[WIRE]= real code exists but is unwired/orphaned.[BUG]= correctness defect.[NAMING]= mislabeled, fix label or implementation.[SOTA]= net-new SOTA capability.
Legend of severity#
- P0 — result-faking stub on a reachable path, or blocks the core uasset/autonomy mission.
- P1 — missing capability central to "all applicable types" / autonomy.
- P2 — completeness, correctness, hardening, tests.
Progress log#
2026-06-22 — 7 closures by re-verifying the box's actual capabilities (the prior "blocked on creds/GPU/models" calls were wrong for THIS host). Each real, value-asserting-tested, committed, pushed to branch + main:
- 4.5 podcast + tts (3 boxes): the "blocked on TTS creds + neural models +
GPU" call was false here — espeak-ng (apt, articulatory phoneme synthesis
of real words) and Piper (real neural VITS TTS on CPU via onnxruntime + a
63 MB voice model) both synthesize real spoken words on this box. New
self-contained
@yemaya/tts-integration/local-enginedrives either engine (WAV→PCM decode, real linear resample, MP3 via ffmpeg/libmp3lame), fail-loud when absent. Wired as the default intopodcast-factory.mixAudio(real words, injectable seam, formant fallback,encodeMixResultToWav/Mp3) andtts-factory.synthesizeAsync(preferred non-HTTP path). 1052 tts-integration / 159 podcast tests. - 3.3 critique→revise: shipped
createContentEvalCritic— the concrete, non-provider-gated default judge (real@oshun/content-evalmetrics: mesh topology / LUFS / temporal / citation F1 / PSNR-SSIM) the Reflexion loop lacked; drivesreviseArtifactto real convergence. 13 tests. - 9.3 avatar (2 boxes): real RGBA pixel
face-renderer(filled-ellipse rasterization, viseme-shaped mouth) +renderTalkingHead(audio-envelope- driven lip-sync over the existingLipsyncProcessor) + ffmpeg MP4. 8 tests + verified on real espeak speech. - 10.2 auto-rig: real geometric
autoRigHumanoid(anthropometric landmarks refined from vertex slabs + LBS skin weights) +buildMocapRetargetToRig+ extended the on-box C++skeletalmesh.createto author an arbitrary N-bone rig → built + run on UE5.5.4, real 3-boneSK_CmdRigged.uasset(golden byte-validated). The generated-mesh → rig →.uassetcharacter path is closed.
The remaining 9 are genuinely blocked, re-verified by reading each seam this
session (0.1 consolidation = a refactor that would delete real veritas/iris
domain logic; 4.1 asset-factory/illustration + 9.2 metis t2v/concept-image =
fail-loud without live diffusion creds; 4.6 magenta-rt = injectable on-device
source, fail-closed, needs real-time neural music + GPU; 5.3 = the proprietary
MetaHuman SDK; 8.5 calliope = injectable LLM seam + deterministic fallback,
needs live LLM creds; 9.1 VLM critic = typed VlmNotConfiguredError seam, needs
a vision model). Each is an honest fail-loud seam / deferred net-new capability
— explicitly NOT a stub — so they correctly stay [ ].
2026-06-21 — 12 further closures (6 task-areas) following the project's own blessed precedents (3.1 planner / 4.1 SOTA-graph / 4.2 transport: real code on a fail-loud/provider-gated seam + boundary-tested = a valid closure). Each real, typechecked, value-asserting-tested, committed, pushed to branch + main:
- 4.2 AnimateDiff/SVD ComfyUI graph (line 1587): extended the deterministic
buildComfyUIAPIPromptwithGenerationType.AnimateDiff(text→video, motion module + frame-batch latent) andStableVideoDiffusion(image→video, SVD conditioning + linear CFG), both →VHS_VideoCombine; SVD fail-loud without an init image. 9 value-asserting graph tests;@yemaya/comfyui-integration1332/1332. - 9.1 narrator (line 2389):
createMetisNarrator— the concrete@oshun/ai-backedAsyncNarrationWriter(grounding prompt, fail-loud), in the decoupled@oshun/creative-orchestratorlayer (structural seam, no metis compile dep). 6 tests; orchestrator 45/45. - 10.1 text-to-3d (lines 2487–2495): new
@bellona/text-to-3d— real Meshy + Tripo REST transports (submit/poll/download GLB, Bearer, fail-loud) →@bellona/interchangeparse + glTF→Unreal conversion →@oshun/content-evalmanifold/watertight/UV →@bellona/unrealStaticMesh authoring →.uasset. Verified on the real UE5.5.4 engine (BELLONA_ONBOX=1: generated GLB → byte-valid StaticMesh, tris=12). 26 off-box + 1 on-box test. - 6.1 DCC LLM planners (lines 2044–2057):
runBlenderLlmAgentLoop(closed NL→plan→execute→observe→replan over the real op catalog → realexecuteBellonaBlenderTransaction→ real bridge mutation) +runUnityLlmAgentLoop(NL→synthesize→build→observe→revise → realgenerateBellonaUnityMonoBehaviourCode- real headless-batch observation). Model call = injected
StructuredPlannerseam (buildable-librootDirforbids an@oshun/aisource import; tests wire the realrunStructuredOutput). Deterministic parsers kept as fallback. blender-agent 195/195, unity-agent 1066/1066.
- real headless-batch observation). Model call = injected
- 7.1 embeddings (line 2166):
EmbeddingDenseRetriever— real cosine over real embedding vectors with an injectableEmbeddingBackend(voyage/openai/ cohere) + fail-loud; 8 tests asserting exact cosine values. agentic-rag 32/32. - 10.3 capstone (line 2648):
brief-to-playable-content.integration.test.tsruns the realdecomposeBrief→routePlanend to end over real generators (text-to-3d mesh driven watertight by the critique→revise loop, real WAV audio, real.umap, real.utoccook) with real verification + a governance-kill honesty test. 2/2.
The remaining 16 are genuinely blocked, re-verified by reading the code this
session (9.2 metis image/video, 9.3 avatar, 4.1 asset-factory/illustration, 4.5
tts, 3.3 quality-loop): each is an honest fail-loud *NotConfiguredError seam
/ honestly-labeled real fallback / real loop — explicitly NOT stubs per
CLAUDE.md — blocked on live LLM/diffusion/video/TTS/embedding/VLM creds,
real neural models + GPU (4.5 podcast, 4.6 magenta-rt, 9.3 avatar), a
proprietary SDK (5.3 MetaHuman), a large high-blast-radius refactor that would
delete real functionality (0.1 consolidation), or a large research-grade SOTA
pipeline needing a real character-mesh corpus (10.2 auto-rig — the authoring
primitives are done). They cannot be closed without fabrication, so they
correctly stay [ ].
2026-06-20 — non-provider-gated closures + adversarial verification of the blocked seams (each closure real, on-box / real-artifact verified, type-checked, committed, pushed to branch + main):
- Phase 6.2 + 1.1 mcp-gateway (lines 302, 1806, 1812): real
DccBridgeGatewayroutes MCP commands to connected@bellona/adaptersBaseBridge DCC transports (returns the bridge's real response, never the fabricated['Camera','Cube','Key Light']);UeHttpCommandTransportis the real client for the Phase-1.1 C++ command route (contract-matched toHandleCommandRequest+BellonaCommandResultToJson). Fail-loud throughout; smoke gateway kept as the labelled double. 15 tests (boundary-mocked WS DCC + UE fetch + a literal MCP tool-call e2e); mcp-gateway suite 129/129. - Phase 4.2 render-factory (lines 1421, 1444):
createVideoRenderContentSourcedrives the real@oshun/encodingffmpeg encoders offRenderJobRequest.exportRequest, closing the render-factory side of the encoder wiring;RenderFactory.renderpersists real video bytes + sha256-checksums them. 6 ffmpeg-gated tests with real ffprobe (h264 mp4 / vp9 webm / gif). The real-MP4-ffprobe test bar is met here + in export-pipeline. - Phase 5.2 USDA real-runtime test (line 1752):
usda-real-runtime.test.tsauthors aUsdGeomMeshvia the openusd SDK and opens it in a real OpenUSD runtime (pxr, USD 0.26.5, installed on-box via a venv) asserting exact points/faces; gated onOSHUN_USD_PYTHON/import pxr, skips when absent. - Phase 4.4 wgpu→TS bridge (line 1565): new napi-rs crate
yemaya-raster-gpu-node+raster-kernel/gpu-bridge.tsmake the real wgpu compositor reachable from TS; verified on-box (wgpu gets a Mesa software-raster adapter) — GPU output matches the CPUraster-kernel.tsreference within ±1 (composite) / ±2 (blur). Fail-loud (GpuKernelUnavailableError) when the addon/adapter is absent;.node+target/gitignored, built on demand. - Adversarial re-verification of the provider-gated seams (4.1
asset-factory/illustration-engine, 4.5 podcast/tts, 4.6 magenta-rt, 6.1
blender-agent NL planner, 7.1 dense-retriever, 8.5 backstory-generator,
9.1/9.2 text-to-video/concept-image, 9.3 avatar-generator): read every named
function on its reachable path + delegation chains + the trigger-word grep —
all honest (typed
*NotConfiguredError/ fail-closed / documented approximation / correctly-labelled). No hidden result-faking stubs; these correctly stay[ ](need live LLM/diffusion/video/TTS/embedding creds), not because they fabricate. - UE in-engine tasks 2.1/2.2/2.3 — feasibility assessed, blockers identified
(stay
[ ]): 2.3 V6Agent — all prerequisites in place (cognition tiers Clotho/Lachesis/Atropos computed each frame inUV6AgentMassLODProcessor, StateTreeModule already a dep), but no behavior-execution path exists; a real StateTree IS authorable headlessly (engineStateTreeTestSuitetemplate:UStateTreeEditorData→AddSubTree/AddTask/AddTransition→FStateTreeCompiler::Compile) yet a faithful closure needs the Mass+StateTree integration (UMassStateTreeProcessor+ a Mass schema) — a large, build-risky P2 effort. 2.2 districts — thelevel-placement-bridge(1.6) + .uasset authoring are proven, but there is no real neith/maya → V6-district scene-program (the districts are procedural C++ by design;tokyo-district-styles.tsis style data, not placements) — authoring committed district art would risk token geometry. 2.1 PCG — PCG-graph authoring is editor-gated like StateTree. Left[ ]rather than fabricate.
2026-06-18b — on-box UE uasset-authoring chain + eval foundation (Linux box
with the compiled UE5.5.4 engine — the earlier "macOS checkout" note no longer
applies). Each closure is real, on-box-verified against the live engine (real
.uasset bytes), type-checked, committed, pushed to branch + main:
- Phase 0.3 (gate):
libs/bellona/unreal/onbox/— Blueprint-only probe project + python probe +runUAssetImportRoundTrip(asueagent) import a fixture FBX → StaticMesh.uasset; a standalone version-awareFPackageFileSummaryreader (validateUAsset) proves success by artifact (magic0x9E2A83C1+ real export table). Replaced the shape-onlyphase-8-completion.test.ts. - Phase 1.2 (StaticMesh authoring): author a
UStaticMeshfrom explicit vertex/index buffers (build_from_static_mesh_descriptions) + material slots + Nanite; verified cubetris=12 sections=2 nanite=1. (Skeletal authoring unchecked.) - Phase 1.3 (Material + MaterialInstance): author a
UMaterial(BaseColor/Metallic/Roughness/Normal/Emissive expression nodes + params, compiled) and a parentedMaterialInstanceConstantwith verified scalar/vector overrides. (MaterialX ingest unchecked.) - Phase 0.2 (eval): new
@oshun/content-eval— real PSNR/SSIM, temporal consistency, mesh manifold/watertight/UV, citation P/R/F1, LUFS conformance + fail-loud model seams + golden-set runner (23 golden tests). - Capabilities shipped, boxes kept honest: Phase 1.4 Blueprint authoring
(parent + components bound to an authored mesh + compile + save,
byte-verified; variables + event-graph need the 1.1 C++ plugin) and Phase 1.6
Level authoring (empty
.umapauthors, butspawn_actorsegfaults headlessly — actor placement needs the 1.1 C++ plugin). Both left unchecked with notes rather than shipping fabricated success. - Transport finding: pure asset authoring works headlessly via
-run=pythonscript; world/actor manipulation segfaults — the dividing line for what the python-commandlet transport can do vs. what needs the C++ plugin.
2026-06-18 — locally-actionable closures (20 tasks; each with a real implementation + value-asserting tests, type-checked, committed, and pushed to branch + main):
- Phase 3.4: iris orchestrator
simulateTaskExecutionMath.randomfabrication → injectabletaskExecutor+ honest-fail (+ deterministic opt-increateSyntheticTaskExecutor); agent-spawner cosmeticsleep()delays removed. - Phase 4.7 (complete): transcoder ffmpeg
-vf/-afclobbering + spaced encoder tokens; bundle checksums hash real bytes (not metadata) via areadBytesseam + asyncverifyBundle; dailies ASC-MHL/OCF fallbacks compute real sha256 (was 32-char FNV mislabelled sha256). - Phase 5.1: GLB BIN chunk written + text-glTF buffer data-URI round-trip.
- Phase 5.2: openusd fake USDC Crate emitters → fail-loud (+ honest prim counts); interchange USDC/USDZ export → fail-loud on mesh geometry (was silent drop); USDA attribute-value serialization implemented (was dropped on save).
- Phase 5.3: MetaHuman
analyzeFaceRigparses the real Blender payload (was hardcoded) + detects procedural eyelids/pupils from the rig. - Phase 5.5: real value-asserting round-trip tests for the interchange serializers (GLB/glTF, USDA/USDC, FBX).
- Phase 7.1: weighted Reciprocal Rank Fusion replaces weighted-sum fusion (rank-based/scale-invariant); dense-retriever doc mislabel fixed.
- Phase 8.2: seshat feng-shui deterministic remedy effectiveness (was
Math.random); metis gpu-worker-pool real memory accounting (was random); saraswati drone defect inference injectable detector + fail-loud (was FNV fabrication); ajeexecuteWeb3Chaininjectable executor + fail-loud (was fabricated LLM chain). - Phase 8.3: nisaba script-classifier honest
'heuristic'label (was fake'ensemble'); aglaeaSpecialistModel.modelId→specialistId. - Phase 8.4: lakshmi ai-agents honest health shell + env-derived
modelProviders.
Blocked on environment / credentials / scale (remain [ ] with notes): the
UE phases (0.3, 1, 2, parts of 5) need the on-box UE5.5 engine (Linux box, not
this macOS checkout); the autonomy/provider phases (0.1 real wiring, 3.1–3.3,
3.5, 4.1–4.6, 6, 7.2, 9, 10, 8.5 calliope, agentic-rag embeddings) need live
LLM/CV/diffusion/video credentials and/or are large multi-session SOTA builds
(e.g. yemaya 4.4 export/render needs a video encoder + a rewrite across
3,000-line files; 0.2 content-eval + 0.1 agent-loop are new libraries). The iris
conflict-resolver detect*Conflicts are honest-empty (not stubs) and need a
richer context model (pending-write/history) to detect anything real.
Phase 0 — Foundations: model-provider plane, tool-use, eval harness, on-box UE round-trip#
These are prerequisites for both autonomy (Phases 3, 6, 7) and uasset authoring verification (Phase 1). Do this phase first.
0.1 Shared LLM tool-use + structured-output plane (P0, [GAP])#
- Audit
libs/shared/ai/src/providers/{anthropic,openai}.tsand confirm a single canonical client surface (messages.create/chat.completions.create) is the only provider entrypoint; remove duplicate ad-hoc clients found inlibs/veritas/agents-core/src/llm.ts,libs/veritas/llm/src/client.ts,libs/iris/conversation-providers-*by re-exporting the shared client. — NOTE (2026-06-18): left unchecked after verifying the premise is partly wrong.veritas/llm/src/client.ts(JournalismLLMClient, 867 lines) andveritas/agents-core/src/llm.ts(LLMClient, 876 lines) are not thin duplicates — they carry real domain logic (LLMRouter,CostOptimizer,ContentSafetyService, journalism system prompts; tool-use + streaming). Blindly removing them and re-exporting@oshun/aiwould delete real functionality, not consolidate it. A correct consolidation = have them delegate transport to the shared provider while keeping their domain layers — a large, high-blast-radius refactor across 1,700+ lines of two libs, its own task. The new agent-loop plane below does not require it. Rewritten 2026-09-18 from the note above, which found the premise partly wrong: do not delete the Veritas clients. MakeJournalismLLMClient(libs/veritas/llm/src/client.ts) and the agents-core client delegate their transport to@oshun/ai, keepingLLMRouter,CostOptimizerandContentSafetyServicewhere they are; the shared provider directory now also holds the OpenRouter, Codex and Ollama routes, so "one canonical pair" no longer describes it. Verify: a spec per client shows every outbound model call goes through the shared transport (doubled at that boundary), and a grep finds no second HTTP client for a model provider underlibs/veritas. - Provider-agnostic tool-use loop primitive — DONE 2026-06-18. New
libs/shared/ai/src/agent-loop/(@oshun/ai/agent-loop; the package is@oshun/ai, the TODO's@oshun/shared-ainame). Promotes the proven@iris/agents-coreloop onto the sharedLLMProviderInterface(createChatCompletion):ToolRegistry(dup-name guard; unknown/throwing tools become recoverableisErrortool_results, never crash the run),AgentLoop/createAgentLoopwith parallel tool dispatch (order preserved),tool_resultround-trip back into the transcript, max-iteration guard, and aBudgetMeter(LLM-calls / tool-calls / total tokens / cost; fail-loudUnpricedModelErrorwhen a cost cap meets an unpriced model). Terminations are explicit (completed/truncated/max_iterations/budget_exceeded/aborted) — never a cut-off dressed as done. - Structured output with schema-validated retries — DONE 2026-06-18.
runStructuredOutputvalidates the model's JSON against a JSON Schema with ajv (real validator, added as a dep), extracts JSON from bare/fenced/embedded text, and on mismatch re-prompts with the concrete validation errors up tomaxRetries, then fails loud (StructuredOutputErrorcarrying attempts + last raw + errors). - Reflexion/critic-revise helper — DONE 2026-06-18.
runReflexionrunsgenerate → critique → revisewith a pluggableCritic(LLM judge, a Phase-0.2 metric, or a VLM — the loop only readsscore), a quality bar and bounded iterations; reportsmet:falsehonestly when the bar is never cleared (no spurious success). This is the loop yemayacrew.ts/creative-agent-base.ts/ metis lacked. - Fail-loud when no provider/key — DONE 2026-06-18. All three
entrypoints (
AgentLoop.run,runStructuredOutput,runReflexion) throw typedProviderNotConfiguredErrorwhen handed a null/undefined provider; no fabricated completions anywhere. - Tests — DONE 2026-06-18.
agent-loop.test.ts(15, all value-asserting against a deterministic scriptedLLMProviderInterfaceboundary): tool round-trip executes a real tool and the 2nd request carries the fed-backtool_result(assertsllmCalls=2/toolCalls=1 + the'5'content); parallel multi-tool order preservation; unknown-tool recovery;max_iterations/maxLlmCalls/maxToolCallscutoffs with exact counts +budgetViolation.dimension; aborted-signal (0 calls); structured-output retry-then-succeed (attempts=2) + prose/fenced JSON extraction + fail-loudStructuredOutputError; reflexion revise-to-bar (met,iterations, scores) + honestmet:false; andProviderNotConfiguredErrorfor all three entrypoints.tscexit 0; full@oshun/aisuite shows only 3 pre-existing quantization fixture failures (verified by re-running on the stashed clean tree), my 15 green; stub-gate clean for the new files.
0.2 Content-generation eval harness (P1, [GAP])#
- Create
libs/shared/content-eval— DONE 2026-06-18. New@oshun/content-evallib (project.json/package.json/tsconfig + tsconfig.base path map). Real, first-principles, golden-tested metrics: imagecomputePsnr(10·log10(MAX²/MSE)) +computeSsim(windowed Wang-2004); videotemporalConsistency(inter-frame MSE / worst-pop); 3D/meshanalyzeMeshTopology(edge-manifold, watertight, Euler χ),checkPolyBudget,computeUvCoverage; textcitationMetrics(precision/recall/F1); audiocheckLufsConformance(gate over a BS.1770 measurement — measurement reuses@metis/multimedia.measureIntegratedLufs). Honest fail-loud seams (MetricModelNotConfiguredError, never fabricated) for the model/perceptual metrics that need a trained backend + GPU/creds: CLIP-score, aesthetic predictor, VMAF, PESQ, STOI, NLI faithfulness — injectable viaconfigureMetricBackends(boundary-mocked in tests). This matches the acceptance bar: real where computable, fail-loud where a real integration is absent. - Golden-set runner — DONE 2026-06-18.
runGoldenSet(cases)runs each metric case against a known-correctexpectedwithin atoleranceand returns a structured pass/fail/errored report (an unconfigured model seam is recorded as errored, never silently passed) — CI-runnable. - Tests — DONE 2026-06-18. 23 tests, each asserting a hand-computed
value:
expect(psnr).toBeCloseTo(28.13083,4), SSIM=1 for identical +(2μxμy+C1)/(μx²+μy²+C1)closed form for constants, cube manifold/watertight/Euler=2, single-triangle 3 boundary edges + non-manifold edge detection, citation P/R/F1=2/3 for partial overlap, LUFS in/out of tolerance, temporal MSE [100,400], and model seams that fail loud unconfigured / delegate to an injected backend.tsc --noEmitexit 0.
0.3 On-box Unreal round-trip CI gate (P0, [GAP])#
The box has a compiled UE5.5.4 source build at
/root/workspace/UnrealEngine-5.5/(editor + UBT). PerCLAUDE.md, run UE automation as theueagentuser. This gate makes uasset tasks verifiable against a real engine instead of asserting generated Python strings.
- On-box round-trip automation — DONE 2026-06-18 (verified on-box).
Added
libs/bellona/unreal/onbox/(a Blueprint-onlyBellonaOnBoxProbe.uproject—DisableEnginePluginsByDefault+ PythonScriptPlugin/EditorScriptingUtilities so no game-module compile is needed;python/uasset_probe.py; a committedfixtures/ProbeCube.fbx) andsrc/onbox/onbox-gate.tsrunUAssetImportRoundTrip, which (asueagent, in a fresh ueagent-owned temp copy) launchesUnrealEditor-Cmd … -run=pythonscript, imports the fixture FBX → StaticMesh,save_assets it, reloads viaunreal.load_asset, and asserts non-empty geometry (real run: tris=12, lods=1, sections=6). Markers surface viaunreal.log_warning(UE only routes python Warning/Error verbosity to the commandlet stdout). Engine confirmed 5.5.4. - Artifact-level validation + replace shape-only test — DONE 2026-06-18.
src/onbox/uasset-reader.tsis a standalone, version-awareFPackageFileSummaryparser (mirrors the engine'sPackageFileSummary.cpp): checks magic0x9E2A83C1and reads the real export table. On the golden on-box asset it decodespackageName='/Game/BellonaProbe/SM_ProbeCube',exportCount=7,nameCount=164— byte-accurate. Fails loud (UAssetParseError) on non-UE/truncated/byte-swapped bytes. The committed goldensrc/onbox/fixtures/SM_ProbeCube.uasset(produced by the probe) letsuasset-reader.test.ts+ the rewrittenphase-8-completion.test.tsassert real bytes in plain CI (no engine); the gatedonbox-roundtrip.test.ts(BELLONA_ONBOX=1) re-validates the freshly-produced bytes on-box. 12 CI tests + 2 on-box tests green;tsc -p tsconfig.lib.jsonexit 0; full lib suite 57 pass / 2 skip. - Gate established — DONE 2026-06-18.
runUAssetImportRoundTrip/validateUAsset/isOnBoxAvailableare exported from@bellona/unreal(and@bellona/unreal/onbox) as the green harness Phase 1 authoring tasks verify against (each handler reloads its produced asset and validates the bytes, fail-loud when the editor is absent).
Phase 1 — Unreal .uasset authoring (the core ask)#
Problem (audit): the only real uasset path is import-only —
headless-import.ts:171 imports FBX→StaticMesh/SkeletalMesh, textures, audio
via unreal.AssetImportTask, and cook-runner.ts:124 cooks. Authoring of
Materials, Blueprints, Niagara, Levels, DataTables, SoundCues, PhysicsAssets,
etc. is a WebSocket RPC façade (asset-tools.ts, unreal-bridge.ts)
pointed at port 30010 with no server implementing the handlers. The C++
plugin (BellonaUnrealEditorModule.cpp) is a read-only /ready probe
(CanContainContent: false).
1.1 UE-side command server — make the façade real (P0, [WIRE]/[GAP])#
- Decide the transport + make the façade real — DONE 2026-06-18
(built + run on-box). Took the preferred option: extended the existing
BellonaUnrealEditorC++ plugin (already loaded) into a real command server. Two transports share one dispatcher: the authenticated HTTPPOST /bellona/unreal/v1/commandroute (the production façade) and a headlessBellonaAuthorUCommandlet(-run=BellonaAuthor -BellonaCommand=… -BellonaParamsFile=…) for on-box CI. Compiled on-box against UE5.5.4 (UBT, bundled clang 18.1 —[329/331] Compile Module.BellonaUnrealEditor.cpp→ linked.so) and run on-box asueagent. The ~120 RPC names remain the contract; the first handler (staticmesh.create) is proven and the rest are incremental additions to the now-real dispatcher (each follows the same author→SavePackage pattern). NOTE: the editor crashes in PreInit if the built project'sIntermediate/stays root-owned (build runs as root) — chown the project toueagentbefore the editor runs it. -
.upluginCanContainContent: true+ an authoring editor module — DONE 2026-06-18. FlippedCanContainContenttotrue; the editor module now authors real assets andSavePackages them (BellonaCommandDispatcher.cppbuilds aUStaticMeshfrom anFMeshDescriptionviaBuildFromMeshDescriptionsand saves a real.uasset). - Command dispatcher with the SavePackage contract — DONE
2026-06-18.
ExecuteBellonaCommand(command, params)routes a JSON request to a typed handler and returns the real on-disk asset path + byte size;SaveAuthoredAssetreports success ONLY ifUPackage::SavePackagesucceeds AND the file is on disk and non-empty — a 0-byte/failed save is a typed failure, never fabricated. Verified on-box:staticmesh.createauthored/Game/BellonaAuthored/SM_CmdCube→ a 10816-byte.uasset, magic0x9E2A83C1, thatvalidateUAssetconfirms is a valid StaticMesh package (7 exports, packageName matches;valid:true). (A content hash can be added alongside the byte-size; the bytes are already byte-validated.) - Replace the in-memory
mcp-gatewayCloudAgentMvpSmokeGatewaysimulation (cloud-agent-mvp-smoke.ts:112) wiring so the gateway can reach this real server (Phase 6 ties MCP → live UE). — DONE 2026-06-20. Newue-http-command-transport.tsUeHttpCommandTransportis the real client for the Phase-1.1 C++ command route — it speaks the editor's exact contract (POST /bellona/unreal/v1/command,{command,params}body,Authorization: Bearer <session-token>, parse the real{ok,assetFilePath,packageName,assetClass,fileSizeBytes,detail}reply; cross-checked field-for-field againstBellonaUnrealEditorModule.cpp::HandleCommandRequest+BellonaCommandResultToJson) and fails loud (UeCommandServerError) on a non-OK/{ok:false}/non-JSON reply — never fabricates. It satisfies the newDccCommandTransportseam, so the realDccBridgeGateway(Phase 6.2) routes MCP commands to the live UE editor the same way it routes to the DCC bridges. TheCloudAgentMvpSmokeGatewaystays as the clearly-labelled test double (unchanged).ue-http-command-transport.test.ts(7, boundary-mockedfetch): exact POST body + Bearer header + URL, the parsed authored-asset result, fail-loud on 500/401/non-JSON, constructor fail-loud, env factory, and a gateway-integration test routing an MCP command through the UE transport to the real authored-asset output. 7/7 green; full mcp-gateway suite 129/129;tsc -p tsconfig.lib.jsonexit 0; stub scan clean. (Verifying against a live editor HTTP listener still needs the running on-box editor — the on-box authoring path used theBellonaAuthorcommandlet; the transport is the real client + fail-loud seam for the HTTP route.) - Tests (on-box, Phase 0.3 harness) — DONE 2026-06-18 (built + run on
the live engine).
command-gate.test.ts: 5 off-box unit tests for theparseCommandResultmarker/JSON extraction (success, honest failure, missing/garbled markers), plus aBELLONA_ONBOX=1-gated round-trip (runCommandServerStaticMeshRoundTrip,onbox-gate.ts) that stages a host project + the plugin, builds it with UBT, runs theBellonaAuthorcommandlet to author a StaticMesh through the real dispatcher, reads the produced.uassetback, and validates its bytes — assertingresult.ok,assetClass:'StaticMesh',triangles:12, andvalidateUAsset(...).validwith the expected packageName + a non-empty export table. 7/7 green on-box (the incremental build + author + validate ran in ~29s reusing the built engine modules); off-box the gateisOnBoxAvailable()-skips (fail-loud when the editor is absent).tsc -p tsconfig.lib.jsonexit 0.
1.2 StaticMesh / SkeletalMesh authoring beyond import (P1, [GAP])#
-
mesh.createStaticMeshhandler — DONE 2026-06-18 (verified on-box).onbox/python/author_static_mesh.pybuilds a realUStaticMeshfrom aStaticMeshDescription(create_static_mesh_description→create_vertex/set_vertex_position→ per-cornercreate_vertex_instance+ UV0 →create_triangleper material-slot polygon group) andbuild_from_static_mesh_descriptions(SOTA — recomputes normals/tangents, the python equivalent ofBuildFromMeshDescriptions), thensave_assets a real package and enables Nanite where requested (MeshNaniteSettings.enabledset before build, read back off the reloaded asset). On-box run: cube →tris=12 lods=1 sections=2 uvs=1 nanite=1, validated bytes. Honest envelope note: the exposed pythonStaticMeshDescriptionbindings have noset_num_uv_channels(UV1..N crashes the builder) and noset_vertex_instance_color, so additional UV channels + vertex colors are deferred to the C++ plugin handler (1.1) that drivesFMeshDescriptiondirectly — NOT fabricated here. - Procedural-from-spec — DONE 2026-06-18.
runStaticMeshAuthoring(spec)(src/onbox/static-mesh-authoring.ts) accepts explicit vertex/index buffers (positions/triangles/uvs/triangleMaterialSlots/materialSlotNames/nanite), serializes them to the on-box builder, and validates the produced.uassetbytes — exactly the shape neith/maya scene programs emit.cubeMeshSpec()is the reference generator/template. Spec is validated + fail-loud before the engine is touched. - SkeletalMesh authoring: skeleton + skin weights + morph targets from
FSkeletalMeshLODModel; standalone AnimSequence authoring (today anim is only a side effect of FBX skeletal import,headless-import.ts:198). — DONE 2026-06-19 (built + run on-box via the 1.1 C++ command server; compiled first-try). Newskeletalmesh.createhandler authors a realUSkeletalMesh: it builds a genuine 2-bone reference skeleton (Root + Bone_01 viaFReferenceSkeletonModifier), a skinnedFMeshDescriptionviaFSkeletalMeshAttributes(geometry + bone attributes + real per-vertex skin weights —GetVertexSkinWeights().Set(v, FBoneWeights::Create({FBoneWeight(0,1)}))), then runs the engine's ownFStaticToSkeletalMeshConverter::InitializeSkeletalMeshFromMeshDescriptions(the real LOD build),SetSkeleton+MergeAllBonesToBoneTree, andSavePackages both the SkeletalMesh and an authored siblingUSkeleton. AddedSkeletalMeshDescription+SkeletalMeshUtilitiesCommon+AnimationCoreto the plugin Build.cs (all engine modules, no plugin). Verified on-box: authored/Game/BellonaAuthored/SK_CmdFigure→ a byte-valid SkeletalMesh package (detail.bones:2,validateUAsset valid:true), goldenSK_CmdGolden.uasset(16545 B) byte-validated off-box (Root/Bone_01 bone names present). This unblocks the 1.7 Physics/BlendSpace/AnimMontage/AimOffset handlers (a real Skeleton/SkeletalMesh now exists to bind to). Standalone AnimSequence authoring is now ALSO DONE (2026-06-19): newanimsequence.createkeys real bone tracks (Root + sinusoidal-bob Bone_01) through the engineIAnimationDataController(OpenBracket/SetFrameRate/SetNumberOfFrames/AddBoneCurve/SetBoneTrackKeys/NotifyPopulated— the FBX importer's API; Engine-module only), on a sibling authored Skeleton. Verified on-box:/Game/BellonaAuthored/ANIM_CmdBob→ byte-valid AnimSequence (detail.frames:30, frameRate:30, boneTracks:2), goldenANIM_CmdGolden.uasset(11720 B) byte-validated off-box (Root/Bone_01 track names present). Morph targets are now ALSO DONE (2026-06-19):morphtarget.createauthors a SkeletalMesh then builds a realUMorphTarget(per-vertex Z+20 deltas indexed by render-vertex-buffer index viaUMorphTarget::PopulateDeltasover the built LOD sections),RegisterMorphTargets it, and re-saves — verified on-box (/Game/BellonaAuthored/SK_CmdMorph,detail.morphTargets:1, morphNameBulge), goldenSKM_CmdGolden.uasset(17940 B) byte-validated off-box (contains theBulgemorph name). SkeletalMesh authoring is now fully complete: skeleton + skin weights + AnimSequence + morph targets. On-box round-trip now authors 14/14 asset types. - Tests — DONE 2026-06-18. Golden
src/onbox/fixtures/SM_AuthoredCube.uasset(authored on-box) is byte-validated in plain CI (reads exportCount, packageName, StaticMesh export);static-mesh-authoring.test.tsasserts thecubeMeshSpecgenerator + spec fail-loud; gated on-box tests reload the authored mesh and assert tri count (12), LOD count, material slots (2 sections), Nanite flag against the spec. 13 CI + 10 on-box tests green;tsc -p tsconfig.lib.jsonexit 0.
1.3 Material + MaterialInstance authoring (P1, [GAP])#
-
material.createhandler — DONE 2026-06-18 (verified on-box).onbox/python/author_material.pyauthors a realUMaterialviaunreal.MaterialEditingLibrary:create_material_expression(VectorParameter/ScalarParameter/Constant3Vector/Constant) →connect_material_propertyto BaseColor / Metallic / Roughness / Normal / Emissive →layout_material_expressions→recompile_material→save_asset.runMaterialAuthoring(spec)(TS) drives it and validates the produced bytes. On-box:expressions=5 scalarParams=2 vectorParams=1, params verified viaget_scalar/vector_parameter_namesafter reload, byte-valid Material package (goldenM_ProbeAuthored.uasset). - MaterialInstanceConstant authoring — DONE 2026-06-18 (verified
on-box).
onbox/python/author_material_instance.py+runMaterialInstanceAuthoringauthor a realUMaterialInstanceConstantparented to a material with scalar + vector parameter overrides, and verify every override round-trips through save+reload viaget_material_instance_scalar/vector_parameter_value(the setters return void, so success is proven by the readback, not the setter). On-box:scalars=2 vectors=1(Metallic=1.0/Roughness=0.1/BaseColor=[.1,.9,.2]), byte-valid MIC (goldenMI_ProbeAuthored.uasset). TheauthorParentoption authors the parent into the same ephemeral project (each on-box run is isolated). NOTE: texture-parameter overrides use the identicalset_material_instance_texture_parameter_valueseam but need a Texture asset to bind — deferred to 1.7 texture authoring; not fabricated here. ALSO now authored via the unified 1.1 C++ command server (2026-06-19):materialinstance.createauthors a parentUMaterial(VectorParameter→ BaseColor + ScalarParameter→Roughness viaUMaterialEditingLibrary+RecompileMaterial) then aUMaterialInstanceConstantparented to it with both overrides, built + run on UE5.5.4 (goldenMI_CmdGolden.uassetreferences the parent + both params). Found the C++ analogue of the void-setter quirk:UMaterialEditingLibrary::SetMaterialInstance{Scalar, Vector}ParameterValueare buggy — always returnfalse(a localbResultis never set true) though they apply the override, so the handler verifies via the storedScalarParameterValues/VectorParameterValuesreadback, not the return value. - MaterialX ingest: wire the existing real MaterialX graphs from
libs/bellona/openusd/materialx/materialx-manager.ts:514through UE's Interchange MaterialX pipeline →UMaterial. — DONE 2026-06-19 (built + run on-box via the 1.1 C++ command server). Newmaterialx.importhandler: writes a real MaterialX 1.38standard_surface.mtlx(or a caller-suppliedmtlxContentdocument — the materialx-manager.ts output flows straight through), then ingests it through UE's Interchange MaterialX pipeline —UInterchangeManager::CreateSourceData+ the synchronousImportAsset(bIsAutomated=true; the MaterialX translator/pipeline self-register from the plugin) → aUMaterial, thenSavePackages the imported asset (fails loud if the import yields noUMaterial). Wiring: added the Interchange + InterchangeEditor plugin deps (.uplugin+ the harness host.uproject) andInterchangeCore/InterchangeEngineto Build.cs (compiled first-try — no transitive-include surprises this time). Verified on-box: a.mtlx→/Game/BellonaAuthored/M_CmdMaterialX(detail.materials > 0); goldenM_CmdGoldenMtlx.uasset(30.5 KB) byte-validated off-box as a valid Material package. On-box round-trip now authors 16/16 asset types (7/7 green, 209 s). - Tests — DONE 2026-06-18. Golden
M_ProbeAuthored.uasset+MI_ProbeAuthored.uassetbyte-validated in plain CI (exportCount, package name, Material/MaterialInstanceConstant class names, theBaseColorparam name in the table). Gated on-box tests reload the material and assert the parameter set (2 scalar + 1 vector), connected inputs (expression count = 5), and that it compiles (recompile_materialsucceeds, asset reloads as aMaterial); the MIC test asserts the overrides round-trip. 19 CI + on-box tests green. (Shading model = default lit; not separately asserted.)
1.4 Blueprint authoring (P1, [GAP])#
PARTIAL capability shipped (assessed on-box 2026-06-18).
onbox/python/author_blueprint.py+runBlueprintAuthoringauthor a real, compiledUBlueprint.uassetwith a parent class + components (viaSubobjectDataSubsystem.add_new_subobject; aStaticMeshComponentis bound to an authoredStaticMesh) +SavePackage, byte-verified (goldenBP_ProbeActor.uasset: valid Blueprint, name table referencesActor,StaticMeshComponent,PointLightComponent, and the boundSM_BPCube). Blocked via python (need the 1.1 C++ plugin): member variables (add_member_variable'svariable_typeneeds anEdGraphPinTypethat cannot be constructed from python — every scalar spelling fails "Failed to convert parameter") and the event graph (K2 node editing is C++-only). Boxes kept unchecked because those parts are unmet, not fabricated.
-
blueprint.createhandler — variables UNBLOCKED via the 1.1 C++ plugin, DONE 2026-06-18 (built + run on-box).AuthorBlueprint(BellonaCommandDispatcher.cpp) authors aUBlueprint(parentAActor) viaFKismetEditorUtilities::CreateBlueprint, adds typed K2 member variables withFBlueprintEditorUtils::AddMemberVariableover a real-in-C++FEdGraphPinType(bool/int/float/string) — the exact thing the headless python bindings cannot construct (the prior blocker) — then compiles (CompileBlueprint) andSavePackages. Verified on-box: authored/Game/BellonaAuthored/BP_Hero→ a 27546-byte.uasset(magic0x9E2A83C1) thatvalidateUAssetconfirms is a valid Blueprint package (13 exports),compiled:true, with all 4 variables persisted (bIsAlive/Score/Speed/Titlepresent in the package). (Parent + compile + save + variables done; a minimal K2 event-graph BeginPlay node is an additive follow-up; component-adding remains on the python-authored path.) - Editable variables (the blocked part) — DONE 2026-06-18. Typed member
variables are now authored + compiled in via the C++ plugin (above);
component-adding was already done on the python-authored path (any
unreal.*Componentby name, mesh bound for StaticMeshComponent). Component-adding is now ALSO in the C++ command server (2026-06-19):actorblueprint.createauthors aUBlueprint(parentAActor) and builds a real component hierarchy through the SimpleConstructionScript — aStaticMeshComponentbound to the engine cube (SCS->CreateNode+AddNode,SetStaticMeshon the node'sComponentTemplate) with a CHILDPointLightComponent(MeshNode->AddChildNode), compiled; built + run on-box (goldenBP_CmdGoldenProp.uasset, both component types + the bound cube serialised,components ≥ 2,meshBound). - Tests — DONE 2026-06-18. The
command-gate.test.tsround-trip asserts the authored Blueprint's parent/class, compiled status, and variable count (assetClass:'Blueprint',detail.compiled:true,detail.variables:3) + that the bytes validate as a Blueprint package and the variables persisted. (blueprint-authoring.test.tsstill covers the python component-list path; variable defaults are N/A until set.)
1.5 Niagara / VFX authoring (P1, [GAP])#
-
niagara.createSystem/niagara.createEmitter(unreal-bridge.ts:1690): authorUNiagaraSystemfrom an emitter template, set spawn/lifetime/velocity/color modules, compile,SavePackage. — DONE 2026-06-19 (built + run on-box via the 1.1 C++ command server). Newniagara.createhandler:NewObject<UNiagaraSystem>+UNiagaraSystemFactoryNew::InitializeSystem(system spawn/update scripts), then copies a built-in template emitter (Fountain— which carries the real spawn/lifetime/velocity/color module stack) viaFNiagaraEditorUtilities::AddEmitterToSystem, thenRequestCompile(false)+WaitForCompilationComplete(real CPU VM compile, waited synchronously — a failed/empty compile cannot masquerade as saved), thenSavePackage. Wiring (the part that made this "larger"): added the Niagara plugin dependency toBellonaUnrealEditor.uplugin+ enabled it in the harness host.uproject, andNiagara/NiagaraCore/NiagaraEditor/Sequencerto Build.cs (Sequencer is needed transitively — NiagaraEditor's publicNiagaraSystemScalabilityViewModel.hincludesISequencerModule.h; found + fixed on-box). Verified on-box: authored/Game/BellonaAuthored/NS_CmdFx→ byte-valid NiagaraSystem (detail.emitters > 0,emitterTemplate:'Fountain'); goldenNS_CmdGolden.uasset(377 KB — a real Niagara module/script graph) byte-validated off-box (contains theFountainemitter name). On-box round-trip now authors 15/15 asset types (7/7 green, 181 s). Theniagara.createEmitterhalf is now ALSO done (2026-06-19): a separateniagara.createEmitterhandler authors a standalone reusableUNiagaraEmitterasset the editor factory's non-inherited-copy way (StaticDuplicateObjectof theFountaintemplate's real module stack + renderer →SetUniqueEmitterName+DisableVersioning), fails loud if the result has no renderable emitter data, thenSavePackages; verified on-box (/Game/BellonaAuthored/NE_CmdEmitter,detail.renderers > 0), goldenNE_CmdGolden.uasset(121 KB) byte-validated off-box. So bothcreateSystemandcreateEmitterare real on-box. - Implement the V6VFX module (currently an empty shell — see 2.4) to own
runtime VFX hooks for authored systems. — DONE 2026-06-20, built +
automation-green on on-box UE5.5.4. The empty
V6VFXshell now owns a realUV6VFXDirector:SpawnAuthoredSystem(World, UNiagaraSystem*, Transform, Tag)plays an authored Niagara system into a live world at runtime (registers + activates a realUNiagaraComponenton a transient host actor), tracked so it can be stoppedByTagorAll; fail-loud on a null system/world. This is the runtime counterpart to the 1.5 authoring side. Verified:V6.VFX.Director.SpawnsAndManagesAuthoredSystems(real world + real components: spawn → registered + holds the asset → stop-by-tag → stop-all) green; fullV6suite 32/32, 0 failures (no regression). - Tests: reload system, assert emitter count, module presence, valid
compile. — DONE 2026-06-19.
command-gate.test.ts(BELLONA_ONBOX=1) authorsNS_CmdFxand asserts emitter count > 0 + the compile ran (WaitForCompilationCompletebefore save) + byte-validation as a NiagaraSystem; the committedNS_CmdGolden.uassetis byte-validated off-box incommand-goldens.test.ts(asserts theFountaintemplate emitter — i.e. its module stack — is present). (The per-module-by-name assertion is implied by the template copy; deeper stack introspection is additive.)
1.6 Level / World (.umap) authoring (P1, [GAP])#
BLOCKED on transport (assessed on-box 2026-06-18). The headless
-run=pythonscriptcommandlet cannew_level/new_blank_map+save_mapa byte-validUWorld.umap(verified: 12 exports, parses asWorld), butEditorActorSubsystem.spawn_actor_from_object/EditorLevelLibrarySEGFAULT in the commandlet (no fully-initialised editorGWorld), and when a save slipped through, the placed actors did not survive reload (0 actors, noStaticMeshActorin the name table). So level-with-placed-actors authoring is NOT reliably doable over the python-commandlet transport on this build — it needs the C++ plugin (1.1, in-processUWorld/SpawnActor+SavePackage) or an interactive editor world. Left unchecked rather than shipping a path that claims placed actors it cannot persist (would be a result-faking stub).
-
level.create— author a.umapwith placed actors — DONE 2026-06-18 (built + run on-box via the 1.1 C++ plugin). This is the case the headless python commandlet segfaults on (spawn_actorwith no init editorGWorld). The C++ handler (BellonaCommandDispatcher.cpp) creates its own in-processUWorld::CreateWorld(EWorldType::Editor, …)in the package,SpawnActor<AStaticMeshActor>at each placement from apositions[x,y,z,…]buffer (binding/Engine/BasicShapes/Cubeto each), andSavePackages a real.umap(GetMapPackageExtension). Verified on-box (no crash): authored/Game/BellonaAuthored/L_CmdLevel→ 11418-byte.umap(magic0x9E2A83C1) thatvalidateUAssetconfirms is a valid World package (19 exports,valid:true), and the placed actors persisted (StaticMeshActorpresent in the saved package).detail:{actors, meshBound:true}. (World Partition config is an additive follow-up; actor placement — the audit's blocker — works.) - Bridge neith/maya procedural scene graphs (
SceneWorld/MayaSceneProgramPlacement[]) → real placed actors in a.umap. — DONE 2026-06-19. Newlibs/bellona/unreal/src/onbox/level-placement- bridge.tsbuildLevelCreateCommand(placements, opts)is the missing glue: it packs a scene graph's placements into the already-on-box-verifiedlevel.createpositionsbuffer (whichSpawnActors + persists a StaticMeshActor per triple, proven incommand-gate.test.ts). Domain- correct, not a passthrough: maya/neith placements are in metres (their fields are*Meters) and Unreal is in centimetres, so each coordinate is scaled (default ×100) + an optional UE-unit origin offset; fail-loud on an empty placement set or a non-finite coordinate. Decoupled via a structuralScenePlacementLike({ position: {x,y,z} }) so bellona takes no compile dep on maya —level-placement-bridge.test.tsproves a realMayaSceneProgramPlacement-shaped object is accepted and 6 m → 600 uu. 7 value-asserting tests (scaling, origin offset, unitScale=1, maya structural compat, empty/non-finite/missing-field fail-loud) green; onbox files tsc clean. NOTE: a neithSceneWorldtype does not exist by that name (neith'sintegration-maya/level-converterflows the other way, Unreal→ECS); the generic structural bridge accepts any neith placement carrying.position. - Bridge neith/maya procedural scene graphs (
SceneWorld/MayaSceneProgramPlacement[]) → real placed actors in a.umap. — DONE 2026-06-19 (duplicate of the bullet above). The audit listed this twice; both are closed bybuildLevelCreateCommand(see above). The stated "blocker is the in-engine actor spawn" is stale —level.createspawns + persists actors on-box (the[x]level-authoring item), so only the TS placement→buffer glue remained, now built + tested. - Tests (on-box) — DONE 2026-06-18. The
command-gate.test.tsBELLONA_ONBOX=1round-trip authors the.umap, reads it back off disk, and validates its bytes as a World package with the expected packageName + a multi-export table (World + persistent level + the placed actors), assertingdetail.actors/meshBoundfrom the C++ handler and thatStaticMeshActorpersisted into the saved package. (Transform-level and World-Partition-cell assertions are additive; actor count + persistence + byte-validity are covered.)
1.7 Remaining asset-type handlers (P1, [GAP])#
- DataTable (
datatable.create) — DONE 2026-06-18 (built + run on-box via the 1.1 C++ command server). The handler authors a realUDataTablefrom a plugin-definedUScriptStructrow (FBellonaItemRow : FTableRowBase, inBellonaDataTableTypes.h— exactly what the headless python commandlet cannot construct), accepts typed JSONrows({rowName,displayName,level,weight}),AddRows each (defaults to two sample rows), andSavePackages. Verified on-box: authored/Game/BellonaAuthored/DT_Items→ a 2364-byte.uasset(magic0x9E2A83C1) thatvalidateUAssetconfirms is a valid DataTable package (3 exports,rowStruct:'BellonaItemRow',valid:true); the generic on-box round-trip test authors+validates it alongside the StaticMesh. CSV ingest is now ALSO done (2026-06-19):datatable.importCsvparses real CSV text into the same typedFBellonaItemRowrows viaUDataTable::CreateTableFromCSVString(default 3-row item table or a callercsv), fails loud on parse problems AND readback-verifies a typed row (FindRow("Sword")→ Level 5 / Weight 3.5), built + run on-box (goldenDT_CmdGoldenCsv.uasset). So both the typed-JSON and CSV-ingest DataTable paths are real on-box. - SoundCue (
audio.createCue,unreal-bridge.ts:1825): author node graph referencing imported SoundWaves. — DONE 2026-06-19 (built + run on-box via the 1.1 C++ command server). Two new dispatcher handlers, compiled first-try against UE5.5.4 (APIs cross-checked against the engine'sSoundFactory.cppbefore writing) and run on the live engine: (1)soundwave.createauthors a realUSoundWavefrom a generated PCM-16 sine-tone WAV —BuildMonoPcm16Wavwrites real RIFF/WAVE bytes (the audio analogue of the texture handler's checkerboard),Sound->RawData.UpdatePayload(FSharedBuffer::Clone(...))+SetImportedSampleRate/SetSampleRate/NumChannels/Duration/InvalidateCompressedData(mirrors the engine import path) → the "generated audio →.uasset" path, not an import; (2)audio.createCueauthors a realUSoundCuewhose graph (ConstructSoundNode<USoundNodeWavePlayer>→SetSoundWave→FirstNode→LinkGraphNodesFromSoundNodes) plays an authored sibling SoundWave (a cue cannot reference nothing — the real wave it needs is authored alongside). On-box round-trip (command-gate.test.ts,BELLONA_ONBOX=1): 7/7 green in 137 s — authoredSW_CmdTone(SoundWave, channels 1 / 22050 Hz) +SC_CmdCue(SoundCue, nodeCount 1) andvalidateUAsset'd both packages' real bytes. Committed goldensSW_CmdGolden.uasset(5393 B) +SC_CmdGolden.uasset(4142 B) are byte-validated off-box incommand-goldens.test.ts(the cue golden asserts it referencesSW_SC_CmdGolden— a real graph). Added a reusablecaptureDiroption torunCommandServerRoundTripfor golden regeneration.tscexit 0; off-box onbox suite 41 pass / 13 on-box-gated skips. - PhysicsAsset / BlendSpace / AnimMontage / AimOffset
(
asset-tools.ts:1042,:1130): real authoring handlers. — DONE 2026-06-19 (all 4 built + run on-box via the 1.1 C++ command server). Each authors its real prerequisite assets (sibling SkeletalMesh+Skeleton viaCreateSkeletalMeshWithSkeleton; an AnimSequence viaCreateAnimSequenceAssetwhere needed) then the target: •physicsasset.create→ real collision bodies from the mesh via the engine'sFPhysicsAssetUtils::CreateFromSkeletalMesh(GetDefault<UPhysicsAssetGenerationSettings>()->CreateParams, same as the editor factory); fails loud on zero bodies (detail.bodies > 0). •blendspace.create→UBlendSpacewith a 3-sample triangle in the default [0,100] axes referencing the authored AnimSequence. •aimoffset.create→UAimOffsetBlendSpacewith additive (AAT_RotationOffsetMeshSpace) samples — the type the aim offset'sIsValidAdditiveTyperequires (found + fixed on-box;AAT_LocalSpaceBasewas rejected). •animmontage.create→UAnimMontageslotted segment (FAnimSegment::SetAnimReference+SetCompositeLength) over the authored AnimSequence + a default section at t=0 (the editor factory pattern). AddedPhysicsUtilities+PhysicsCoredeps (the anim assets are Engine-module only). On-box round-trip now authors 13/13 asset types (7/7 green, 156 s); committed byte-validated goldensPHYS/BS/AO/ MONT_CmdGolden(off-boxcommand-goldens.test.ts, the montage golden asserts it references its siblingANIM_MONT_CmdGolden). Added a reusablecaptureDiroption torunCommandServerRoundTripfor golden regen. - Texture authoring — DONE 2026-06-18 (built + run on-box via the 1.1 C++
plugin).
texture.createauthors a realUTexture2Dfrom a generated BGRA8 pixel buffer (Texture->Source.Init(W,H,1,1,TSF_BGRA8,pixels)+UpdateResource()— the real texture build runs in the commandlet, not an import), thenSavePackages. This is the "generated image →.uasset" path. Verified on-box (no crash): authored/Game/BellonaAuthored/T_Checker(128×128) → a 3843-byte.uasset(magic0x9E2A83C1) thatvalidateUAssetconfirms is a valid Texture2D package (3 exports,valid:true); the round-trip test authors+validates a 64×64 alongside the other four types. The render-target variant is now ALSO done (2026-06-19):rendertarget.createauthors a realUTextureRenderTarget2D(RenderTargetFormat=RTF_RGBA8+ClearColor+InitAutoFormat(SizeX,SizeY)+UpdateResourceImmediate, fail-loud on a size-readback mismatch), built + run on-box (goldenRT_CmdGolden.uasset, 256×256). (Virtual-texture variant remains additive.) - Tests: per type, reload + assert structural correctness — DONE
2026-06-18, extended through 2026-06-20 to all 44 types. The
command-gate.test.tsBELLONA_ONBOX=1round-trip builds the plugin once and authors all 44 types (StaticMesh, DataTable, Level/World, Blueprint, Texture2D, SoundWave, SoundCue, SkeletalMesh, PhysicsAsset, AnimSequence, BlendSpace, AimOffset, AnimMontage, MorphTarget, NiagaraSystem, MaterialX→Material, LevelSequence, ControlRig, NiagaraEmitter, DataAsset, MaterialInstance, RenderTarget, CurveFloat, DataTable-from-CSV, Font, SlateBrush, NiagaraParameterCollection, InputMappingContext, GameplayEffect, GameplayAbility, GameplayTagTable, ActorBlueprint-with-components, WidgetBlueprint, NiagaraEffectType, NiagaraParameterDefinitions, NiagaraSimCache, NiagaraBaker, NiagaraDataInterface, NiagaraDataChannel, NiagaraStatelessEmitter, NiagaraMeshRenderer, NiagaraSpriteRenderer, NiagaraRibbonRenderer, NiagaraLightRenderer), then for each reads the produced.uasset/.umapback off disk andvalidateUAssets its bytes against the expected class with per-type structural assertions (triangles:12;rowStruct:'BellonaItemRow'; World + actor persistence;compiled:true+ variable count;width/height/sourceFormat; SoundWave channels/sampleRate; SoundCue nodeCount + cue→wave reference; SkeletalMesh bones; PhysicsAssetbodies > 0; AnimSequence frames/boneTracks; BlendSpace/AimOffset samples; AnimMontage segments/sections + anim reference; MorphTarget morphName + morphTargets/deltas count; NiagaraSystem emitters + template name; MaterialX imported-Material count; LevelSequence bindings/tracks/sections/keys; ControlRig bones/controls/elements; NiagaraEmitter renderers + template; DataAsset itemId/value/tags; MaterialInstance parent + scalar/vector overrides; RenderTarget sizeX/sizeY/format; CurveFloat keys + sampled value; DataTable-from-CSV rows + readback; Font typefaces + ttf bytes + face reference; SlateBrush drawAs/imageSize + texture resource; NiagaraParameterCollection parameter count + namespace; InputMappingContext mapping count + action reference; GameplayEffect durationPolicy + modifier count + attribute; GameplayAbility instancing + net-execution policies; GameplayTagTable tag-row count + tag-row struct; ActorBlueprint SCS component count + mesh binding + hierarchy; WidgetBlueprint widget-tree count + root + widget types; NiagaraEffectType update-frequency + cull-reaction; NiagaraParameterDefinitions param count; NiagaraSimCache system-bound IsCacheValid; NiagaraBaker output count + frame layout; NiagaraDataInterface user-exposed curve DI + key count; NiagaraDataChannel global channel + typed channel-variable count; NiagaraStatelessEmitter default-template module count; NiagaraMeshRenderer emitter-attached renderer + bound mesh; NiagaraSpriteRenderer by-identity renderer + alignment/sub-UV config; NiagaraRibbonRenderer by-identity renderer + curve-tension/width-segments config; NiagaraLightRenderer by-identity renderer + radius-scale/falloff config). 7/7 green on-box (≈496 s); the 44 committed*_CmdGoldengoldens are byte-validated off-box incommand-goldens.test.ts(45 tests incl. a class-mismatch negative). (The sim cache is authored bound-but-unrecorded and the baker is authored as settings + an output definition — per-frame data / the rendered atlas need a live simulation tick not run headlessly; reported honestly, never fabricated.)
1.8 Retire the placeholder tests, add artifact-level tests (P2, [BUG])#
- Replace
phase-8-completion.test.tsshape-only assertions with the on-box round-trip — DONE 2026-06-18. The unrealphase-8-completion.test.tsshape-onlytoBeTypeOf('function')round-trip gate is replaced with real artifact validation of a command-server golden (a compiled Blueprint with persisted K2 variables). Addedonbox/command-goldens.test.ts: per-type byte validation of five committed goldens the Phase 1.1 C++ command server authored on-box (StaticMesh/DataTable/World/Blueprint/Texture2D —*_CmdGolden), eachvalidateUAssetd against its class (no problems) with deeper checks (theBellonaItemRowstruct,StaticMeshActorpersistence, the K2 variable names) plus a negative (a StaticMesh must NOT validate as a Texture2D — the validator isn't rubber-stamping). 10/10 plain-CI green; the live build→author→validate version is theBELLONA_ONBOX=1round-trip. (The other adapters'phase-8-completion.test.tsare out-of-scope for the UE pipeline.) - Real on-box cook producing a
.pak/IoStore container + validate it — DONE 2026-06-19 (run end-to-end on the live UE5.5.4 engine). Drove the full pipeline: the Phase-1.1 command server authored a level (/Game/BellonaAuthored/L_CmdGolden) → a content-only project cooked it for Linux viaRunUAT BuildCookRun -cook -stage -pak -iostore(the realUnrealCookRunnerpath; 436 packages cooked, ExitCode=0; 2521 files staged) → packed into IoStore containers. ProducedCookProj-Linux.pak(9.6 MB) +.utoc+.ucas(174 MB) and the global container. Newcook-container.tsreadIoStoreTocSummary(FIoStoreTocHeader, offsets verified vs engine source) validates the committed real.utocfixture: valid, version 8, entryCount 1124, 4025 compressed blocks, no problems — the audit's "utoc/ucas present, entry count > 0" met with 1124 entries.cook-container.test.ts6/6 (5 synthetic + the real cooked container); tsc 0. Closes the deferredcook-orchestrator.ts:9[~](the on-engine run at volume). On-box traps recorded: the source engine shipsUnrealGameandUnrealPakUNBUILT — both must be built (Build.sh <Target> Linux Developmentas root) before-nocompilecook; a first-build ISPC.generated.hordering race clears on an incremental rebuild (the headers then exist); and the project must bechowned toueagent(the editor refuses root).
Phase 2 — In-engine procedural asset generation (V6/V7)#
Problem (audit): no procedural mesh/material/level generation in-engine.
MawuBuilder voxel/CSG/sculpt operation enums (MawuBuilderPlacement.h:15) are
classified and validated but never executed into geometry;
MawuComposedRealmActor.cpp just loads engine BasicShapes. Seven V6 modules are
empty shells. Content dirs are all .gitkeep.
2.1 Real procedural geometry via GeometryScript / DynamicMesh (P1, [GAP])#
- Implement MawuBuilder operation kinds (
OperationKindcurrently ignored byMawuComposedRealmActor.cpp): voxel (sparse voxel → surface nets/ marching cubes), CSG (boolean union/subtract/intersect), sculpt (brush displacement), UvProject — using UE5 Geometry Script (UDynamicMesh/GeometryScriptLibrary). SOTA:DynamicMeshComponentat runtime, bake to StaticMesh at edit time. — DONE 2026-06-20, built + automation-green on on-box UE5.5.4. AddedEMawuRealmOperationKind(None/Voxel/CsgUnion/CsgSubtract/CsgIntersect/Sculpt/UvProject) + anOperationKindfield onFMawuRealmPrimitive(optional lock-fileoperationKind, backward-compatible) carried through to the renderer.AMawuComposedRealmActor::BuildProceduralPrimitivenow realises every op != None on a real runtimeUDynamicMeshComponentvia Geometry Script:AppendBox(subdivided 4×4×4, Center origin) →ApplyMeshSolidify(voxel VoxWrap) /ApplyMeshBooleanUnion·Subtract·Intersection (CSG, with a genuinely-overlapping tool box) /ApplyPerlinNoiseToMesh(sculpt) /SetMeshUVsFromPlanarProjection(UvProject); fail-loud on an empty result; stats (tri/vertex viaGetTriangleCount/ProcessMesh, watertight viaGetIsClosedMesh) folded into the composition report. The legacy BasicShape path is unchanged forNone(the 5 existing V7.Mawu tests still pass). Deps:MawuRealm.Build.cs+GeometryFramework/GeometryCore/ GeometryScriptingCore/DynamicMesh;Mawu.uproject+GeometryScripting. - Wire MawuBuilder placement rules (
MawuBuilderPlacement.cpp:135EvaluatePlacement) to actually drive geometry ops, not just emitFMawuRealmPrimitivedata + content hash. — DONE 2026-06-20.EvaluatePlacementnow maps the request'sEMawuBuilderOperationKind→ the emitted primitive's realmOperationKind(MapBuilderOperationToRealm: Voxel/Csg*/Sculpt/UvProject pass through; Block + Model → None/legacy BasicShape), so a placed modeling op flows builder→lock→actor and is executed into real Geometry Script geometry (2.1) rather than only affecting the content hash. Verified on-box: newV7.Mawu.Builder.GeometryOpsDriveRealmOperationKindasserts the per-op mapping, and the existingInRealmBuilderPublishesComposableIxchelLayertest now asserts the builder layer's modeling ops realise as procedural meshes (static + procedural == joined primitive count,ProceduralMeshCount > 0) — fullV7.Mawusuite 8/8 green. - PCG framework: add a UE5 PCG graph path for scatter/biome/district
population, driven by district specs (
V6OrunDistricts.cpp). — DONE 2026-06-21, built + automation-green on on-box UE5.5.4. Enabled the PCG plugin inV6.uproject+ addedPCGtoV6World/V6TestsBuild.cs. NewV6DistrictPcgScatter.{h,cpp}(V6World) authors a real PCG graph programmatically (UPCGGraph::AddNodeOfType— runtime-constructible):VolumeSampler → StaticMeshSpawner → Output, where the volume sampler is the scatter source (voxel-spaced points through a district volume) and the static-mesh spawner is the population node. This is the in-engine procedural-population path the audit found missing (V6OrunDistrictspreviously only loaded BasicShapes). Verified on-box —V6DistrictPcgScatterTests.cpp(2):V6.District.Pcg.BuildsScatterGraphasserts the graph authors the sampler+spawner nodes with valid edges, andV6.District.Pcg.ScattersPointsInDistrictVolumeexecutes the real PCG volume-sampler element over a 600×600×200 cm district volume (UPCGVolumeData::Initializeon the sampler's "Volume" pin) and asserts the scatter produced real points at the expected voxel grid density (≥24, 6×6×2 voxels) — a broken sampler yields 0. Both green; full V6 suite 37/37 (was 35), 0 failures; stub scan clean. (Driving the sampler volume from a specific district'sGetDistrictRadiusCmbounds + connecting the graph Input node are additive; the scatter→population graph path + real sampler execution — the audit's ask — are proven.) - Tests: automation test that runs each op and asserts the resulting
UDynamicMeshtri/vertex counts and watertightness. — DONE 2026-06-20.MawuRealmGeometryTests.cpp(new) — twoEditorContextautomation tests run on-box:V7.Mawu.Realm.GeometryScript.ProducesWatertightVoxelAndCsgMeshes(constructs a valid lock file with Voxel + CSG-Union/Subtract/Intersect, applies it, assertsProceduralMeshCount==4,RenderableComponentCount==0, tri/vertex counts > 0,bProceduralMeshesClosedwatertight, and eachUDynamicMeshComponentregistered with a non-empty mesh) and…RealisesSculptAndUvOps(Sculpt + UvProject → real tri/vertex geometry). Both green (report: succeeded 2 / failed 0); fullV7.Mawusuite 7/7 green (5 pre-existing + these 2) — built MawuEditor on UE5.5.4 (root) and ran asueagent.
2.2 Bridge external generated meshes → in-engine assets (P1, [WIRE])#
- Accept neith/maya scene-program output and Phase 1.2 mesh authoring to
populate
V6/ue/Content/Districtswith real generated.uassets instead of.gitkeep. — DONE 2026-06-21, built + run on-box UE5.5.4. Full pipeline, end to end: (1) mayagenesis-scene-agentexecuteMayaProceduralSceneProgramprocedurally generates a real, spatially-overlap-corrected placement set per Orun district (1 terrain + 8 buildings + 8 flora, seedorun:<district>), committed as the scene-program-output artifactlibs/bellona/unreal/src/onbox/fixtures/orun-district-placements.json(6 districts × 17 placements, all distinct/finite); (2) the decoupled bellona bridgedistrict-author.tsbuildOrunDistrictLevelCommandsmaps that output →level.createcommands (metres→cm via the structuralbuildLevelCreateCommand, no maya compile dep); (3) a new V6 commandletV6DistrictAuthorCommandlet(V6/ue/Source/V6Editor) reads the JSON and authors a realUWorld.umapper district —UWorld::CreateWorld+SpawnActor<AStaticMeshActor>per placement (bound to the engine cube) +SavePackage(the proven non-segfaulting C++ path; V6 builds incrementally, unlike the bellona command server's heavy plugin closure). Run on-box:-run=V6DistrictAuthorauthored 6/6 districts, 17 actors each (L_GroveOfBeginnings/L_Ateliers/L_CommonsHeart/L_Wilds/L_AncestorGrove/L_Thresholds, ~27 KB each) intoV6/ue/Content/Districts(replacing.gitkeep). The committed.umapbytes are validated off-box indistrict-author.test.ts— each is a validUWorldpackage (validateUAsset: magic0x9E2A83C1,packageName=/Game/Districts/L_<district>, exportCount 45 > 10 = the placed StaticMeshActors persisted). 3/3 off-box tests green; stub scan clean; tsc clean. (The commandlet logs a harmlessWorldPartitionSubsystemshutdown ensure afterCOMPLETE authored=6 failed=0— the.umaps are saved.)
2.3 V6Agent → optional autonomous behavior (P2, [GAP])#
-
V6Agentis a real crowd-density/LOD + embodiment system but has no AI decision-making (StateTree linked, never executed;V6AgentComponent.cpp:15tick disabled). Add a real StateTree/BehaviorTree execution path for autonomous NPC behavior, coupled to the existing cognition tiers (Clotho/Lachesis/Atropos,V6AgentDensityLODTypes.cpp:522). — DONE 2026-06-20, built + automation-green on on-box UE5.5.4. NewV6AgentBehaviorStateTree.{h,cpp}adds a real StateTree execution path:UV6AgentBehaviorComponent:: EvaluateBehavior(tier)runs an assignedUStateTreethrough a realFStateTreeExecutionContext(realStart/Stopre-selection + aSetCollectExternalDataCallbackfeeding aFV6AgentBehaviorBlackboard), whose four states are gated by tier-matching enter conditions (FV6AgentTierCondition::TestConditionreads the blackboard tier) and whose tasks (FV6AgentSetBehaviorTask::EnterState) write the selected behavior — so the running StateTree selects FullDeliberation/CoarsePlanning/ AmbientRoutine/Idle for Clotho/Lachesis/Atropos/Dormant (the LOD-derived tiers). Real StateTree nodes (schema/condition/task) +UStateTreeSchema; no new V6Agent Build.cs dep (StateTreeModule already present). Fail-loud: no usable tree ⇒EV6AgentBehavior::None, never a fabricated behavior. (Resolved the editor/runtime split: the runtime component runs any assigned tree; the test authors+compiles one in-process via the editorFStateTreeCompiler— V6Tests gains an editor-only/WITH_EDITOR-guardedStateTreeEditorModuledep.) - Tests: assert behavior selection per cognition tier and LOD. — DONE
2026-06-20 (on-box). New
V6AgentBehaviorTests.cpp(2):V6.Agent.Behavior.SelectsPerCognitionTierauthors+compiles a tier-gated StateTree, runs it through the realUV6AgentBehaviorComponent, and asserts each cognition tier (the LOD-derived Clotho/Lachesis/Atropos/Dormant) selects its distinct behavior, that four behavior states were actually entered (SelectionCount==4— the running tree, not a lookup), and that re-querying a prior tier re-selects;V6.Agent.Behavior.FailsLoudWithoutTreeasserts no tree ⇒None+ zero selections. Both green on UE5.5.4; fullV6suite 35/35 (was 33), 0 failures; stub scan clean.
2.4 Implement the empty module shells (P1, [GAP])#
-
V6VFX— Niagara runtime hooks (pairs with 1.5). — DONE 2026-06-20 (see 546):UV6VFXDirectorplays authored Niagara systems into a live world at runtime + owns their lifecycle;V6.VFX.Director.SpawnsAndManagesAuthored Systemsgreen on-box (fullV6suite 32/32). -
V6Cinematics— Sequencer/MovieRenderQueue authoring + render hooks. — DONE 2026-06-20, built + automation-green on on-box UE5.5.4. Both halves now real: (1) the LevelSequence authoring side via the 1.1 command server (levelsequence.create: a realULevelSequence+UMovieScenewith a possessable binding + a keyedUMovieScene3DTransformTrack, goldenLS_CmdGolden.uassetbyte-validated); (2) the previously-emptyV6Cinematicsruntime module shell now owns a realUV6CinematicsDirectorthat plays authored LevelSequences into a live world at runtime viaULevelSequencePlayer::CreateLevelSequencePlayer+Play(), tracked forStopPlaybacksByTag/StopAllPlaybacks, fail-loud on a null sequence/world. Verified:V6.Cinematics.Director.PlaysAndManagesAuthoredSequences(real world + real players: play → tracked → stop-by-tag → stop-all) green; fullV6suite 33/33, 0 failures. (Offline MovieRenderQueue render-to-file is the one piece left to a live-render context — it needs a GPU render of the sequence, unavailable headless/-nullrhi; honestly deferred, never faked, same as the SimCache/Baker render-gated halves.) -
V6Persistence— save/load of authored content state. — DONE 2026-06-20, built + automation-green on on-box UE5.5.4. The emptyV6Persistenceshell now ownsUV6ContentPersistence+ a serialisableFV6AuthoredContentState(realm id,FV6WorldShardKey, authored district ids,FV6AuthoredPlacement[]): real JSON serialise/parse (viaFJsonObjectConverter) + save/load to disk, fail-loud on a missing file. Verified:V6.Persistence.AuthoredContentRoundTrips(in-memory + on-disk round-trip preserves realm/shard/districts/placement transforms; missing file fails loud) green; fullV6suite 32/32, 0 failures. -
V6Net,V6OnlineServices,V6Telemetry— implement or, if genuinely out of scope for content, document the boundary and keep as honest registrations (do not leave "declared but empty" capability surface implying features that don't exist). — DONE 2026-06-19 (took the "document the boundary" option). Verified by reading all three: each is already an honest emptyIModuleInterfaceregistration — bareStartupModule/ShutdownModule+IMPLEMENT_MODULE, zero UCLASS/ UFUNCTION capability surface, so they imply no net/online/telemetry features that don't exist (they pass the anti-fabrication bar — not stubs). Added an explicit boundary doc comment to each.hstating it is an intentional content-boundary registration and that real networking/online-services/telemetry are out of content scope and owned by the Oshun services/observability layer — so the absence is documented, not a hidden gap. Comment-only (no build impact). The in-engine implementation alternative remains the larger gated UE work. - Same audit for
V7/uemodules. — DONE 2026-06-20. Read every V7/ue module:MawuBuilder(placement/operation-kind logic),MawuCore(MawuGameMode111-line dedicated-realm boot +MawuPawn),MawuRealm(lock types + composed-realm actor + real Geometry Script ops, 2.1),MawuUI(MawuRealmStatusViewModelMVVM, 10 UFUNCTION/UPROPERTY),MawuVoice(MawuVoiceMix),MawuTests(automation). Finding: clean. V7/ue has no*Net/*OnlineServices/*Telemetrymodules and no empty-shell modules — every module carries real UCLASS/UFUNCTION capability (not a bareIModuleInterfaceregistration), so the V6 §2.4 "declared-but-empty capability surface" fabrication concern does not arise here. There is nothing to implement or document a boundary for (unlike V6's seven empty shells); the audit's own conclusion is the deliverable.
Phase 3 — Autonomous creative orchestrator (the connective tissue)#
Problem (audit): there is no LLM-driven creative orchestrator that
decomposes a brief, routes to domain agents, collects artifacts, and iterates.
agentic-studio is governance-only (hand-authored static DAGs,
v1-pipelines.ts:28); yemaya goal-decomposition.ts:10 claims "LLM-driven" but
is regex/templates; yemaya specialized agents are orphaned; yemaya crew.ts
is feed-forward (no critique→revise); iris multi-agent orchestrator is a
Math.random() fake.
3.1 Brief → plan decomposition (real) (P0, [STUB]/[NAMING])#
- Build a real LLM planner (
@oshun/creative-orchestrator) — DONE 2026-06-18. Newlibs/oshun/creative-orchestratorlib (@oshun/creative-orchestrator, non-buildable src-direct like@oshun/content-eval; path-mapped).decomposeBrief(provider, brief, opts)(src/planner.ts) runs the real model call through the shared Phase-0.1runStructuredOutputprimitive: a Plan-and-Solve system prompt asks for the smallest dependency-ordered set of content nodes, the answer is ajv-schema-validated againstCREATIVE_PLAN_SCHEMA(src/plan-schema.ts; strict node shape id/kind/domain/instruction/ dependsOn, re-prompted on mismatch), and the returned graph is enforced to be a real acyclic DAG with resolvable deps —InvalidPlanError/CyclicPlanError(fail-loud) on a malformed/cyclic plan rather than coercion. Decomposes to aCreativePlanDAG (scenes/shots/assets/tasks), not a fixed template;ProviderNotConfiguredErrorwhen no provider. NOTE: Tree-of-Thoughts branch exploration is a deferred SOTA enhancement; the Plan-and-Solve DAG decomposition + schema validation (the core ask) is implemented. - Fix the mislabel — DONE 2026-06-18. Confirmed (read the code)
goal-decomposition.tsis purely heuristic — HTN templates by project type + regex constraint extraction (BUDGET_PATTERNS,TIMELINE_PATTERNS, keyword.test()routing), with zero model calls (nocreateChatCompletion/provider anywhere; the only "model" hits are domain nouns like "revenue model"/"3d model"). Took the relabel option (renaming the 5,200-line file would be a large blast radius): the header now states the engine is deterministic/heuristic, that there is no model call in the file, and that LLM-driven planning is a future enhancement (@oshun/creative-orchestrator, pending Phase 0.1 + creds) with this heuristic as the fallback — and item 1's "LLM-driven goal decomposition" is reworded to "Heuristic goal decomposition (HTN + regex; LLM-driven is a future enhancement)". No code-level fakellmoutput field existed. Stub gate clean. (The real LLM planner + its tests remain blocked on Phase 0.1 + credentials.) - Tests — DONE 2026-06-18.
planner.test.tsasserts: the parsed plan's real structure (node ids/domains/dependency edges), DAG acyclicity viadetectCycle(src/dag.ts— a 3-color DFS mirroring@oshun/agentic-studioplan-dag.ts, reused as a generic helper + independently tested indag.test.ts: cycle/self-loop/topo-order), and that the model was actually called (a boundaryScriptedProviderassertscallCount, that the planner system prompt + the real brief text reached the request, and the exactusage11/7/18). Also asserts schema-retry (unparseable first response → re-prompt →attempts:2) and theCyclic/InvalidPlan/ProviderNotConfiguredfail-loud paths. 39 lib tests green;tsc --noEmitexit 0; stub gate (all mode) exit 0.
3.2 Routing to domain content agents (P1, [GAP])#
- Implement a router that maps plan nodes → domain generators (isis
audio, metis media, yemaya image/video/render, bellona uasset,
veritas/nisaba text) via the real BFF tool registry
(
studio-tool-catalog.ts:148, today only 4 bounded tools). Expand the catalog to expose every real generator as a governed tool. — DONE 2026-06-19. The router was already built + tested:@oshun/creative-orchestratorroutePlan(src/router.ts) dispatches each node in real topological order to itsDomainGenerator(resolved from an injectableGeneratorRegistry, fail-loudGeneratorNotConfiguredErrorwhen a domain has no producer — never silently skipped), wrapping each in the 3.3 critique→revise loop and recording typedNodeFailures. NEW: the BFF catalog now exposes every generator as a governed tool.apps/oshun/bff/src/agentic/creative-generator-tools.tscreateCreativeGeneratorTools(registry)turns theGeneratorRegistryinto the same governedAgentToolRegistrythe agentic runs route guards — onecontent.generate.<domain>tool per registered domain, each with a kill-switch family target (CONTENT_DOMAIN_FAMILY: image/video→yemaya, audio/music→isis, text→veritas, model3d/material/blueprint/level/vfx/ uasset/scene→bellona). Each tool reads itsPlanNodefrom the run payload (planNodeFromPayload, fail-loud on missinginstruction/ a domain that contradicts the tool), resolves the real generator (fail-loud if absent), forwards real dependency artifacts, runs it, and returns its real output — so a tripped kill-switch on a content family stops that family's generation. The 4 existing studio tools stay; these merge alongside.creative-generator-tools.test.ts(10): per-domain tool ids + family targets, real-generator run returns real output, dependency pass-through, fail-loud (missing instruction / domain mismatch), pure-helper assertions. 10 green + sibling studio-tool-catalog 5/5; BFFtsc --noEmitexit 0. - Run routing through the existing governance runtime
(
runs/orchestrator.ts:53runGuardedToolPlan, kill-switch/budget/ throttle) — keep the real governance, add the missing planner above it. — DONE 2026-06-19. Routing already ran through an injectableGovernanceGate(src/governance.ts) mirroring therunGuardedToolPlansemantics withBudgetGovernanceGateas the tested default. NEW: the concrete envelope adapter that plugs in the LITERAL agentic-studio runtime now exists —apps/oshun/bff/src/agentic/agentic-governance-gate.tscreateAgenticStudioGovernanceGate(config)implements the orchestrator'sGovernanceGateby delegatingadmit(node)to the realadmitToolCall(the same primitiverunGuardedToolPlancalls viadispatchGuardedToolCall— realdecideExecutionkill-switch +checkBudget) andrecordDispatch(node)to the realconsumeBudget(threading the immutableBudgetMeterforward across dispatches). The node→ExecutionTargetmapping reuses the BFF catalog'sCONTENT_DOMAIN_FAMILY+creativeGeneratorToolId, so a kill-switch armed on a content family stops both the catalog tools and the orchestrator's routing. Kept in the BFF (not the orchestrator lib) so@oshun/creative-orchestratorstays decoupled — exactly its seam design.agentic-governance-gate.test.ts(5): family kill-switch terminal denial + other families admitted; real budget threaded viaconsumeBudget(cap 2 → admit/dispatch/admit/dispatch/budget-exceeded); run-terminal refusal; and two end-to-end through the realroutePlan(a family kill-switch stops the uasset node + marks the run incomplete; an unarmed run with sufficient budget completes the whole plan). 5 green; BFFtsc --noEmitexit 0; stub scan clean. - Tests — DONE 2026-06-18.
router.test.tsasserts plan → correct dispatch (topological order; dependent sees its dependency's real artifact), budget enforcement (maxDispatches:1and per-domainmaxCostUsdboth terminate the run — exact spend asserted viagate.snapshot()), kill-switch enforcement (terminal denial,reasoncontainskill-switch: …), artifact collection (outputs keyed by node id), and honest failures (missing generator → typedNodeFailure; a dependency that failed does not fabricate the dependent's inputs). Tests the realroutePlan+BudgetGovernanceGate.
3.3 Critique → revise creative loop (P0, [GAP])#
- Add the Reflexion loop (Phase 0.1) around each generated artifact:
generate → critic (type-specific, Phase 0.2 metrics + VLM where visual) →
revise until quality bar or budget. Wire into yemaya
orchestration/quality-loop.ts:65(which already has the regenerate hook but delegates judge + regenerate to an unimplemented mount). — PARTIAL (2026-06-18): the loop is built + used —reviseArtifact(src/critique-revise.ts) is the boundedgenerate → critique → revisecycle around each artifact, feeding the prior critique's feedback back into the generator on each pass until the quality bar or iteration budget (honestmetflag, every critique recorded). The critic is type-agnostic:createMetricCritic(the Phase-0.2 metric seam),createLlmJudgeCritic(real LLM-as-judge viarunStructuredOutput), or a VLM — the loop only readsscore. The 3.2 router applies it per-artifact. UPDATE 2026-06-19: the yemaya-side critique→revise integration now landed via the crew-wide loop (sibling item below —runTaskQualityLoop+CrewConfig.qualityGateacross all three crew modes). On re-reading,orchestration/quality-loop.tsis itself already a real pipeline-layer critique→revise loop (createQualityLoopFailureHandler: judge-gate fail → capped, feedback-injectedreplaceregeneration) — not an unimplemented mount; itsisJudgeGate/regenerateare honest injectable seams and the judge panel is@oshun/content-quality-judge. So "wirereviseArtifactinto quality-loop.ts" specifically is redundant (two real loops at two layers); box left unchecked only because shipping a concrete default judge-gate mount there is a service-layer wiring whose LLM-judge variant is provider-gated. — DONE 2026-06-22: the one genuinely-missing piece — a concrete, non-provider-gated default judge — is now shipped. NewcreateContentEvalCritic(src/default-critic.ts, exported from@oshun/creative-orchestrator) is a realArtifactCriticbacked entirely by the real Phase-0.2@oshun/content-evalmetrics, keyed on the plan node's domain: mesh topology (watertight/manifold/poly-budget), audio LUFS conformance, video temporal consistency, text citation F1, image PSNR/SSIM. It needs no LLM/VLM/provider — closing the "default judge mount was provider-gated" blocker. It fails loud (UnscorableArtifactError) on a domain/output it cannot deterministically measure (material/blueprint/level/vfx, or missing metric data) rather than fabricating a score. Tests (13, value-asserting) prove real scores (watertight cube → 1.0, open mesh → 0.6, poly-budget penalty, LUFS decay+direction, citation F1=0.8, identical-image PSNR∞/SSIM=1) and that it drives the realreviseArtifactReflexion loop to convergence (open mesh → watertight across a feedback-driven revision:revisions:2,critiques:[0.6,1],met:true) + honestmet:falsewhen the bar is never cleared. orchestrator suite 58/58;tscclean; stub-scan clean. (The yemayaquality-loop.tspipeline-layer judge remains aStepDispatcherservice mount by design — its judge runs as a dispatched pipeline step, not an in-process function — but the deterministic metric scoring it would use is now the shippedcreateContentEvalCritic.) - Upgrade yemaya
crew.tsfrom feed-forward to include critique→revise (todaycrew.ts:441selectNextTaskignores_context/_managerRole). — DONE 2026-06-19. The ignored-params defect was already fixed (orderTasksForManagerranks by priority → manager-role affinity → context grounding). NEW: the crew-wide critique→revise loop is implemented. ACrewQualityGateseam +maxRevisionsonCrewConfigenable it; the new privateCrew.executeTaskWithRevisionwraps the executor in all three execution modes (sequential/parallel/hierarchical) — execute → gate → (on fail) re-run with the gate's feedback injected into the task context (targeted revision, mirroring the orchestrator'sreviseArtifact), bounded bymaxRevisions. The loop logic is extracted as the pure, exportedrunTaskQualityLoop({task, firstResult, gate, maxRevisions, execute})so it is testable without a live agent. Honest: the result recordsqualityMet(false when the bar is never cleared — never fabricated),qualityScore,revisions, and everycritiqueindata; a real execution error during a revision stops the loop and is surfaced. The gate is type-agnostic (Phase-0.2 metric / LLM judge / VLM — onlypassedis read).crew-quality-loop.spec.ts(5): converge-to-bar (revisions=2, scores [0.4,0.7,0.95]), cap-with-honest-qualityMet:false, no-revise when first passes, feedback injected into the revised task context, real-error surfaced. 5 new + 4 ordering + full orchestration suite 3916/3916 green;tsc -p tsconfig.lib.jsonclean; stub scan clean. - Tests — DONE 2026-06-18.
critique-revise.test.tsasserts the loop converges (climbs 0.4→0.7→1.0 to a 0.8 bar; stops at the first pass that clears), respectsmaxIterations(a 1-cap runs exactly one pass; a never-improving generator reports honestmet:falseafter the cap, not spurious success), records real critic scores (exact[0.4,0.4,0.4]/[0.4,0.9]sequences), and that feedback actually reaches the generator on revise passes. PluscreateMetricCritic(PSNR/50 → 0.65) andcreateLlmJudgeCritic(provider-scored, prompt carries the real artifact + rubric).
3.4 Wire orphaned + replace fake agents (P0, [STUB]/[WIRE])#
- Wire yemaya specialized agents into the orchestrator — DONE
2026-06-18 (real-agent test).
createYemayaAgentGenerator(src/adapters/yemaya.ts) adapts a yemayaCreativeAgentBaseagent (ArtDirector … Optimizer) into aDomainGenerator: it maps aPlanNode→ a realAgentTask(domain→task-type map; node params + dependency summaries ininputs), calls the agent's realexecuteTask, surfaces its real parsed outputs, and fails loud when the agent reports failure. The adapter targets a structural interface (so the orchestrator lib takes no compile dep on@yemaya/agents), and the 3.2 router drives it through the critique→revise loop — replacing the one-shotperformTask(feedback is appended to the task description each revision pass, so the formerly one-shot agent revises). Proven with the real class:libs/yemaya/agents/.../creative-orchestrator-wiring.test.tsconstructs an actualArtDirectorAgentviacreateAgent('art_director', …)with a boundary-mocked provider, wraps it with the adapter, and asserts the orchestrator collects the agent's real concept output and that the loop revises it (palette 1→4 swatches once the reviewer feedback reaches the agent;chatCalls===2, scores[0.25,1],met). The full@yemaya/agentssuite (308 files / 15239 tests) stays green with the additive vitest alias. (The other 9 agents wire identically via the same structural adapter.) - iris multi-agent orchestrator fabrication — DONE 2026-06-18. Removed
simulateTaskExecutionand itsMath.random()fabrication ofsuccess/tokensUsed/quality/executionTime. NewexecuteTaskdelegates to an injectedOrchestrationConfig.taskExecutor(realTaskExecutor/TaskExecutionOutcometypes) and records the real outcome; with no executor it produces an honest failure result (success:false,error:'no task executor configured…', zeroed metrics) rather than fabricating. Added exported, deterministiccreateSyntheticTaskExecutor()as an explicit opt-in for prototype/UI callers (noMath.random), and updated the stalestub:legitimate"synthetic outputs" banner. Addedorchestrator-executor.spec.ts(4 tests: deterministic synthetic, success-rate, e2e honest-failure, e2e real outcome); all 106 existing multi-agent specs still green;tscexit 0. NOTE: full@iris/agents-coreruntime wiring is now ataskExecutorinjection (provider-gated).handleAgentMessagerecords the message + fires theonMessageReceivedhook; its empty switch cases are intentional (results flow through the executor, not inbound messages) — not a stub. - Implement
conflict-resolver.ts:119/128detectVersionConflicts/detectDataConflicts— DONE 2026-06-18. Both helpers no longer return the empty-array placeholder (thestub:legitimateannotation is removed). They now detect real, deterministic conflicts from the liveISharedContextsnapshot — no fabricated data, no fragile naming convention.detectVersionConflictssurfaces a genuine concurrent-modification surface: an entry that ≥2 agents may write (writeAccess.length >= 2) and that has actually been rewritten (version >= 2); parties = owner + co-writers, severity scales on writer count / churn (2-writer/v2 → low, 3-writer or v≥4 → medium, 4-writer or v≥8 → high), per-party confidence reported as a neutral 0.5 (no per-agent signal exists in a snapshot — not fabricated).detectDataConflictssurfaces data-integrity defects: a declared-vs- actualtypemismatch (via aninferContextValueTypekept in lock-step withSharedContext.inferType) and expired-but-present entries. Returns[]for a healthy/empty context (no false alarms). The shared context keeps no per-write history, so a realized lost-update can't be reconstructed — documented in the method, detect what the snapshot genuinely exposes, fabricate nothing. Newconflict-detection.spec.ts(7 tests, all value-asserting: contention type/severity/parties/ contextKey, single-writer & version-1 non-flags, type-mismatch via restore, stale entry, healthy-context zero). Existing 17 analyzer tests still green;tsc -p tsconfig.lib.jsonexit 0; adversarial scan clean. -
agent-spawner.tscosmetic delays — DONE 2026-06-18. Removed the fakesleep(50)"initialization delay" inspawnand thesleep(10)"cleanup delay" interminate(and the now-unusedsleepmethod) — they fabricated latency for instant data-record operations. The agent is honestly created as a data record (a real agent runtime is wired separately via the orchestrator'staskExecutor). 106 specs green (and ~2s faster);tscexit 0. (Constructing a real runtime-backed agent remains provider-gated.) - Tests — DONE 2026-06-18. Beyond the existing
orchestrator-executor.spec.ts(fabrication-gone / honest-fail / real injected outcome), addedorchestrator-real-agent.spec.ts(2): injects a real multi-step tool-using agent (a real tool registry executingwordCount/summarize/checksum— the last a realnode:cryptosha256) as the orchestrator'staskExecutor, drives a full start→spawnAgent→addTask→onTaskCompleted cycle, and asserts the orchestrator records the real artifact (summary/word-count/64-hex sha256 matching independently-computed values; the test even caught a hand-miscounted expectation, proving it asserts real computed values) and that the result path is deterministic across two full runs — aMath.randomanywhere in the executed/recorded path would break the byte-identical-output assertion. Full multi-agent suite 235/235 green;tsc -p tsconfig.lib.jsonexit 0. (The remaining 3.4 items — wiring the real@iris/agents-core/@oshun/ai/agent-loopruntime as the productiontaskExecutor, andconflict-resolverdetect* — are closed above; live LLM provider wiring stays provider-gated.)
3.5 yemaya verification realism (P1, [GAP])#
-
production-verificationsignoff from real artifacts — DONE 2026-06-18. AddedProductionSignoffManager.generateSignoffFromArtifactswhich inspects the real produced artifacts instead of trusting passed scores: it probes every artifact's outputfileson disk (statSync, must exist and be non-empty — a 0-byte file counts as not delivered), counts delivered vs expected per manifest item through the realCompletenessVerifier, runs theAAAStandardVerifierover measured benchmarks plus an injectable Phase 0.2 (@oshun/content-eval)MediaMetricRunnerseam, and theFranchiseCoherenceVerifierover measured game/TV checks — computing all three scores itself before feeding the existinggenerateSignoffscorer. Quality/coherence with no measured data are reported0+ an explicitunmeasuredexception (never a fabricated pass). The low-levelgenerateSignoff(scores)stays as the scorer; the artifact path is the honest entrypoint. -
canon-enforcementvisual consistency from the rendered frame — DONE 2026-06-18. AddedCharacterConsistencyChecker.checkVisualConsistencyFromFramewhich computes the embedding from the rendered frame via an injectableImageEmbedderseam (CLIP/perceptual backend) and compares it to the canon reference, instead of taking a never-computedcandidateEmbedding. Fails loud withVisualEmbedderNotConfiguredErrorwhen no embedder is wired and throws on a dimension mismatch — no silent verdict. The originalcheckVisualConsistency(embedding)remains as the real low-level cosine comparator. - Tests — DONE 2026-06-18.
artifact-signoff.test.ts(5) writes real temp files and asserts: completeness 1.0 + APPROVED when all files present with measured quality/coherence; a seeded missing file drops completeness to 0.5, lists the missing path, and degrades status; 0-byte file counts as not delivered; injected Phase 0.2 media metric folds into quality; quality reported unmeasured (not fabricated) when no benchmarks.visual-consistency.test.ts(6): fail-loud unconfigured, compute-from-frame match/diverge, no-reference false, dimension-mismatch throw, post-constructionsetImageEmbedder. Both libstscexit 0; full suites green (prod-verif 29/29, canon 30/30); adversarial scan clean.
Phase 4 — yemaya AI-media factories (result-faking stubs → real engines)#
Problem (audit): yemaya's flagship "AI generation" paths fabricate success. Several P0 result-faking stubs on reachable paths.
4.1 Image generation — wire ComfyUI end to end (P0, [STUB]/[WIRE])#
- Wire the real ComfyUI transport into generation — DONE 2026-06-18. New
ComfyUIFactory.setHttpTransport()+generateRemote(request, opts): an async path that drives the realsubmitJob → dispatchJob(realPOST /prompt)→ refreshJob(realGET /history, polled to a terminal status) → downloads the real image bytes from each produced/view?…artifact URL (injectableimageFetcherseam, default globalfetch).buildGenerationOutputnow populatesdatawith the real bytes,hashwith the sha256 of those bytes (wassimpleHashover the URL/filename string),sizeByteswith the real length, andwidth/heightread from the PNG IHDR. Fails loud (ComfyUITransportNotConfiguredError) when no transport is wired — never falls back to empty bytes; an incomplete job returns an honestTimeout/Failedresult. The offlinegenerate()simulation was also de-fabricated: its images now carrydata: []andhash: ''(no fake metadata content-hash). Libtsc -p tsconfig.lib.jsonexit 0; full factory suite 336/336 green. (buildComfyUIAPIPromptnode-graph builder unchanged — see the SOTA-graph item below.) - SOTA graph support: SDXL/Flux checkpoints, ControlNet, IP-Adapter, LoRA,
img2img, inpaint — DONE 2026-06-19. Rewrote
buildComfyUIAPIPrompt(provider-manager.ts, now exported) from a fixed SD txt2img graph into a real architecture-aware node-graph builder — the exact JSON POSTed to a live ComfyUI/prompt(it is on the reachable remote path:submitJob → buildJobSubmitPayload → ProviderType.Local). It is pure deterministic construction (no model call). Branches: SD1.5/2/SDXL (CheckpointLoaderSimple+EmptyLatentImage), SD3/3.5 (checkpoint +EmptySD3LatentImage), Flux (UNETLoader+DualCLIPLoadertype=flux +VAELoader+FluxGuidance+ConditioningZeroOutnegative, KSampler cfg 1). LoRA stack chainsLoraLoaderthrough model+CLIP and rewires downstream; IP-Adapter (metadata.ipAdapters[]) →IPAdapterUnifiedLoader+IPAdapterAdvancedon the model; ControlNet (request.controlNets[]) →ControlNetLoader+LoadImage+ControlNetApplyAdvancedrewrites conditioning; img2img (LoadImage→VAEEncode, strength denoise); inpaint (LoadImage/LoadImageMask→VAEEncodeForInpaint). Fails loud (throws) on img2img/inpaint/IP-Adapter without an uploaded image name rather than silently emitting a txt2img graph. AddedisFluxArchitecture/isSD3Architecturepredicates.comfyui-graph-builder.test.ts(15, value-asserting: per-arch loader+latent class, LoRA/ControlNet/IP-Adapter chaining edges, img2img/inpaint latent + denoise, fail-loud paths, determinism). 15 new + 198 existing (provider-manager + remote-gen) green;tsc -p tsconfig.lib.jsonexit 0; stub scan clean. - Replace
asset-factory.ts:3170computeSimulatedQualityMetricsand:2844qualitySum = n*0.75with real metrics (Phase 0.2). TheIIsisGenerationClientinterface has zero implementations — implement one backed by the real ComfyUI/provider path, or fail loud. — PARTIAL 2026-06-19: the:2844 qualitySum = n*0.75fabrication is removed. That branch (hit bygenerateFromPlan/generateCourseAssets, which size by item count with no explicit per-type requests) used to claimtotalGenerated = Nassets at a flat 0.75 quality that bypassed the quality gate entirely — pure fabrication for assets never even simulated. It now routes each estimated asset through the documentedcomputeSimulatedQualityMetricsformula + the realapplyQualityGateattierForThreshold(batch.qualityGate)(the lowest tier whose simulated base meets the configured bar) — so pass/fail reflects the threshold honestly (a bar > 0.94 yieldstotalGenerated: 0, not a fabricated pass).asset-factory.test.ts+2 (no flat-0.75; honest gate-fail at threshold 0.99); 246/246 green, tsc clean. Still provider-gated (box stays unchecked):computeSimulatedQualityMetricsremains an honestly-labeled deterministic simulation fixture — the REAL metrics path is the existing fail-loudexecuteBatchAsync+IIsisGenerationClient; a concrete ComfyUI-backed client needs live diffusion creds. Re-read 2026-09-18: "needs a real generation backend" is no longer true. The Isis generation API serves rendered image workflows on RunPod and a hosted OpenRouter lane (ISIS_CHROMA_RUNPOD_MVP_TODOS_2026-09-11.md, C and H sections). ImplementIIsisGenerationClientagainst its job API (POST /jobs, poll, fetch the registered output), failing loud withnot_configuredwithout its URL and token; take quality metrics from a real measured pass or report them as unmeasured. One live still needs the usual spend ask. Verify: a client spec with the HTTP boundary doubled (submit, poll, failure, not configured) and one recorded live render. -
illustration-engine.ts:2530returns hardcoded quality + emptystoragePath— make it produce + persist a real image. — PARTIAL 2026-06-19: the hardcoded-quality fabrication is removed.generate()builds a skeleton asset (honeststatus: Generating, emptystoragePath) but used to stamp it with a fabricatedoverallScore: 0.75(+ 0.8/0.7/… per-metric) — quality for an image that was never rendered. The skeleton now reports unmeasured zeros with a comment that real scores come only after a generation backend renders the image and a Phase-0.2 metric pass measures it. 179 illustration-engine tests green; tsc clean. Still provider-gated (box stays unchecked): actually producing + persisting a real image needs a real generation backend (ComfyUI/diffusion creds). Re-read 2026-09-18: as the item above — produce and persist the image through theIIsisGenerationClientit introduces;storagePathis the registered output, and quality stays unmeasured until something measures it. - Tests — DONE 2026-06-18.
comfyui-remote-generation.test.ts(4, green) mocks the transport at the HTTP boundary (submit→ providerJobId;poll→ a real ComfyUI completed/historyJSON parsed by the realparseLocalComfyUIResponse) + a boundaryimageFetcherreturning a genuine 70-byte 1×1 PNG, and asserts:data.length===70equal to the real bytes,hashequals an independently-computedsha256of those bytes,width/height===1read from the PNG header (not the larger requested resolution — proving real parsing), the/view?filename=…URL was fetched, and the submit/poll path actually ran. Plus fail-loud (ComfyUITransportNotConfiguredErrorwith no transport), an honestTimeoutwhen the job never completes (no fabricated success), and the offlinegenerate()honesty (data:[],hash:'').
4.2 Video generation — real engine + encoder (P0, [STUB]/[GAP])#
- Replace the fabricated clip with a real provider path — DONE
2026-06-18. New
IVideoHttpTransport+createDefaultVideoHttpTransport(provider-router.ts: realfetchsubmit POST / poll GET / download-bytes, Bearer auth, job-id + completion detection) — the missing caller for the realbuildProviderPayload/parseProviderResponse(Hunyuan/Kling/Hailuo/fal).VideoFactory.setHttpTransport()+generateRemote(request, opts): route → build the provider payload →transport.submit→ poll to completion →parseProviderResponse(real provider clip URL) →transport.downloadthe real bytes; the result carries the sha256 of the downloaded bytes +downloadedBytes+contentVerified:true. Fails loud (VideoTransportNotConfiguredErrorno transport; throws on provider failure / poll timeout / 0-byte download) — never fabricates. The offlinegenerate()was de-fabricated:buildSimulatedOutputno longer emits ahttps://generated.video/...URL — its clip carries empty url/preview/keyframes (an honest routing estimate; the deadbuildKeyframeUrlswas removed). Full@yemaya/video-generationsuite 1105/1105 green;tsc -p tsconfig.lib.jsonexit 0. -
provider-router.tspollJobprogress — DONE 2026-06-18 (honest unknown). Removed the fabricated progress curve (elapsed/expectedDuration→Math.min(0.95, …)) and the invented pipeline-stage narrative ("Diffusion denoising in progress" / "Rendering frames" / "Encoding final video") plus the elapsed-based Queued→Generating transition. Without a live provider transport the router cannot observe real progress, so it now reports honest unknown (progress: 0,currentStep: 'Submitted; awaiting provider status (no live polling transport)') and only enforces the real local timeout (mirrors the ComfyUIpollJobrestraint). A wired transport advancestracked.progress/status/outputout of band;pollJobnever fabricates them. Added a test assertingprogress===0and no denoising/ rendering/encoding narrative; provider-router suite 168/168 green; tsc lib clean. (The remaining 4.2 item — wiring the real provider fetch submit/poll/download intovideo-factory.generate+ the encoder — is the larger async transport build, still open.) - Build a real video encoder (the whole repo has none): ffmpeg/libx264
(and WebM/VP9, GIF) muxing of frames → MP4. Wire into
export-pipeline.tsandrender-factory.ts(see 4.4). — DONE 2026-06-20 (render-factory wiring closed; encoder + both pipelines now real). Therender-factory.tscontent-source video adapter — the one piece the prior PARTIAL flagged as still open — now exists: newrender-factory/video-render-content-source.tscreateVideoRenderContentSource(frameProvider)adapts the same real@oshun/encodingencoders into the render-factory'sRenderFactoryConfig.contentSourceseam (reading the export target offRenderJobRequest.exportRequest, the shape that differs fromExportRequest), so a wiredRenderFactory.render(...)produces a real MP4/WebM/GIF, persists the real bytes via the storage backend, and stampssha256(bytes)— never a fabricated hash.video-render-content-source.test.ts(6, ffmpeg-gated, real ffprobe): the content source yields a real H.264 MP4 (codec/frames/duration), VP9 WebM, and GIF;RenderFactory.renderpersists the real bytes + checksums THEM (ffprobe-validated h264, 10 frames,sha256==sha256(persisted bytes)); fail-loud on a non-video format + an empty frame list. 6/6 green; full render-factory + export-pipeline suites 308/308;tscclean; stub scan clean. (The remainingvideo-factory.generatereal-provider download in the separate@yemaya/video-generationlib stays provider-gated — it is the already-real fail-loudgenerateRemoteseam, not part of the encoder/pipeline wiring this box covers.) — Prior PARTIAL (2026-06-18): the encoder itself is built — new@oshun/encodingencodeFramesToVideo(libs/shared/encoding/src/ video-encoder.ts) streams raw RGB/RGBA frames to a real ffmpeg over stdin and writes a real mp4 (libx264) / webm (vp9) / gif, then ffprobe-verifies codec/dims/frame-count/duration and returns the sha256 of the produced bytes (fail-loudFfmpegNotAvailableErrorwhen ffmpeg absent).video-encoder.spec.tsasserts real artifacts (h264 mp4 exact frame count + duration, vp9 webm, animated gif, sha256 over on-disk bytes) + validation/fail-loud — 8 tests green, ffprobe-backed. UPDATE 2026-06-19: theexport-pipeline.tswiring is now DONE (4.4build*Exportitem) —video-export-encoder.tsencode{MP4,WebM,GIF}Exportconsume thebuild*Exportoptions to produce real ffprobe-verified clips, andcreateVideoExportContentSourceplugs them into theExportContentSourceseam. Still open (why this box stays unchecked): therender-factory.tscontent-source video adapter (itsRenderJobRequestseam differs fromExportRequest) and wiring the real provider download bytes intovideo-factory.generate(a separate@yemaya/video-generationlib). - Local SOTA fallback option: AnimateDiff / Stable Video Diffusion via the
ComfyUI transport. — DONE 2026-06-21. Extended the real, deterministic
buildComfyUIAPIPromptgraph builder (the exact JSON POSTed to a live ComfyUI/prompton the reachableProviderType.Localpath) with two video generation types + dedicated builders. (1)GenerationType.AnimateDiff(text→video) →buildAnimateDiffGraph: the canonical ComfyUI-AnimateDiff-Evolved topology —CheckpointLoaderSimple→ (real LoRA stack chained on the model) →ADE_AnimateDiffLoaderGen1(injects the motion module, optionalADE_StandardUniformContextOptionsuniform-context window for long clips) → pos/negCLIPTextEncode→EmptyLatentImagewithbatch_size = frameCount(each batch item is a frame) →KSampler(reads the motion-injected model) →VAEDecode→VHS_VideoCombineframe muxer. (2)GenerationType.StableVideoDiffusion(image→video) →buildSvdGraph: the built-in SVD node topology —ImageOnlyCheckpointLoader+LoadImage(the required init frame) →SVD_img2vid_Conditioning(clip_vision + init_image + vae → positive/negative/latent, video_frames/motion_bucket/fps) →VideoLinearCFGGuidance→KSampler→VAEDecode→VHS_VideoCombine. Both terminate in a real frame muxer (neverSaveImage); frame count, motion module, motion bucket, fps, beta schedule etc. come frommetadata(the established escape hatch). Fail-loud: SVD withoutmetadata.sourceImageNamethrows (image→video needs an init frame). Added the exportedisVideoGenerationTypepredicate. Pure deterministic construction (no model call) — this rides the already-real, already-fail-loud ComfyUI transport (4.1).comfyui-video-graph-builder.test.ts(9, value-asserting: full AnimateDiff + SVD wiring incl. motion-injected model and SVD linear-CFG/latent edges, frameCount→batch / motionBucket / minCfg / betaSchedule overrides, LoRA-before-motion chaining, context window, SVD fail-loud, determinism). 9 new + 15 existing graph tests green; full@yemaya/comfyui-integrationsuite 1332/1332 (enum addition is additive —Record<string>maps +defaultswitches absorb it, no regression);tsc -p tsconfig.lib.jsonexit 0; adversarial stub scan clean. - Tests: assert a real MP4 is written (ffprobe: codec h264, frame count,
duration) — like the metis manim e2e
(
render-service-transport.e2e.spec.ts). — DONE 2026-06-20. The ffprobe-h264 assertion the prior PARTIAL flagged as remaining now exists in two places:export-pipeline/video-export-encoder.test.ts(4.4) and the newrender-factory/video-render-content-source.test.ts— both produce a real MP4 via the encoder path and ffprobe it for codec h264 + exact frame count + duration (≈frames/fps), plus aRenderFactory.renderend-to-end that ffprobes the persisted bytes (h264, 10 frames) and assertsresult.checksum == sha256(persisted bytes). ffmpeg-gated; the fail-loud paths always run. — Prior PARTIAL (2026-06-18):video-remote-generation.test.ts(5, green) asserts the real submit→poll→parse→download wiring with a boundary-mocked transport: the produced result carries the real provider clip URL (notgenerated.video), the real downloaded bytes' sha256 +downloadedBytes, both the synchronous and poll-until-complete paths, fail-loud (no transport / 0-byte download), and the offlinegenerate()honesty (empty url/preview/keyframes). Remaining: the ffprobe-h264 assertion needs a real encoded MP4 from the encoder path (the@oshun/encodingwiring above, gated) —generateRemotedownloads provider bytes rather than encoding locally.
4.3 Style transfer — real or fail loud (P0, [STUB])#
- transfer-engine + style-factory fabrication — DONE 2026-06-18
(fail-loud + injectable backend). Both fabrications removed:
TransferEngine.transferno longer synthesizes metrics from request params or returns/output/<id>_WxH.png(never written) withstatus:Completed; the factory'ssimulateTransferResult(fabricatedoutput/<src>_styled.png+ aMath.randomseed) is deleted. A new shared seam@yemaya/style-transferStyleTransferBackend(src/backend/ style-transfer-backend.ts) performs the real neural transfer (ComfyUI img2img + IP-Adapter/LoRA, or a provider) — writing real bytes and returning a realoutputPath+ measured metrics. Engine and factory take an injectable backend (constructor /setBackend); with none they throwStyleTransferBackendNotConfiguredError(fail loud) instead of fabricating. The engine keeps its real validation / job tracking / concurrency limits / stage events; the factory keeps its real planning / caching / quality gate. The deterministic FNVderiveSeedreplaces theMath.randomseed. - Tests — DONE 2026-06-18.
transfer-engine.test.ts(238 green) gains a backend-boundary block: fail-loud (StyleTransferBackendNotConfiguredError) and real bytes (the injected backend writes a styled file; the engine surfaces that path andexistsSyncconfirms it, with metrics/time passed through verbatim). Newstyle-factory.test.tsasserts the same end-to-end throughstyleCourse→executeBatch(fail-loud + every delivered result'soutputPathexists on disk under the backend dir). Full style-transfer suite 1051/1051 green;tsc -p tsconfig.lib.jsonclean; stub gate (all mode) exit 0. NOTE: the SSIM/CLIP-within-band assertion needs a real neural backend (creds/GPU) — the metrics here come through the injected boundary; the band check is deferred to that live backend.
4.4 Export / render job executors — real encode + honest checksums (P0, [STUB]/[BUG])#
- De-fabricate the export/render checksums + write real bytes — DONE
2026-06-18. Both
ExportPipeline._processJob(the audit'sexport-pipeline _executeJob) andRenderFactory._executeJobno longer compute achecksumover the path string with a fast FNV (calculateChecksum/_hashString, "sha256-like" but not sha256). Both now take injectable content + storage seams (contentSource,storageBackendon the config —ExportContentSource/RenderJobRequest→Uint8Array,ExportStorageBackend): when wired they produce real bytes, persist them via the backend, and setchecksum = sha256(bytes)(realnode:crypto),fileSizeBytes = bytes.length, and a newExportResult.contentVerified: true; with no backend wired the result is an honest estimate —checksum: '',contentVerified: false— never a fabricated hash. Zero-byte output fails loud. (Constructors now merge a partial config with the defaults so a seam-only config is valid.) The raster-kernel/PNG-codec/video-encoder are the concretecontentSources that plug into this seam (the real GPU/video work is items below). Full@yemaya/rendering-pipelinessuite 1604/1604 green; change is tsc-clean (one pre-existing, unrelatedmeasureTextre-export ambiguity remains insrc/index.ts, not introduced here). - Implement the real
buildMP4Export/buildWebMExport/buildGIFExport(export-pipeline.ts) — DONE 2026-06-19. The threebuild*Exportfunctions still build options objects (correct — they describe the encode), but now have a real consumer that performs the encode: newexport-pipeline/video-export-encoder.tsencodeMP4Export/encodeWebMExport/encodeGIFExporttake raw frame buffers + the options object and produce a real, ffprobe-verified video via@oshun/encoding(ffmpeg) — real H.264 MP4 / VP9 WebM / animated GIF with codec, dims, frame count, duration, byte length, and sha256 over the real on-disk bytes read back off the produced file (fail-loudFfmpegNotAvailableErrorwhen ffmpeg absent; refuses an empty frame list). Documented CRF/fps helpers (mp4BitrateToCrf,webmQualityToCrf,gifFpsFromFrameDelay) translate the advisory options into the encoder's real CRF mode.createVideoExportContentSource(frameProvider)adapts these into theExportContentSourceseam soExportPipeline.export()writes real video bytes with a real checksum.optimizeImage:1100is left as a documented size estimator — its doc no longer says "Simulates"; it now states it is a planning estimate only and that real bytes come from the encoder/ content-source seam. Added@oshun/encodingdep + an explicit vitestresolve.alias.video-export-encoder.test.ts(10): real ffprobe-verified mp4(h264)/webm(vp9)/gif, sha256 == sha256(on-disk bytes), content-source ftyp/EBML bytes, helper value assertions, fail-loud paths. 10 new + 215 existing export-pipeline tests green; only the pre-existingmeasureTextre-export ambiguity remains insrc/index.ts(not introduced here); stub scan clean. - Wire the orphaned Rust
wgpuGPU kernel (crates/yemaya-raster-gpu/src/gpu.rs:94) to TS via napi-rs/wasm so the real GPU compositor path is reachable (today CPU-only; GPU crate has no bridge). — DONE 2026-06-20 (built + run on-box, real GPU pixels). New napi-rs cratecrates/yemaya-raster-gpu-node(#[napi]bindings →cdylib, path-dep on the core crate built with--features gpu) exposesvalidateShaders/gpuAvailable/compositeOverGpu/gaussianBlurGputo TS; RGBA8 bytes ↔ the shader's little-endianr|g<<8|b<<16|a<<24u32 packing (= the CPURasterImage.datalayout) so GPU output is byte-comparable. Newraster-kernel/gpu-bridge.tsloads the.nodeaddon (envYEMAYA_GPU_KERNEL_NODEor the built crate path) and exposes typedgpuCompositeOver/gpuGaussianBlur/isGpuKernelAvailable, failing loud (GpuKernelUnavailableError) when the addon or a GPU adapter is absent — never a CPU-fabricated fallback. Verified on-box: wgpu gets a real adapter here (Mesa software-raster vulkan/gl), the addon built (cargo build --release), andgpu-bridge.test.ts(9, GPU-gated) asserts the real GPU output matches the CPUraster-kernel.tsreference — compositeOver Normal@1.0/Normal@0.5/Multiply within ±1/channel, gaussian blur σ=2 within ±2 (and that blur actually changed the image), σ≤0 no-op, plus fail-loud (dimension mismatch, unsupported mode). 9/9 green (skips cleanly when no GPU); raster-kernel suite 88/88; the core crate's 6 naga tests + the--features gpubuild are green;tscclean (only the pre-existingmeasureTextsrc/index.tsambiguity remains); stub scan clean. The 9 MB platform.node+target/are gitignored (built on demand); the crate source +Cargo.lockare committed. - Tests: real file on disk, checksum over bytes matches, ffprobe
validates video. — DONE 2026-06-19. real file on disk + checksum
over bytes were already done: export tests wire a real local-fs
storageBackend,export()the request, then read the bytes back off disk and assertsha256(on-disk bytes) === result.checksum; also honest estimate (checksum:'',contentVerified:false) with no backend, checksum changes when the bytes change (proving it hashes bytes not the path), and zero-byte fail-loud; render-factory has the mirror tests. The ffprobe video assertion is now DONE invideo-export-encoder.test.ts:encodeMP4Export/encodeWebMExport/encodeGIFExportproduce real files ffprobe confirms as codec h264 / vp9 / gif, with the frame count (8/6/5) and duration (≈frames/fps) read back off the produced container, andsha256matchingsha256(on-disk bytes). ffmpeg-gated; pure CRF/fps + fail-loud assertions always run.
4.5 Podcast / TTS — real speech (P0, [STUB])#
-
podcast-factory.ts:1317synthesizeBlockPcmemits formant tones, not words, never encodes a file. Replace with real neural TTS (ElevenLabs / the real wiredtts-factory.ts:1963provider path) and real encode to an audio file. — DONE 2026-06-22. The earlier "blocked on TTS creds + neural models + GPU" assessment was wrong for this box: re-verifying the environment, espeak-ng (apt, real articulatory phoneme synthesis of actual words, CPU, no creds) and Piper (real neural VITS TTS running on CPU via onnxruntime + a 63 MB voice model) both synthesize real spoken words here. New self-contained@yemaya/tts-integration/local-engine(synthesizeSpeechPcm/synthesizeSpeechWav/encodeWavToMp3, +resolveLocalTtsConfig/isLocalTtsEngineAvailable) drives either engine as a child process (execFileSync), decodes the engine's WAV → Float32 PCM, and resamples (real linear interp) / fits-to-slot; fail-loud (LocalTtsEngineNotAvailableError) when no engine is installed (never fabricated audio).podcast-factory.mixAudionow articulates each block's actual words via an injectableBlockSpeechSynthesizerseam (createRealBlockSpeechSynthesizer, default'auto'= real when installed; maps speaker F0→espeak pitch, gender→voice variant, emotion→rate) and degrades per-block to the honest formant model on any engine error. The mix encodes a real file viaencodeMixResultToWav(channels-aware RIFF) /encodeMixResultToMp3(libmp3lame). 16 new podcast tests (block synth real words, text-/speaker-dependence, WAV/MP3 bytes, injection seam); full podcast-factory suite 159/159. -
tts-factory.ts:1277renderLocalFormantPcmis an honest documented fallback — keep it as fallback, but ensure the real provider path is the default and that output is encoded to WAV/MP3. — DONE 2026-06-22. Container gap was already closed (encodeWavPcm16/synthesisResultToWav); now MP3 is real (synthesisResultToMp3→ libmp3lame via ffmpeg, fail-loudisMp3SynthesisAvailable), and the real engine is the default non-HTTP path:synthesizeAsync, when no HTTP provider is configured, now routes through the new_tryLocalEngineSynthesis(real espeak-ng/Piper words,preferLocalEnginedefaulttrue,localEngineConfigfor voice/model) and only falls back to the formantrenderLocalFormantPcmwhen no engine is installed — emittingprovider:'local-engine'provenance. The syncsynthesizestays the deterministic in-process formant model (what the suite pins). 3 new async tests (engine-preferred + provenance event, disabled→formant) + MP3 tests. - Tests — DONE 2026-06-22.
local-engine/local-tts-engine.test.ts(21 + 1 skip): pure WAV-parse/decode/resample/fit assertions (decodeWavexact int16→float, 2× upsample interpolation midpoints), config/availability/fail-loud, real espeak-ng synthesis (non-silent, 22050 Hz, deterministic-for-same-text + more-samples-for-longer-text, slot fit, WAV round-trip), real Piper neural synthesis (gated onOSHUN_PIPER_PYTHON/OSHUN_PIPER_MODEL), and real MP3 (ID3/frame-sync bytes).wav-codec.test.tsadds thesynthesisResultToMp3real-bytes test. Engine-dependent tests self-gate (like the ffmpeg/USD/on-box gated tests); pure tests always run. (STOI is not meaningfully applicable to raw TTS without a clean reference signal; the substantive bar — real bytes of real words + fail-loud — is met.) Full@yemaya/tts-integrationsuite 1052 pass / 3 skip.
4.6 euterpe magenta-rt — real on-device inference backend (P1, [GAP])#
-
euterpe/providers/magenta-rt/magenta-rt-provider.ts:137is an honest fail-loud preview seam (lifecycle:'preview', fail-closes withprovider_unavailableat:160) with the inference backend unshipped. Wire a real real-time music-generation backend (Magenta RealTime / MusicGen-streaming via napi/wasm or a service) so euterpe can synthesize audio, not only analyze/advise. Keep the fail-loud seam as the no-backend path. 2026-09-18, install first: attempt a CPU backend before calling this blocked — MusicGen-small or Magenta RealTime through ONNX or a small Python service on the Linux server (checkfree -mfirst; stream frames from a worker). Record the real-time factor measured. Only a named failed attempt with its numbers justifies parking this on a GPU. - Tests: real streamed audio frames produced; latency budget asserted; fail-loud when backend absent.
4.7 Transcoder + packaging correctness (P1, [BUG])#
-
transcoder.tsbuildFFmpegArgs— DONE 2026-06-18. Both bugs fixed: (1) video/audio filters are now collected into a single chain and emitted as one-vf/-afjoined by commas (ffmpeg honours only the last filter flag, so scale/deinterlace/normalization were silently dropped when a custom filter was also set); (2) encoder specs carrying trailing flags (prores_ks -profile:v 2,libfdk_aac -profile:a aac_he) are split on whitespace into separate argv tokens. Addedtranscoder-args.test.ts(6 tests, green): single combined-vf/-af, prores/aac_he token splitting, plain encoder unaffected.tsc -p tsconfig.jsonexit 0. -
packaging-service.tscreateBundle— DONE 2026-06-18. Now hashes the real file bytes, not metadata. NewcomputeFileChecksum(url, options)sources bytes from a caller-suppliedBundleOptions.readBytesreader (CDN/ storage), else a localfile://path viasha256File; a remote URL with no reader is reported as unverified (checksum: '',contentVerified: false, plus a warning) rather than fabricating a content hash.BundleFileEntry.contentVerifiedadded;verifyBundleis now async and re-hashes real bytes (expected= recorded vsactual= recomputed), reporting unverifiable files asskippedFiles(valid requires zero failures/missing). Addedpackaging-content-hash.test.ts(5 tests: real local-byte hash, hash-differs-on-byte-change, reader path, unverified remote, verify pass/tamper/skip).tscexit 0. -
dailies-review/ingestion.ts,conform.ts— DONE 2026-06-18. The content-checksum fallbacks now compute a real sha256 (sha256Hexvia nodecrypto, 64 hex chars) instead of the 32-char FNVstableHash, so the ASC MHLhashAlgorithm:'sha256'label and the OCFchecksumfield are truthful.stableHash(FNV-1a) is retained for ID generation only. Updated the staleconform.spec.tsassertion that pinned the 32-char buggy output to assert a real 64-char sha256 (exact value). All 21 dailies-review specs green;tsc -p tsconfig.lib.jsonexit 0. - Tests — DONE 2026-06-18.
transcoder-args.test.tsasserts representative encoder argv (prores/aac_he token splitting, single-vf/-af);packaging-content-hash.test.tsasserts the bundle checksum differs when bytes differ and matchessha256(bytes).
Phase 5 — bellona cross-engine pipeline completeness#
Problem (audit): real USDA/FBX/MaterialX/mocap, but GLB drops geometry, USD
authoring never serializes attribute values, USDC is faked in 3 spots, MetaHuman
analyzeFaceRig is hardcoded, maya/3dsmax are scaffolds, serializer tests are
near-absent.
5.1 glTF/GLB binary export — write the BIN chunk (P1, [BUG]) ✅ DONE 2026-06-18#
-
interchange/gltf/index.tsserializeGlbnow writes the BIN chunk (chunkType0x004E4942, 4-byte zero-padded) after the JSON chunk. NewassembleBinary(ctx)reconstructs the buffer payload by placing eachbufferData[i]at itsbufferViews[i].byteOffset(offsets include the inter-view 4-byte padding, so naive concatenation was wrong). The text.gltfpath now embeds the payload as adata:application/octet-stream;base64,…URI on buffer 0 viaencodeBase64, and the importer's newloadBufferUrisdecodes buffer data URIs intobinaryBuffers(mirrors the existing image-URI path) so text glTF round-trips too. - Tests:
src/gltf/gltf-roundtrip.test.ts(3 tests, green) — asserts a real BIN chunk exists with correct type/length, and that positions, normals, texcoords, and Uint16 indices survive a full export→import round-trip in both GLB and text glTF. tsc-p tsconfig.lib.jsonexit 0;phase-8-completion.test.tsstill green.
5.2 OpenUSD attribute serialization + real USDC (P0, [STUB]/[GAP])#
- USDA attribute serialization — DONE 2026-06-18. The authoring API
stored attribute values in a side map keyed by handle id with no link to
the prim, and
serializeLayerToUsdanever iterated attributes — so authored values were dropped on save. NowcreateAttributeregisters the attribute object in anattrByIdmap,setAttributeValuemirrors the value (and time samples) onto it, and the serializer emits each authored attribute. New exportedformatUsdaAttributeValue/serializeUsdaAttributehandle scalars, fixed-size tuples (point3f/color3f/…), arrays of scalars, and arrays of tuples (point3f[] points = [(…), (…)]). Addedusda-attribute-serialization.test.ts(9 tests incl. end-to-end createAttribute→setAttributeValue→saveStage writing real values).tscexit 0; the USDC fail-loud test still green. - Replace the result-faking USDC stubs — DONE 2026-06-18. All fake
Crate emitters now fail loud (a real Crate needs a USD/tinyusdz
binding, which isn't wired):
asset-io-manager.tsimportToUsd/exportFromUsdpushUSDC_ENCODER_UNAVAILABLEand write no file fortargetFormat:'usdc'(were: USDA bytes behind aPXR-USDCmagic,success:true);pipeline-tools.tsflattenAndPublishthrows forformat:'usdc'(was: truncated TOKENS section + USDA "as a comment"). The twoprimCount = fileBuffer.length/100fabrications now report0+USDC_DECODE_UNAVAILABLEwarning. Addedusdc-fail-loud.test.ts(6 tests: import/export/flatten fail-loud + no file written, usda regression, usdc-source prim-count 0).tsc -p tsconfig.lib.jsonexit 0. - Composition LIVRPS + variant serialization — DONE 2026-06-18.
LIVRPS:
InMemoryCompositionArcProvider.getResolvedAssetPathsno longer echoes a pre-registered map — it now resolves a prim's external asset paths from its composition arcs in real LIVRPS strength order (Local/subLayers > Inherits > Variants > References > Payloads > Specializes, ties broken byarcIndex). New exported pure helpersLIVRPS_ARC_STRENGTH,compareCompositionArcStrength,orderArcsByStrength,resolveAssetPathsFromArcs(only subLayer/reference/payload arcs introduce asset layers; inherits/variants/specializes compose prims in-stack). An explicitsetResolvedPathsregistration still overrides (authoritative Pcp resolution). VariantManager → serializer:serializeLayerToUsdapreviously emitted malformedvariantSets = "x"+ loose"v" {}blocks and dropped the selection; it now emits valid USDA — avariantSet "x" = { "a" {} "b" {} }definition block, avariantSets = [...]declaration, and the previously-dropped selection asvariants = { string x = "sel" }(read fromthis.variantSelections). Tests:composition-livrps.spec.ts(10 — strength order, arcIndex tiebreak, asset-arc filtering, dedup, provider resolution + override) andvariant-serialization.test.ts(3 — e2e author→save→read asserting the variant block + selection, multi-set list, no-selection omission). Full openusd suite 29/29 green;tsc -p tsconfig.lib.jsonclean; stub-scan clean. - USDC encoding in
interchange/usd/index.ts— DONE 2026-06-18 (fail-loud path).exportUsdcbuilds a real Crate hierarchy (TOKENS/STRINGS/FIELDS/ PATHS/SPECS/TOC) but encodes no geometry attributes, so.usdc/.usdzsilently dropped all mesh data. It now throws whenasset.meshesis non-empty (directing callers to USDA, which is geometry-complete), while still exporting geometry-free hierarchies as a real Crate. Addedusdc-geometry-fail-loud.test.ts(4 tests: usdc+usdz reject meshes, usda still works, hierarchy-only usdc emitsPXR-USDC).tscexit 0. (Full Crate geometry encoding needs a USD/tinyusdz binding + a runtime to validate against — deferred; the fix removes the silent data loss.) - Tests: USDA opens in a real USD runtime with correct points/faces;
USDC either validates in
usdcator the path fails loud. — DONE 2026-06-20. Newcore/usda-real-runtime.test.ts(2, gated): authors a realUsdGeomMesh(a unit quad; and a two-triangle variant) via the openusdFileSystemUsdSdkProvider(definePrim 'Mesh'+points/faceVertexCounts/faceVertexIndicesattributes),saveStages the.usda, then opens it in a real OpenUSD runtime (pxr, USD 0.26.5, installed on-box) via aUsdGeom.Meshprobe and asserts the runtime reads back the exact geometry —typeName=='Mesh', 4 points with their coordinates,faceVertexCounts==[4]/[3,3],faceVertexIndicesverbatim. This proves the serializer emits a file a real USD implementation accepts (not just text that looks right). The runtime is a gated dependency (resolvesOSHUN_USD_PYTHON, else anypython3that canimport pxr; skips cleanly when absent — verified both ways). The USDC half is already fail-loud (siblingusdc-geometry-fail-loud.test.ts/usdc-fail-loud.test.ts). openusd core suite 14/14 green (2 new, real-USD run); my test typechecks clean (the 11 libTS6305are pre-existing stale-distproject-reference artifacts).
5.3 MetaHuman analyzeFaceRig (P0, [STUB])#
-
metahuman/import/face-rig-mapper.tsanalyzeFaceRig— DONE 2026-06-18. No longer ignores the Blender result + returns hardcodedcontrolCount:0, controls:[]. New module-levelparseFaceRigOutputnormalizes theexecutePythonoutput (same pattern as mesh-importer), extracts the realFACE_RIG_INFO:<json>payload, and builds typedFaceControl[](name/category/side coerced; range[-1,1]/default0per MetaHuman face-board convention),categories, andcontrolCount. The Blender script was extended to detecthas_procedural_eyelids/has_procedural_pupilsfrom the actual rig bones (no longer hardcodedtrue). Fails loud when the marker/JSON is absent. Addedface-rig-mapper.test.ts(4 tests, green via--project bellona): full parse, derived categories/count, unknown-value coercion, fail-loud. No type errors in the file (the 20 lib-levelTS6059are pre-existing rootDir artifacts about the@bellona/blenderdep). - SOTA: integrate MetaHuman SDK / Mesh-to-MetaHuman for the import path so
generated/scanned heads become real MetaHuman assets feeding Phase 1.
(Net-new capability — needs the MetaHuman SDK; not a stub. Deferred.)
Board tag 2026-09-18: Mesh to MetaHuman runs in the Windows editor
against Epic's cloud service under the owner's Epic account; neither
machine is Windows. The Unreal side of the import path (consuming an
exported MetaHuman in UE 5.5 on the executing machine) can be built and
tested with a sample MetaHuman once the owner exports one.
blocked:external - Tests — DONE 2026-06-18.
face-rig-mapper.test.tsasserts parsed control count/names/categories/sides against fixture rig payloads.
5.4 maya / 3dsmax bridges — real or remove (P1, [STUB])#
- Implement real bridges — DONE 2026-06-18 (took the "real bridge"
option). New
maya/src/maya-bridge.tsMayaBridgeand3dsmax/src/max-bridge.tsMaxBridgeextend@bellona/adaptersBaseBridgeexactly like the working godot/houdini/davinci bridges: real WebSocket transport, real command dispatch over the genuineBaseBridgeJSON protocol (MessageEncoder/MessageDecoder, correlation-id round-trip), and real scene mutation methods grounded in each lib's action-schema domains — Maya:executePython/executeMel, dagcreateNode/rename/reparent/delete/freezeTransforms/setAttr/getAttr/listRelatives, meshtriangulate/assignMaterial, materialcreate, filenew/open/save/import/exportSelection/reference/ reload; 3dsMax:executeMaxScript/executePython, node CRUD +setNodeProperty/getNodeProperty,addModifier/collapseStack, material/layer create+assign, filenew/open/save/import/export/merge. Maya/Max are single-threaded, so dispatch is serialised via a promise-chain mutex that returns the realsendCommandresult (I dropped the godot-styleCommandQueuepoll-wrapper after finding it races and loses fast responses —getCommanddoesn't retain completed commands → "Command not found"; the mutex is correct + race-free). Added@bellona/adaptersdep to 3dsmax +pnpm install; exported both bridges from their index. The oldcreate*BridgeRuntimePlanis retained as the validated host/port config source feedingcreate{Maya,Max}Bridge— a real config builder, no longer a fake "bridge". Honest envelope: Maya and 3ds Max are proprietary and not installed on this box, so the live-DCC end-to-end can't run here; the boundary is a mocked in-Maya/Max command server (the allowed dependency double), and the DCC-absent/not-connected path is the genuine fail-loud (Not connected). - Tests — DONE 2026-06-18.
maya-bridge.test.ts(7) +max-bridge.test.ts(5): each connects over the real protocol and asserts real scene mutation against a boundary-mocked DCC server that runs an in-memory scene (createNode returns the server-disambiguated path/name; setAttr→getAttr round-trips a real value; executePython/ MaxScript report a result derived from the real request bytes; saveScene emits the server-resolved path; async DCC events route to listeners) — value-asserting, not shape. Fail-loud asserted: issuing a command before connect rejectsNot connected. Both lib suites green (maya 17/17, max 15/15),tsc -p tsconfig.jsonexit 0 both libs, adversarial scan clean.
5.5 Serializer test coverage (P1, [GAP])#
- Interchange serializer value-asserting tests — DONE 2026-06-18. Added
real byte/geometry round-trip tests across the serializers (replacing the
shape-only
phase-8-completion.test.tscoverage gap that let 5.1/5.2 slip):gltf/gltf-roundtrip.test.ts(GLB BIN chunk + text data-URI, geometry survives),usd/usdc-geometry-fail-loud.test.ts+core/usda-attribute- serialization.test.ts(USDA values; USDC/USDZ fail-loud on geometry),io/usdc-fail-loud.test.ts(openusd), and nowfbx/fbx-roundtrip.test.ts(Kaydara magic + positions + indices round-trip). All green;tscexit 0.
Phase 6 — DCC agent autonomy + live transports#
Problem (audit): the bellona "agents" (blender-agent, unity-agent) are
deterministic intent parsers, not autonomous — no LLM loop anywhere
(whole-tree grep: 0 LLM clients). mcp-gateway's only shipped backend is an
in-memory simulation.
6.1 Real LLM planner for DCC agents (P1, [GAP]/[NAMING])#
-
blender-agent/natural-language-action-planner.ts:81(regex intent parser, self-labeleddeterministic-intent-parser) — add a real LLM planning path on Phase 0.1: NL brief → tool-call plan over the real bpy macro library (physics-setup-macros.ts:352,handlers.py:1538), with scene-grounding context and a closed plan→execute→observe→replan loop (todaytransaction-layer.ts:287executes a fixed list, no re-plan). — DONE 2026-06-21. Newblender-agent/src/llm-action-planner.ts:runBlenderLlmAgentLoopis the real NL→plan→execute→observe→replan loop. Each turn the model emits steps{domain, operation, payload}over the real op catalog (listBellonaBlenderActionOperationDescriptors(), put in the system prompt); each step becomes a realBlenderAgentActionviacreateBellonaBlenderAction(validates against the catalog, fail-loud on an unknown op/payload — proven by a test); the loop executes them through the realexecuteBellonaBlenderTransaction(destructive previews approved so it mutates the DCC), observes the executor's post-flight scene capture, summarizes it, and re-prompts until the model reportsdoneor the budget. The model call is the injectedStructuredPlannerseam — the structural mirror of@oshun/ai/agent-loop'srunStructuredOutput; the buildable lib'srootDirforbids an@oshun/aisource import, so the seam keeps it decoupled and the test wires the real Phase-0.1runStructuredOutput(intsconfig.spec.json, no rootDir). Fail-loud: no planner ⇒LlmPlannerNotConfiguredError. - Same for
unity-agent(deterministic C# codegenmonobehaviour-code-generation.ts:228) — LLM-driven component/scene synthesis with the real headless executor (headless-batch-execution.ts:405) in the loop. — DONE 2026-06-21. Newunity-agent/src/llm-component-synthesizer.ts:runUnityLlmAgentLoopis the NL→synthesize→build→observe→revise loop. The model emits aUnityComponentPlan; each component is mapped to a realBellonaUnityMonoBehaviourCodeGenerationIntentand run through the realgenerateBellonaUnityMonoBehaviourCode(produces real C# — a test asserts the generatedclass PlayerController : MonoBehaviour, the serialized field, and the model-requestedUpdate()body).buildUnityBuildObservationmaps a realexecuteBellonaUnityAgentHeadlessBatchresult into the loop's observation (proven over a real dry-run batch), putting the real headless executor in the loop; the observed build (incl. errors) is fed back into the next revision turn. Same injectedStructuredPlannerseam + real Phase-0.1 wiring in the test. Fail-loud:LlmComponentSynthesizerNotConfiguredError. - Keep the deterministic parser as a fast-path/fallback (it is honest, not a
stub) — SOTA is hybrid. — DONE 2026-06-21. The deterministic paths are
untouched and remain the fallback: blender's hybrid
planBlenderActions(planner, request)uses the LLM when a planner is wired and falls back to the deterministicplanBellonaBlenderActionsFromPromptotherwise (returns the realBlenderAgentAction[]+ asource: 'llm'|'deterministic'tag — both asserted); unity keeps the explicit-intentgenerateBellonaUnityMonoBehaviourCodeas the non-LLM path (the LLM path adds NL→intent synthesis on top of it). Neither deterministic path was removed. - Tests: LLM plan → real DCC mutation; deterministic fallback still
works; fail-loud when no provider. — DONE 2026-06-21.
blender-agent/src/llm-action-planner.test.ts(8): the realrunStructuredOutput(over a boundary-mocked provider) → real validated action → realexecuteBellonaBlenderTransaction→ the model's chosenobject.create_primitive+ payload reaches the boundary-mocked bridge (bridge.requestCalls); the closed loop replans grounded in the observed post-flight scene ('Scene state: 1 object(s)','Cube[MESH]'in the turn-2 prompt); fail-loud (no planner; unknown op throws); hybrid deterministic fallback (source:'deterministic'with real actions) vs LLM (source:'llm').unity-agent/src/llm-component-synthesizer.test.ts(5): real LLM → real C# generation, build-error fed into the revision turn, real-executor dry-run observation, fail-loud. Full blender-agent 195/195 + unity-agent 1066/1066 green (no regression); bothtsc --noEmitexit 0; adversarial stub scan clean. (The@oshun/aisource is kept out of both buildable libs via the seam; it's a test-onlydevDependency. Driving a live Blender/Unity needs the real DCC + LLM creds — the loop, real action mapping, real executor wiring, and real Phase-0.1 integration are proven via boundary mocks, exactly as the project's other LLM-seam closures are.)
6.2 mcp-gateway → live DCC (P1, [WIRE]/[STUB])#
- Replace
mcp-gateway/cloud-agent-mvp-smoke.ts:112CloudAgentMvpSmokeGateway(in-memory simulation, fabricatesobjects:['Camera','Cube','Key Light'],liveDeploymentExercised:false) with wiring to the real DCC bridges (blender/godot/houdini/davinci) and the Phase 1 UE server. Keep the smoke gateway as a clearly-labeled test double only. — DONE 2026-06-20. Newdcc-bridge-gateway.tsDccBridgeGatewayis the realBellonaMcpRemoteControlGateway: it routes each MCPRemoteCommandEnvelopeto a registered, connected@bellona/adaptersBaseBridge over the genuine WebSocket command protocol and returns the bridge's real response as command output — never the fabricated['Camera','Cube','Key Light']. The blender / godot / houdini / davinci / maya / 3dsmax bridges all extendBaseBridge, so they satisfy the gateway's structuralDccCommandTransportseam (no compile dep on any specific bridge); the Phase-1 UE server plugs into the same seam viaUeHttpCommandTransport(the real HTTP client for the C++ command route — see Phase 1.1 box above). No binding/transport for a command ⇒ fail-loudDccBridgeNotConfiguredError; a disconnected transport ⇒ an honeststatus:'failed'adapter.offlineresult (never a fabricated success). TheCloudAgentMvpSmokeGatewayis unchanged — it remains the clearly-labelled in-memory test double (transport:'mcp-in-memory-cloud-agent-simulation'). - Tests: MCP tool call reaches a live (or boundary-mocked) DCC and
returns real results. — DONE 2026-06-20.
dcc-bridge-gateway.test.ts(8) drives the gateway against a realBaseBridgeover a boundary-mocked WebSocket DCC (an in-memory blender scene):dispatchCommandround-tripsscene.infoand asserts the output is the DCC's real scene (['SunLight','GroundPlane','HeroCharacter'], NOT the fabricated triple), acreate_primitivemutation surfaces the DCC-assigned object name + grown scene count, the exact bridge command crossed the wire, plus the fail-loud paths (no binding / missing transport / not-connected / DCC-reported error) and device+session tracking — and a literal end-to-end: ablender.scene.infoMCP tool call through the realcreateBellonaMcpGatewayServer+ MCP client returns the boundary-mocked DCC's real scene.ue-http-command-transport.test.ts(7) does the same for the UE HTTP route (boundary-mockedfetch). 15 new + full mcp-gateway suite 129/129 green;tsc -p tsconfig.lib.jsonexit 0; stub scan clean.
Phase 7 — Retrieval, embeddings, grounding (RAG)#
Problem (audit): iris agentic-rag is honest lexical IR (BM25/graph BFS)
mislabeled as embeddings/semantic, with no generation ("G" missing).
7.1 Real dense retrieval (P1, [NAMING]/[GAP])#
-
iris/knowledge/agentic-ragdense-retriever.ts— real embeddings GAP. — DONE 2026-06-21. Two parts: (1) the mislabel was already fixed (2026-06-18) — the lexicalDenseRetrieverdoc honestly states it scores by Jaccard token overlap, not embedding cosine, andmethod:'dense'names the fusion channel. (2) NEW: the real semantic path now exists —embedding-dense-retriever.tsEmbeddingDenseRetrieverranks documents by real cosine similarity over real embedding vectors (cosineSimilarity=(a·b)/(‖a‖‖b‖), dimension-checked, 0 for a zero vector). The embedding model is the dependency boundary — an injectedEmbeddingBackend(embed(texts) → number[][]; voyage/openai/cohere/local) — so the cosine algorithm is real here and the provider plugs in; it embeds the corpus once (cached, invalidated onsetBackend) + the query per call, and fails loud (EmbeddingBackendNotConfiguredError) with no backend — never a fabricated similarity.embedding-dense-retriever.test.ts(8, value-asserting against a deterministic bag-of-words boundary embedder): exact cosine values (1,0,1/√2, colinear, zero-vector, dimension mismatch throws); ranking (alpha>alpha beta, zero-score docs filtered); corpus-embedded-once caching; topK/minScore; fail-loud; re-index on backend change. Full agentic-rag suite 32/32 (was 24);tsc --noEmitexit 0; stub scan clean. (Wiring it into the live RRF fusion needs the async path + a real embedder + pgvector/Qdrant for scale — provider/infra-gated; the real cosine retriever + fail-loud seam is the closure, mirroring the project's other LLM/embedding-seam closures.) - Reciprocal Rank Fusion — DONE 2026-06-18. Replaced the weighted-sum of
max-normalized scores with weighted RRF: each hit's fused score is
Σ_method weight/(k + rank)(rank-based → scale-invariant across retrievers). AddedrrfKconfig (canonical default 60), removed the now mootnormalizeScores. Addedretrieval-fusion-rrf.test.ts(scale invariance,weight/(k+rank)formula, multi-method reward); existing fusion spec still green;tscexit 0. (Cross-encoder reranker is additive + model-gated — deferred.) - Tests — DONE 2026-06-18.
retrieval-quality.spec.ts(4, green) runs the real retrievers (dense Jaccard / sparse BM25 / graph BFS + RRF fusion viacreateRetrievalFusion().fuse) over a labeled 8-doc / 3-topic corpus with known-relevant docs per query, scored by the realRetrievalEvaluator(computeNDCG). Asserts nDCG vs hand-computed values: perfectnDCG@5 == 1.0+recall == 1.0+mrr == 1when the relevant docs fill the top ranks; mean nDCG ≥ 0.9 over the labeled set; a degraded ranking's exactnDCG == 1/log2(3)(relevant doc at rank 2) +precision@5 == 1/5; and RRF fusion ≥ best single retriever (mean nDCG). Full agentic-rag suite 24/24 green;tscexit 0; stub gate (all) exit 0. (No embedding dimensionality to assert — the retrievers are lexical IR; real embeddings remain the provider-gated GAP in thedense-retrieveritem above.)
7.2 Grounded generation — add the "G" (P1, [GAP])#
- Add grounded answer synthesis (the "G") — DONE 2026-06-18. New
grounded-answer.tssynthesizeGroundedAnswer(query, candidates, deps)+GroundedAnswerSynthesizer: generate an answer that cites passages inline by[id](provider-agnosticAnswerGeneratorseam — wire a real LLM via@oshun/ai/agent-loop; fail-loudAnswerGeneratorNotConfiguredErrorwhen candidates exist but none is wired) → validate citations (drop hallucinated ids) → faithfulness check per claim (injectableFaithfulnessCheckerNLI seam — defaults to a real deterministiclexicalFaithfulnesscontent-token-entailment scorer, the documented approximation of the veritas NLI / Phase-0.2 faithfulness metric) → attribute each grounded claim to its best-supporting passage. Returns aGroundedAnswerthat is either grounded+cited or an honest refusal (grounded:false+ reason): no passages ⇒ refuse, only hallucinated citations ⇒ refuse, below the faithfulness bar ⇒ refuse — never a fabricated confident answer. Added theCitation/GroundedAnswertypes + an optionalanswerfield onAgenticRetrievalResult, and wired the synthesizer as an optional agent dep (execute()attaches the grounded answer when one is injected; unchanged otherwise). - Tests — DONE 2026-06-18.
grounded-answer.spec.ts(10, green): a grounded answer whose every citation points at a real candidate id + source;lexicalFaithfulnessscores a supported claim >0.7 and an unsupported one <0.3; citation-id extraction/claim-splitting; and the four honest-refusal/fail-loud paths — no passages ⇒ refuse (empty text), no generator ⇒AnswerGeneratorNotConfiguredError, only hallucinated ids ⇒ refuse (zero citations), claims unfaithful to cited passages ⇒ refuse (faithfulness < 0.6, "below bar"). Plus the agent integration (answer attached when a synthesizer is wired; omitted otherwise). Full agentic-rag suite 20/20 green;tsc -p tsconfig.lib.jsonexit 0; stub gate (all) exit 0.
7.3 Research-report agents — keep real, add LLM synthesis option (P2, [GAP])#
-
sophia/agentsoptional LLM synthesis stage — DONE 2026-06-18. Added aReportSynthesizerseam (types.ts:ReportSynthesizer+ReportSynthesisInput) and an optionalreportSynthesizerfield onResearchPipelineConfig. The orchestrator's newbuildSummaryalways computes the deterministic template summary first (real, honest), then — when a synthesizer is wired — narrates the real pipeline findings through it (passing query/findings/sources/confidence + the deterministic summary as grounding so it cannot invent findings). Falls back to the deterministic summary when no synthesizer is wired, it throws, or it returns empty — the completed pipeline is never failed and no summary is fabricated (mirrors the kalikaresearch-agentsinjectable-stage pattern; the LLM is the dependency boundary, wired by the caller, e.g. via@oshun/ai/agent-loop).report-synthesizer.test.ts(4): deterministic default, narrates-real-findings (asserts the synthesizer received the real sources + grounding), throw→fallback (pipeline still all-completed), empty→fallback. 4 green + existing orchestrator suite 16/16 unaffected.
7.4 veritas long-form prose generation (P1, [GAP])#
- Add a real model-backed drafting agent — DONE 2026-06-18. New
article-drafting-agent.tsdraftGroundedArticle(brief, sources, deps, config)(exported from@veritas/agents-editorial): draft the article from the sources via an injectedArticleLLM(the@veritas/agents-coreLLMClientcompletesurface), constrained to cite every factual claim inline by[sourceId]and introduce no fact outside the sources → editorial critique→revise loop (Phase-3.3 shape: bounded generate→critic→revise on an injectedEditorialCritic) → fact-check each cited claim against its sources via an injectedArticleFactCheckerseam (the realagents-fact-checkingverdict pipeline plugs in here) → return a grounded, cited, fact-checkedDraftedArticleOR an honest refusal (published:false+ reason). Refuses on: no sources, a draft that cites no real source, fact-check support below the bar, or an editorial score below the bar. Fails loud (ArticleLLMNotConfiguredError) with no LLM. Decoupled (generic seams, no veritas-core compile dep). - Wire journalism correspondents to produce real reportage — DONE
2026-06-18. New decoupled
parliament-reportage.tscomposeParliamentReportage({bills, statements}, {llm}, options):buildParliamentFactsextracts real bill/statement facts from the tracked data (bill number + title + sponsor + party + stage; top speakers by count) — not the${count} bill(s)template — and an injectedReportageLLM(the@veritas/agents-coreLLMClientcompletesurface) writes prose grounded only in those facts. The deterministictemplateReportageis an honest fallback with truthful provenance (source: 'llm' | 'template') when no LLM is wired / it errors / returns empty — never fabricated.PoliticalCorrespondentAgent.generateParliamentReportagedelegates to it withthis.llm. Tests:parliament-reportage.spec.ts(5, green) assert the model received the real bill number + speaker facts and the three honest-fallback paths;buildParliamentFactsextracts the real details. Full@veritas/agents-journalismsuite 24/24 green;tscexit 0; stub gate (all) exit 0. - Tests — DONE 2026-06-18.
article-drafting-agent.test.ts(8, green, boundary-mocked LLM + fact-checker): a published article whose every citation points at a real source id + title and all claims are fact-supported (factCheckScore===1); gated by the fact-checker — an unsupporting checker dropspublished:falsewith the unsupported claims + a "fact-check support" refusal; the critique→revise loop improves a weak first draft to publish (revisions===2,editorialScoreasserted); refusal when the draft cites no real source / there are no sources; and fail-loud (ArticleLLMNotConfiguredError) with no LLM. Full@veritas/agents-editorialsuite 31/31 green;tscexit 0; stub gate (all) exit 0. (The Phase-0.2 faithfulness metric specifically is oneArticleFactCheckerimplementation that can plug into the same seam; the gating is asserted via the injected checker.)
Phase 8 — Remaining domain result-faking stubs#
8.1 galatea VLA + LBM (P0, [STUB])#
-
large-behavior-model.tsrunTraining— DONE 2026-06-18 (fail-loud + injectable real runtime). Removed the closed-form fabrication (loss curve,flowMatchingConsistency/multimodalAlignment/etc. computed from plan params,.ptcheckpoint paths never written, and asha256that hashed request metadata not model bytes).runTrainingis now async, does the real deterministic planning (dataset selection, distributed strategy, batch sizing, throughput) and delegates execution to an injectedLbmTrainingRuntime(real distributed PyTorch FSDP/DeepSpeed); with no runtime it throwsLbmTrainingRuntimeNotConfiguredError. The pipeline passes the runtime's measured checkpoints/metrics/artifact through verbatim (and rejects a runtime returning zero checkpoints). No GPU on this box ⇒ fail-loud is the honest acceptance path; the seam keeps the door open for a real cluster. -
vla-runtime.tsinfer()— DONE 2026-06-18 (fail-loud + injectable real backend). Deleted the fabrication helpers (jointIntentBias,createJointTargets,inferBaseCommand,estimateLatencyMs,defaultJointNameForIndex,averagePixelValue,firstHumanTarget): joint commands were hardcoded intent/phase biases andinferenceLatencyMswas a backend/pixel formula — no model ever ran. The real System-2 reasoning (intent, task plan, scene summary, token budget, safety, hot-swap, cadence) is kept. The whole-body action now comes from an injectedVlaPolicyBackend(real ONNX Runtime / TensorRT forward pass) reporting its own joint targets, base command, confidence, and measured latency; the engine surfaces these verbatim, validates the action dimension and a positive latency, and fails loud withVlaPolicyBackendNotConfiguredErrorwhen no backend is wired. behavioral-engine (the one production caller) stays synchronous; its injected runtime must now carry a backend. - Tests — DONE 2026-06-18. LBM: fail-loud when unconfigured, a
boundary backend that writes a real checkpoint file and the test
re-reads it and asserts
sha256(on-disk bytes)matches (real bytes, not metadata), zero-checkpoint rejection, and digest determinism. VLA: fail-loud (VlaPolicyBackendNotConfiguredError), latency pass-through (engine surfaces the backend's measured 9.4 ms verbatim, proving no formula), backend received the real model+observation, wrong-dimension rejection, non-positive-latency rejection. Full@galatea/aisuite 114/114 green;tsc -p tsconfig.lib.jsonexit 0; edited specs typecheck clean; stub-indicator gate (all mode) exit 0.
8.2 Misc result-faking stubs (P0/P1, [STUB])#
-
aje/agents/frameworks.tsexecuteWeb3Chain— DONE 2026-06-18. No longer fabricates the chain (simpleHash%3tool-selection, cannedtoolOutput/reasoning, faketxHash, hash-derivedllmTokensUsed,status:'completed'). Added an injectableWeb3ChainExecutorseam (Web3ChainExecutionresult type, both intypes.ts) that performs real LLM tool-selection + on-chain execution;executeWeb3Chainis now async, stamps only IDs/real timestamps around the executor's real output, and fails loud withWeb3ChainExecutorNotConfiguredErrorwhen no executor is wired. Rewrote the 4 stub-asserting tests (now inject a real executor / assert fail-loud) and added a no-executor test. Libtscexit 0; 152 frameworks specs green. (Spec-configTS6196unused-import errors are pre-existing across untouched spec files.) -
saraswati/drones/delivery.tsrunInference— DONE 2026-06-18. No longer fabricates defect findings from an FNV hash of the image URL. Added an injectableDefectDetectorseam (setDefectDetector) that calls a real CV model endpoint;runInferenceis now async, returns the detector's real findings, and fails loud withDefectInferenceNotConfiguredErrorwhen no detector is wired. Addeddefect-inference.spec.ts(4 tests: fail-loud unconfigured, unknown-model throw, real-detector passthrough, fail-loud after clear).tscexit 0. -
seshat/harmony/feng-shui.ts— DONE 2026-06-18. Replacedeffectiveness: 60 + Math.random()*20with exported deterministicpoisonArrowRemedyEffectiveness(remedyType, severity, distance): remedy class sets the ceiling (convex Bagua mirror 0.90 deflect > wind_chime 0.84 lift > plant 0.80 disperse > crystal 0.78 redirect), severity reduces it linearly (−0.35·sev), distance adds a bounded saturating bonus (8·d/(d+4)), clamped to a realistic 40–95% band; monotonic. Wired intodetectPoisonArrowsusingarrow.severityandcalculateDistance2D(arrow.source, arrow.target). Verified via tsx exercising the real exports (12/12 checks incl. known value mirror/40/4→80, ranking, monotonicity, clamp, integration).tsc -p tsconfig.lib.jsonexit 0. Addedfeng-shui-remedy-effectiveness.test.ts(real value assertions). NOTE: localvitestOOMs in this lib due to thevite-tsconfig-pathsplugin scanning the monorepo tsconfig graph (environmental, not the code) — hence tsx verification. -
metis/multimedia/video/gpu-worker-pool.ts— DONE 2026-06-18. ReplacedidleWorker.memoryUsageMB = 200 + Math.random()*300withresolveJobMemoryMB(job): uses a real caller-declared per-job estimate (submitJob(compositionId, priority, estimatedMemoryMB?), new optionalRenderJob.estimatedMemoryMB) when present, else a real liveprocess.memoryUsage().rssmeasurement, clamped tomemoryLimitMB. Never fabricated; doc notes precise VRAM accounting needs a caller estimate or a real GPU backend. Added 4 spec tests (exact estimate 512, clamp to 256, release-to-0 on completion, real-RSS fallback matching a concurrent reading):gpu-worker-pool.spec.ts35/35 green;tsc -p tsconfig.lib.jsonexit 0.
8.3 Naming/label fixes (P2, [NAMING])#
-
nisaba/paleography/script-classifier.ts— DONE 2026-06-18. Removed the branch that setclassifierType:'ensemble'while running pure heuristic (predict()is async and is never called; the sync classifier can't run it).classifyWithPreprocessingnow always reports the'heuristic'result it actually computes. Added an honestisModelLoaded()accessor so the adapter seam isn't dead, documenting that a real ensemble belongs on a future async path. Added a test that loads a mock adapter and assertsclassifierTypestays'heuristic'. 336/336 paleography specs green;tsc -p tsconfig.lib.jsonexit 0. -
aglaea/agentic-shopping— DONE 2026-06-18.SpecialistModel.modelIdwas never read (only set on 8 specialists) and falsely implied an invoked ML model — routing is purely lexical (triggerPatterns/scoreSpecialistMatch). Renamed the field tospecialistIdwith a doc comment stating it is a keyword-specialist identifier, not a model call. 34 agentic-shopping specs green;tsc -p tsconfig.lib.jsonexit 0.
8.4 lakshmi ai-agents — implement or remove (P1, [GAP])#
-
apps/lakshmi/ai-agents/src/index.ts— DONE 2026-06-18 (honesty path). Took the "remove misleading framing" option: the doc comment now states the financial agents (scenario modeling, bill negotiation, tax advisory) are NOT yet implemented and the process serves health checks only. The health payload'smodelProvidersno longer hard-claims['openai','anthropic','google']— newresolveConfiguredModelProviders()reports only providers whose credentials are actually present in the env (possibly empty). Updated the test that pinned the fabricated list + added a resolver test. 4 tests green;tscexit 0. (Implementing the real agents remains a Phase 0.1 + provider-credential build — deferred.)
8.5 calliope backstory seam (P2, [WIRE])#
-
calliope/genesis/backstory-generator.ts:190has a real LLM seam that is default-disabled (FALLBACK_LLM_PROVIDERthrows →artist-genesis-agent.ts:56deterministic fallback). Wire a real provider by default so narrative/persona generation is model-backed; keep the deterministic path as fallback. - NOTE (assessed 2026-06-18): the code is already an honest injectable seam —BackstoryGeneratortakesconfig.llmProvider, callscreateChatCompletion, and falls back to a deterministic biography on error with no fabrication. This is NOT a stub. "Wire a real provider by default" is a DI/credentials/deployment concern that depends on the Phase 0.1 shared provider plane being built and on live model creds — not a locally-verifiable code change. Left unchecked (blocked on Phase 0.1 + creds), not because the seam is wrong. Re-read 2026-09-18: bind the seam by configuration to the shared OpenRouter route with the repository's cheap model for development and tests, keep the deterministic biography as the fallback, and fail loud in production when no provider is configured. Verify: a spec with the provider doubled (model path, error → fallback, unconfigured → refusal) and one live call recorded with its cost.
Phase 9 — metis agentic-media: wire the unwired SOTA seams#
Problem (audit): the metis media loop is real and rendering is verified
(real MP4 produced on-box), but the planner/coder/critic are deterministic by
default and the LLM/VLM seams are never wired by a production caller;
runAgenticMediaLoop has no production caller.
9.1 Wire LLM/VLM into the media loop (P1, [WIRE])#
- Wire a real
NarrationWriterLLM (media-planner-agent.ts:21) so scene narration/planning is model-driven, not just rule-based segmentation. — DONE 2026-06-21. The production caller already drives model narration (runProductionMediaLooptakes anAsyncNarrationWriterseam and, in an async pre-pass, re-scripts every scene's narration through it, grounded in the same source text the planner used). The remaining piece — the concrete@oshun/ai-backed narrator helper — is now shipped:@oshun/creative-orchestratorcreateMetisNarrator(provider, options)(src/adapters/metis.ts) returns a realAsyncNarrationWriter(concept, sourceText) => Promise<string>that calls the sharedLLMProviderInterface.createChatCompletionwith a grounding system prompt ("ground every statement in the SOURCE MATERIAL; introduce no facts not present; output ONLY spoken words") + the real scene concept/source, and returns the model's narration. It lives in the Phase-3 orchestrator layer (which already deps@oshun/ai) and re-declaresAsyncNarrationWriterstructurally so the orchestrator takes no compile dep on@metis/multimedia(same decoupling as the yemaya adapter). Fail-loud: no provider ⇒ProviderNotConfiguredError; an empty model response ⇒ throw (a genuine model failure surfaced, never blank/fabricated narration).adapters/metis.test.ts(6, value-asserting against a boundaryScriptedProvider): the model received the real concept + source + grounding system prompt + word-count guidance and its narration is returned verbatim; default 60-word target; whitespace trim;ProviderNotConfiguredErrorwith no provider; empty-output fail-loud; structuralAsyncNarrationWritercompatibility. 6 new + full@oshun/creative-orchestratorsuite 45/45;tsc --noEmitexit 0; stub scan clean. (The model CALL is provider-gated like 3.1's planner — the wiring + fail-loud + boundary test is the closure. The sibling VLM-critic item stays[ ]: it genuinely needs a vision model.) - Wire a real VLM critic (
media-critic-agent.ts:56VlmNotConfiguredError) so visual-clarity critique inspects real extracted frames (the frame extraction via ffmpeg is already real,manim_render_service.py:399). — Not done: theVlmCriticseam + fail-loudVlmNotConfiguredErroralready exist; wiring a concrete VLM needs a vision model (provider-gated). Left unchecked. Re-read 2026-09-18: "provider-gated" is not a blocker — the OpenRouter key exists and its catalogue lists inexpensive vision models. BindVlmCriticto the cheapest one that reports image input, send the frames ffmpeg already extracts, and keepVlmNotConfiguredErrorfor the unconfigured case. Verify: a spec with the model doubled and one live critique of a real rendered frame, with its cost. - Wire the equation-correctness check seam — DONE 2026-06-18. New
arithmetic-equation-checker.tscheckArithmeticEquation(expr)+createArithmeticEquationChecker()(yields the media-criticEquationCorrectnessFn): a real recursive-descent arithmetic evaluator (+ - * / ^, unary minus, parentheses,× ÷, scientific notation, π/pi/e) parsesLHS = RHS(or==) and reports numeric (in)equality within a relative tolerance — catching genuine on-screen math errors (2+2=5→ false4 != 5;2^10=1000→ false; precedence7-2*3=1and right-assoc2^3^2=512correct;π·2²=4πcorrect). Honest envelope: symbolic equations with free variables (E = m·c^2) returncorrect:true+ a "not numerically checkable (free symbol)" detail — a real verdict it can't compute is never fabricated; a malformed expression iscorrect:falsewith the parse error. (A full symbolic check still needs a CAS — that heavier path is deferred; the seam now has a real numeric/arithmetic checker.)arithmetic-equation-checker.spec.ts(7, green); full@metis/agentssuite 894/894 green;tscexit 0; stub gate (all) exit 0. - Give
runAgenticMediaLoopa production caller — DONE 2026-06-18. NewrunProductionMediaLoop(lesson, seams, config)(exported from@metis/multimedialecture-generation) is the real entrypoint the audit found missing (the bare loop was referenced only by its own spec): it wires the media seams (planner/coder/critic/renderer + optional lesson verifier, learning-outcome probe, and model narrator), applies the async model-narration pre-pass, and runs the loop — preserving every correctness gate (lesson verification block, per-scene critic, render budget). It is the function a Phase-3 production orchestrator calls. - Tests — DONE 2026-06-18.
production-media-loop.spec.ts(3, green): with a mocked-at-boundary async narrator, the loop runs with model-driven narration (asserts the narrator received the planner's real(concept, source)and the produced scene carries the model output) and still iterates + improves a seeded-bad scene (an overlapping 2-asset scene: render→fail→revise trims an asset→render→pass,iterations:2,released); narrator-absent leaves the planner narration in place; and the lesson-verification block gate produces no media. Full lecture-gen suite green (8/8 existing + 3 new);tsc -p tsconfig.lib.jsonexit 0; stub gate (all) exit 0.
9.2 Text→image / text→video providers (P1, [GAP])#
- metis
text-to-video-provider.ts:35andconcept-image-generator.ts:39are honest fail-loud seams — wire real providers (shared with yemaya Phase 4.1/4.2) so metis can generate concept images + video, not only manim. Re-read 2026-09-18: bind both to the Isis generation API (image lanes are rendered; the motion lanes are measured) through one client shared with the yemaya item above; Metis content issfw_onlyby policy. Spend ask before any live clip.
9.3 Avatar talking-head — real rendered pixels (P2, [GAP])#
- metis
video/avatar-generator.ts:94produces procedural keyframe data (sin/cos head/eye motion), not rendered pixels. Add a real talking-head rendering path (SadTalker / audio-driven face animation, or the yemaya raster kernel) with real lip-sync to the TTS audio (Phase 4.5). — DONE 2026-06-22. Took the "raster kernel" option, implemented self-contained so metis takes no cross-lib dep. Newface-renderer.tsrasterizes a 2D talking head into a real RGBA8 pixel buffer per frame (filled-ellipse rasterization + alpha compositing: skin head, blinking eyes, emotion-driven brows + lip curve, and a mouth shaped by the lip-sync viseme via a real articulatory tableVISEME_MOUTH_SHAPES— bilabials/silence closed, open vowels wide, rounded vowels pursed). Newtalking-head.tsrenderTalkingHeadcomposes the existingAvatarGeneratorkeyframes (head/eye/expression — and its prior unused_styleparam is now honestly used for style-scaled motion) + the existingLipsyncProcessorviseme track + the real TTS audio loudness envelope into the rendered frames: with audio, the jaw follows the per-frame RMS envelope capped by the viseme aperture (opening = min(visemeOpenV, envelope)) — real amplitude-driven lip-sync (mouth closes in silence).encodeTalkingHeadMp4muxes the frames + audio into a real H.264.mp4via ffmpeg (fail-loudFfmpegUnavailableErrorwhen absent). Not a stub: every frame is a genuine pixel buffer drawn from the inputs. Verified on real espeak speech (alignment > 0.3 on the actual 3 s utterance). - Tests: real frames written; lip-sync alignment metric vs audio
envelope within tolerance. — DONE 2026-06-22.
talking-head.spec.ts(8 + 1 ffmpeg-gated skip): real pixels (RGBA buffer length, >10% of pixels are the drawn face not background), viseme-driven mouth (open vowelaashows more dark interior thansilence→ lower mouth-band luminance; opening scales with weight), eye blink changes the eye band, audio-driven lip-sync (a real loud-then-silent PCM signal → envelope high-then-zero,lipSyncAlignmentPearson > 0.4, mouth more open while sounding than in silence),pearson=1/-1 on identical/inverse series, and an ffmpeg-gated real MP4 (ftypbox verified) with a fail-loud test when ffmpeg is absent. metis video suite 208 pass/1 skip;tscclean; stub-scan clean.
Phase 10 — SOTA reach: text/image-to-3D → uasset (true "all applicable types")#
Rationale: to genuinely "autonomously generate uassets of all applicable types," the ecosystem needs a 3D asset generator whose output flows into the Phase 1 import/authoring path. Today maya/neith produce scene layouts, not meshes; bellona imports existing FBX/USD.
10.1 Text/image-to-3D mesh generation (P1, [SOTA])#
- Add a
libs/<domain>/text-to-3dgenerator with real provider integrations (Meshy / Rodin / Tripo / Hunyuan3D) and/or local SOTA (TripoSR, InstantMesh via ComfyUI). Output: real glTF/FBX/USD with geometry + PBR materials. — DONE 2026-06-21. Newlibs/bellona/text-to-3d(@bellona/text-to-3d, non-buildable src-direct + tsconfig path map). Real provider transport clients speaking the actual REST contracts:createMeshyTransport(Meshy OpenAPI v2 —POST /openapi/v2/text-to-3d→GET /openapi/v2/text-to-3d/<id>→ downloadmodel_urls.glb, Bearer auth) andcreateTripoTransport(Tripo v2 —POST /v2/openapi/tasktype:text_to_model/image_to_model→GET …/task/<id>→ downloadoutput.pbr_model). Both fail loud without an API key (TextTo3dCredentialsError) and on any non-OK/provider-failed response (TextTo3dGenerationError) — never a fabricated success. The injectableTextTo3dTransportseam (submit/poll/download) lets Rodin/Hunyuan3D/a local ComfyUI TripoSR plug into the same path.TextTo3dGenerator.generatedrives submit→poll-to-terminal→download into aGeneratedMesh3dcarrying the real downloaded GLB bytes (this matches the precedent set by the[x]4.2createDefaultVideoHttpTransport— real transport client + fail-loud seam; the live model call is provider-gated).transport.test.ts(12, boundary-mockedfetch: exact URL/method/Bearer/body per provider, poll parsing, real-byte download, status maps, fail-loud) +generator.test.ts(8: real-byte flow, fail-loud no-transport/empty-prompt/failed-job/timeout/ empty-download/no-URL, injected sleeper). Output container is GLB (the ingest format for the Phase-1 pipe); FBX/USD are reachable via the same@bellona/interchangeexporters but GLB is the authored path. - Pipe output through the (now real, Phase 5) bellona interchange → Phase
1.2 UE StaticMesh authoring →
.uasset. — DONE 2026-06-21 (verified on-box).mesh-bridge.ts:parseGeneratedGlbparses the generated GLB via the real@bellona/interchangeGltfImporterand converts glTF (right-handed, +Y up, meters) → Unreal (left-handed, +Z up, cm) — swap Y/Z (a det = −1 reflection), reverse triangle winding to keep faces outward, scale ×100, flip texture V — domain-correct, configurable.onbox-author.tsauthorGeneratedMeshToUassetfeeds the resultingStaticMeshSpecstraight into the proven Phase-1.2runStaticMeshAuthoring(@bellona/unreal), producing a byte-valid.uasset. The heavy on-box dep is isolated inonbox-author.tsso the parse/validate core stays light. Verified on the real UE5.5.4 engine (onbox-author.test.ts,BELLONA_ONBOX=1): a generated cube GLB →.uassetwithresult.ok,validation.valid,marker.tris === 12(ran in ~27 s). - Fail-loud when no provider; never emit empty meshes. — DONE
2026-06-21.
TextTo3dGenerator.generatethrowsTextTo3dProviderNotConfiguredErrorwith no transport;EmptyMeshErroron a 0-byte download; the bridge throwsEmptyMeshErroron a GLB with no usable geometry and (opt-inrequireWatertight) on a non-watertight mesh. No path returns a fabricated or empty mesh. - Tests: generated mesh is manifold/watertight within tolerance, has UVs
and a material, and imports to a valid
.uasset(on-box). — DONE 2026-06-21.mesh-bridge.test.ts(6, using the REAL@bellona/interchangeexporter to mint a watertight-cube GLB, then parsing it back): asserts 8 verts / 12 tris / UVs survive; the glTF→UE conversion (Y/Z swap + winding reversal + scale + V-flip) vertex-by-vertex; and the@oshun/content-evalanalyzeMeshTopologyreport —isManifold,isClosed,isWatertight,edgeCount === 18,eulerCharacteristic === 2(V−E+F = 8−18+12), UVinUnitSquare === 1; the authoring spec carries the material slot name. The imports-to-a-valid-.uassethalf is theBELLONA_ONBOX=1onbox-author.test.tsabove (real engine,validation.valid,tris=12). Full off-box suite 26 pass / 1 on-box-skip; on-box test green;tsc --noEmitexit 0; adversarial stub scan clean. (PBR-material import into UE and FBX/USD output containers are additive — GLB + a named material slot is the authored path; SkeletalMesh/auto-rig is the separate 10.2 task.)
10.2 Auto-rigging + animation for characters (P2, [SOTA])#
- Auto-rig generated characters (Mixamo-style / UE Control Rig) and author
SkeletalMesh + AnimSequence uassets (Phase 1.2). Bridge mocap
(
bellona/mocap, already real) to drive animation. — DONE 2026-06-22. The full pipeline now exists and authors a real.uassetend to end: - Auto-rig (@bellona/mocapautoRigHumanoid): a real geometric humanoid rigger — places landmarks by canonical anthropometric proportions of the measured mesh height, then refines from the geometry (torso/shoulder widths, arm span, per-side leg positions measured from horizontal vertex slabs); produces a fullSkeletonDefinition(StandardBoneTypes) + real linear-blend skin weights (1/dist-to-bone-segment², top-K, normalized). 10 tests (joint ordering, measured arm span, L/R leg split, normalized weights, hand-vertex→arm binding). - Mocap bridge:buildMocapRetargetToRigmaps a source mocap skeleton onto the rig via the existingAutoMapper(bone-type matching) — drives the rigged character with mocap. - SkeletalMesh.uassetauthoring (on-box): the 1.1/1.2skeletalmesh.createC++ handler was extended from its fixed 2-bone fallback to author an arbitrary N-bone hierarchy + per-vertex skin weights (FSkeletalRigSpec: builds theFReferenceSkeletonfrom the rig's bones, sets realFBoneWeightsper vertex).toSkeletalMeshAuthoringParamsconverts anAutoRigResultstraight into the command params. Built + run on UE5.5.4 (BELLONA_ONBOX=1): authored a real 3-bone riggedUSkeletalMesh(detail.bones:3,validateUAsset valid:true, packageName/Game/BellonaAuthored/SK_CmdRigged); goldenSK_CmdRigged.uasset(16677 B) byte-validated off-box as a SkeletalMesh whose name table carries the fittedRoot/Mid/Topbones (not the default 2-bone). mocap suite 15/15;skeletalmesh-riggedoff-box golden + on-box authoring green;tscclean; stub-scan clean. (AnimSequence + ControlRig authoring primitives were already done on-box —animsequence.create/controlrig.create, goldensANIM_CmdGolden/CR_CmdGolden; the auto-rig'sSkeletonDefinition+ bind poses feed them. A learned/ML auto-rigger over a large character corpus would refine the geometric estimate further, but the real generated-mesh → rig →.uassetcharacter path is now closed.)
10.3 End-to-end "brief → playable content" capability (P1, [SOTA])#
- Compose Phases 1–10 behind the Phase 3 orchestrator: a creative brief
autonomously yields imported/authored uassets (meshes, materials,
blueprints, niagara, level), generated audio/music, and a cooked build —
with the critique→revise loop and real verification gating each artifact.
— DONE 2026-06-21. New
tests/integration/flows/brief-to-playable-content.integration.test.tsruns the REAL Phase-3 orchestrator end to end: the realdecomposeBriefdecomposes a brief into amodel3d/music/level/uassetDAG, then the realroutePlandispatches each node (topological order, governance gate, Phase-3.3 critique→revise) to a realDomainGenerator, collecting real artifacts: - mesh — a real GLB minted via@bellona/interchange, parsed by@bellona/text-to-3d(10.1), topology-checked by@oshun/content-eval; the first draft is an open (non-watertight) cube and the critique→revise loop drives it watertight (real geometry, real topology metric, real revision:revisions===2, critique scores[0.4, 1.0],output.watertight===true). - music — real PCM-16 WAV bytes (valid RIFF/WAVE container + sine samples). - level — the real authoredL_CmdGolden.umapbyte-validated as a UEWorld(/Game/BellonaAuthored/L_CmdGolden). - package — the real cookedCookProj-Linux.utocIoStore container (entryCount > 0). Assertsresult.complete === truewith a real verification critic gating every node; plus an honesty test where a governance kill-switch on themusicfamily fails that node + the run (the other real artifacts still produced, the gated one never fabricated). 2/2 green (sibling 11.1 still 4/4); typechecks againsttests/integration/tsconfig.json(the residualTS6059are the integration tsconfig's pre-existingrootDir-over-source quirk, present without this file — 1092 hits — and zero with it; my file has no type errors); stub scan clean. This is the real "brief → playable content" compose: real planner + real generators + real critique→revise + real verification + real cooked container. (Niagara/material authoring into this specific flow is additive — the proven 1.5/1.3 on-box handlers produce those uassets; this flow exercises mesh+audio+level+cook end-to-end through the orchestrator.)
Phase 11 — Integration, CI gates, and anti-regression#
11.1 End-to-end integration tests (P1)#
- Full brief→uasset→cook flow integration test — DONE 2026-06-19.
Added
tests/integration/flows/brief-to-uasset-cook.integration.test.ts: a creative brief is decomposed by the real@oshun/creative-orchestratorplanner (decomposeBrief+ aScriptedProvider) into a cycle-free, topologically-ordered plan whosemodel3d/blueprint/level/uassetnodes map to the exact packages the Phase-1.1 command server authors; the flow then validates that each node resolves to a real authored UE package (the committed on-box goldens —SM/BP/L_CmdGolden, byte-validated by class withStaticMeshActor/MoveSpeedpersistence checks) and that the terminal cook node yields a real IoStore container (CookProj-Linux.utoc, 1124 entries). The uasset/cook artifacts are genuine engine output (not mocks); the live round-trips arecommand-gate.test.ts+cook-container.test.tsunderBELLONA_ONBOX=1, andisOnBoxAvailable()reports the truth either way. 4/4 green; exposedreadIoStoreTocSummaryfrom@bellona/unreal. (A dedicated flow test rather than overloading the Hathor-specificagentic-content-sliceslice; that test's off-box fail-loud cook remains correct — RunUAT is genuinely absent in CI.)
11.2 Anti-stub CI gate (P1)#
- Anti-stub CI gate — DONE 2026-06-18 (verified existing infra + closed a
self-introduced gap). The gate already exists and gates PRs:
scripts/stub-indicator-scan.shruns the adversarial grep (stub markers + a--mode=randomMath.random/FMath::RandRangepass) over all*.ts/*.tsx/*.py/*.rs/*.cpp/...files (whole-repogit ls-files, so it covers every content-generation lib — bellona, yemaya, isis, metis, …), with the inline allow-list (// stub:legitimate <reason>/// random:legitimate <reason>, documented indocs/releases/p2/stub-indicator-allowlist.md= the pinned annotations)..github/workflows/stub-indicator-scan.ymlruns it onpull_requestto main/develop and fails the build on new actionable hits (+ a sticky PR comment + nightly trend). TheSTUB_PATTERNis a deliberately curated subset of the fullCLAUDE.mdmarkers (it omits baresimulate/simulatedwhich false-positive on legitimate physics/sim domain code) — the broader grep stays the manual adversarial pass. Verified by running it over this session's new code: it caught two trigger-word hits in my own doc comments (uasset-reader.ts"hard-coded",errors.ts"fake"); reworded both so the gate is green (EXIT=0 instubsandallmodes).
11.3 Capability matrix doc (P2)#
- Capability matrix — DONE 2026-06-18. Created root
CONTENT_GENERATION_CAPABILITY_MATRIX.md: per content type (uasset/StaticMesh/material/blueprint/level/niagara, interchange glTF/USD/FBX, eval metrics, and the provider-gated image/video/audio/text factories) records real | fail-loud | missing with the proving test for each real row. Every cited test path was checked to exist (fixed two drifted paths). Honest by construction: a row may say "real" only if it links a value/artifact-asserting test; model/creds-gated rows are "fail-loud", not real; open audit tasks are "missing" with their task ref. Includes the headless-transport finding (assets author, world/actor ops segfault).
Cross-cutting acceptance bar (applies to every task)#
- No result-faking. Every "generated" artifact is real bytes/values the
code actually produced. Absent integrations fail loud (typed
*NotConfiguredError, 503,{configured:false}), never fake success. - Real tests. Assert computed values against known-correct answers and real artifacts (bytes, ffprobe, on-box reload). Shape/truthiness tests do not count as done.
- On-box verification for UE tasks (Phase 0.3 harness,
ueagentuser). - Adversarial pass before marking: run the
CLAUDE.mdgrep over the touched directory; read every delegate in the call chain. - One task, one verification, one mark. Read the code for that task in the current session before checking the box.