mirror of
https://github.com/Heretek-AI/game-engine.git
synced 2026-11-07 10:55:58 -10:00
* docs: move P5-06 notes out of frozen NOTES.md #185 appended its slice notes to docs/NOTES.md, which spec rule 7 freezes. Move them to docs/notes/P5-06.md unchanged and record the open engine_audio_spatial race item. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VjcGdfRk1pMqciEq3aZUEZ * docs: record P5-06 30x audio repeat result (from #188) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VjcGdfRk1pMqciEq3aZUEZ --------- Co-authored-by: Claude <[email protected]>
58 KiB
58 KiB
Illusion Layer implementation notes
Frozen: parity-roadmap slices log to
docs/notes/<slice id>.mdinstead (seedocs/notes/README.md).
Baseline
- Date: 2026-10-30 (ILLU-M0).
origin/maincommit:3112291aa06f476b068b37502cfcee7d198423c0(Merge PR #51).- Main-drift note (IMPLEMENTATION_PROMPT §3): spike was done on
70846a5; currentorigin/mainis3112291, recorded here as required. NoFind-text edits were needed in M0 (no code changes). - Branch:
feat/illusion-layer-abi-4.2, created fromorigin/mainin this task. - Tool versions:
dotnet 10.0.403,cmake 3.31.6,ninja 1.12.1,g++ (Debian 14.2.0-19) 14.2.0. - CTest:
100% tests passed, 0 tests failed out of 40(seebuild/logs/m0-ctest.log). Failing tests: none. - Managed tests:
grep -c 'all checks passed' build/logs/m0-tests.log= 9 (>= 9),grep -c FAIL= 0 (seebuild/logs/m0-tests.log). dotnet build Engine.slnx -c Releaseexit 0 (seebuild/logs/m0-dotnet.log);cmake --preset ci-linuxandcmake --buildexit 0.
Discoveries
- 2026-10-30: all 40 CTests pass on main, including the 8 export tests listed in §4.7 (
engine_export_publish_aot,engine_export_stage*,engine_export_coldboot). The doc expects them to fail on machines whose .NET SDK lacks the NativeAOT ILCompiler package; this machine's SDK (10.0.403) evidently has it, so the M0 baseline is fully green. Later milestones compare against this all-pass baseline. - 2026-10-30: a stray
.hermes/scratch dir (Hermes runner cache) was present at repo root and showed ingit status; removed it (rm -rf .hermes) since it is runner scratch, not a repo artifact.build/is git-ignored, sobuild/logs/m0-*.logare not committed.
M1 spikes
- Date: 2026-10-30 (ILLU-M1). Spike sources created verbatim from
docs/IMPLEMENTATION_PROMPT.md:309-418underbuild/(git-ignored, not committed):build/spikes/flecs_spike.cpp,build/spikes/cs1503/cs1503.csproj,build/spikes/cs1503/Program.cs. - Flecs spike build/run (
c++ -std=c++20 -O2 -I../../deps/flecs/include -I../../deps/flecs/distr flecs_spike.cpp -L../ci-linux/deps/flecs -lflecs ...):
mover=456 mover2=456 health=457
lookup(sep="")=456 lookup(default)=0 has_parent_Spike=0
type_info size=12 align=4
query=0x6179464df9e0 field_count=2
table count=3 mover_col=0x617946550b90 mod64=16 health_col=0x617946550ad0 entities=0x617946550b60
tables=1 rows=3
e0 speed=0 x=100
e1 speed=1 x=0
e2 speed=2 x=100
e3 speed=3 x=0
e4 speed=4 x=100
fatal: entity.c: 1522: abort(): Spike\.Mover (INVALID_COMPONENT_SIZE)
exit=134
- CS1503 repro build (
dotnet build build/spikes/cs1503):
Chunk_Spike_Mover.g.cs(37,65): error CS1503: Argument 3: cannot convert from 'delegate* unmanaged[Cdecl]<Engine.Bindings.NativeApi.EcsChunkView*, void>' to 'delegate* unmanaged[Cdecl]<Engine.Bindings.NativeApi.EcsChunkView*, float, void>' [cs1503.csproj]
0 Warning(s)
1 Error(s)
Discoveries
- 2026-10-30: spike confirms §4.1 facts on this machine — same id on same-size re-register (
mover == mover2 == 456), abort (exit 134, SIGABRT) on different-size re-register,lookup(default)=0so callers must useecs_lookup_path_w_sepwith empty seps,size=12 align=4, only 3 of 5 rows matched (Health-gated). Columnmod64=16here vsmod64=32in the doc's capture — addresses vary per run, but the point stands: user columns are NOT 64-byte aligned. - 2026-10-30: CS1503 repro error carries a
to '...EcsChunkView*, float, void>'target suffix (doc acceptance cites only thefrom '...EcsChunkView*, void>'prefix), i.e. the generated chunk thunk drops thefloat dtparameter theTicksignature requires. M2's ABI work must reconcile the nativeEcsChunkView*-only callback shape with the managedTick(..., float dt)signature.
M2 ABI 4.2 header
- 2026-10-30: applied the 3 doc-ordered edits to
native/bridge/include/engine/bridge/engine_abi.h(IMPLEMENTATION_PROMPT.md:420-610): edit 1 bumpedENGINE_ABI_VERSION_MINOR1 -> 2 with the 4.2 comment tail; edit 2 added the generic tail (ENGINE_ABI_MAX_TERMS 8, ACCESS IN/INOUT, ERR codes -1..-6,EngineChunkColumns,EngineSystemCallback, 6 fn typedefs); edit 3 appended the 6 table fields ending withsystems_runas last field before} EngineApiTable;. Each Find text matched exactly once. - 2026-10-30:
grep -c 'ENGINE_ABI_VERSION_MINOR 2'->1;grep -nE 'EngineSystemsRunFn systems_run;'-> one line, last field before} EngineApiTable;;grep -c 'typedef struct EngineChunkColumns'->1. - 2026-10-30:
cmake --preset ci-linux && cmake --build build/ci-linux -j4fails as the source doc predicts (M2 acceptance block: "expected to fail until M3"):engine_abi.cpp:1013: error: missing field 'component_register' initializer [-Werror,-Wmissing-field-initializers]. Header is 4.2,kApiTablehas no new initializers yet — M3 fills them in. No drift: all 3 Find texts matched exactly once.
M3A GenericSystems registration
- 2026-10-30: created
native/bridge/src/generic_systems.hppverbatim fromdocs/IMPLEMENTATION_PROMPT.md<!-- file: native/bridge/src/generic_systems.hpp -->block (programmatic fence diff: match=True, 2022 bytes). - 2026-10-30: created
native/bridge/src/generic_systems.cpppart 1 (throughget()):register_componentwithecs_get_type_infopre-check returning 0 on size/alignment mismatch (never callsecs_component_initon mismatch — §4.1 abort rule), pluscreate_entity/set/getwith NULL-arg/in-run guards.register_system/runintentionally deferred to ILLU-M3B (child cardt_e07a5570owns them plus allengine_abi.cppwiring); fence diff vs doc block is exactly the split-note header + missing register/run tail. - 2026-10-30: TDD temporary spike
build/spikes/m3a_reregister_spike.cpp(git-ignored, DELETED after run):a=470 b=470 same=1,mismatch=0 (expect 0), set/get round-tripspeed=1.5 x=2.5,SPIKE PASS, exit 0. Spike compiledgeneric_systems.cppwith-Wall -Wextra -Werrorclean (flecs.c compiled as C;engine_core_export.hppstubbed in git-ignored spike dir only — header includes it under#ifdef __cplusplusonly so no repo change needed). - 2026-10-30: split note — full
cmake --build build/ci-linuxstill fails atkApiTablemissing 6 initializers (M2-predicted state); M3B adds the wrappers + table entries that fix it. Noengine_abi.cppchange in this commit by design (avoids colliding with M3B's 6 doc-ordered edits).
M3B systems run + shutdown (ILLU-M3B)
native/bridge/src/generic_systems.cppcompleted verbatim from the M3 doc fence (register_system + serial run() appended; file now byte-matches the doc fence minus the M3A-split NOTE header comment).grep -c ENGINE_ABI_ERR_IN_RUN= 3 (set, register_system, run in-run refusal paths).native/bridge/src/engine_abi.cpp: applied the 6 doc-ordered edits (each Find matched exactly 1x): generic_systems.hpp include, EngineChunkColumns static asserts (sizeof 120, offsets 64/96/104/108/112), GenericSystems member on EngineWorldImpl, generic.shutdown() before ecs fini in ApiWorldDestroy, 6 Api* wrappers (component_register/entity_create/entity_set+plan-invalidate/entity_get/system_register/systems_run with NULL-dispatch dt=0), 6 kApiTable tail entries.native/tests/test_abi_layout.cpp: 312→360B table-size edit applied.- Build:
cmake --build build/ci-linux -j4exit 0.ctest -R engine_abi_layout -Vpasses, showsabi: EngineApiTable 360B (ABI 4.2 generic tail). engine_compositor_livefails post-M3B withABI version mismatch: native 4.2.0— expected: managedNativeApi.g.csstill pins AbiVersionMinor=1; regeneration is owned by ILLU-M5 (abi_codegen). Not a native regression: native-sideengine_abi_layoutis green.- Card said "7 engine_abi.cpp edits" but the doc contains exactly 6 engine_abi.cpp edit blocks + 1 test_abi_layout.cpp edit (step 3); all 7 applied.
M4 native test of the generic tail (ILLU-M4)
native/tests/test_generic_systems.cppcreated verbatim from the M4 doc fence (programmatic fence diff: BYTE-MATCH, 149 lines).native/tests/CMakeLists.txt: applied M4 edit #1 verbatim (engine_generic_systems add_executable/link/flags/add_test, TIMEOUT 120). Edit #2 deferred to M9 per doc.- 312→360B table-size edit was already applied in M3B (test_abi_layout.cpp:218) — verified present, not re-applied.
- Build:
cmake --build build/ci-linux --target engine_generic_systems engine_abi_layoutexit 0.ctest -R engine_abi_layout|engine_generic_systems: 100% passed (2/2). Verbose tail showsgeneric_systems: all checks passedplus the doc acceptance lines[PASS] systems_run with NULL dispatchand[PASS] row 4: speed=4.0 x=25.0 (want speed=4 x=25.0). - No fix-forward needed: first-run green,
generic_systems.cppuntouched.
M5A codegen parser rejects unknown 4.2 types (ILLU-M5A)
- Applied doc edits #6 (
ValidateCType) + #7 (CsType) ONLY totools/abi_codegen/Program.cs: addedconst void*,const uint64_t*,const uint32_t*,const EngineChunkColumns*,EngineSystemCallback(plain Cdecl callback shape, no SuppressGCTransition). - Pre-change baseline:
dotnet run --project tools/abi_codegen -c Release -- .exited 1 withunsupported ABI type 'const EngineChunkColumns*'at engine_abi.h:492 (fail-closed confirmed). - Post-change: still exits 1, now at
Fn typedef(s) not used by EngineApiTable: EngineSystemCallback— the required-entry + unused-typedef checks (edits #5/#12) and emission belong to ILLU-M5B. No.g.cstouched.
M5B 4.2 bindings emitted (ILLU-M5B)
- Applied doc edits #1-5 + #8-13 to
tools/abi_codegen/Program.cs(M5A already owned #6+#7): GenericDefineRe + ParseGenericDefines (9 required consts, MAX_TERMS==8 gate), EngineChunkColumns required struct, 6 required fn entries, Emit signature carriesgenericdict, MaxTerms..ErrInternal consts emission, EmitChunkColumns with fail-closed 120-byte field check, EngineSystemCallback unused-typedef removal, loader drift guard extended with EngineChunkColumns 120B. dotnet run --project tools/abi_codegen -c Release -- .printsabi_codegen: 44 entries, exit 0. Second run:git diffon NativeApi.g.cs unchanged (idempotent).- TDD red: pre-test-edit
dotnet run --project managed/Engine.TestsshowedAbiLayoutTests: 2 FAILED(minor==1, ApiTable 312B) against the new 360B table. Applied 4 AbiLayoutTests edits (minor 1->2, ApiTable 312->360B + component_register @312 + systems_run @352 + ChunkColumns 120B, columns offsets 96/104/108/112, system_register Cdecl assertion). Post-edit:AbiLayoutTests: all checks passed,grep -c FAIL= 0. grep -c SuppressGCTransition NativeApi.g.cs= 3 (unchanged); systems_run entry is plain Cdecl;Size = 120appears once.
M6 Ecs helpers (ILLU-M6)
managed/Engine.Runtime/Ecs.cscreated verbatim fromdocs/IMPLEMENTATION_PROMPT.md:1919-2067doc fence (programmatic fence diff: VERBATIM, 5043 bytes).dotnet build managed/Engine.Runtime -c Release:Build succeeded. 0 Warning(s) 0 Error(s).- TDD spike
build/spikes/ecs6/(git-ignored, DELETED after run): calledEcs.ComponentId<Mover>twice on a liveEngineWorldCreateworld —GREEN(same non-zero id). Note:ApiLoader.Load()resolvesengine_coreby SONAME only, so the spike preloaded/home/hermes/game-engine/build/ci-linux/native/libengine_core.soviaNativeLibrary.Load(absolute)and boundengine_get_api_tableby export instead of usingApiLoader.Load(). dotnet build Engine.slnx -c Release --nologo:Build succeeded. 0 Warning(s) 0 Error(s).
M7A generator Register emission (ILLU-M7A)
- Replaced
managed/Engine.Analyzers/ComponentSystemGenerator.csperdocs/IMPLEMENTATION_PROMPT.md:2068-2821up to and includingRegisteremission: doc-verbatim header/pipeline/discovery/resolution/EmitOne/EmitSizeRegistration/SystemName, plus doc-verbatimComponentParams/RowArgs/IsRunnable/IsFloatScalarhelpers.EmitChunkRunneris an inert M7A stub with the stable ABI 4.2 signature (RunChunk(EngineChunkColumns*, float dt)); the span-unpacking/row-loop/try-catch body is owned by ILLU-M7B (child cardt_d8676106). Ecs.csnote: ILLU-M6's commit (1078062) exists only on its dispatch branch, not onfeat/illusion-layer-abi-4.2in this worktree, so the M7A spike build borrowed that file uncommitted for compilation only. It is NOT staged in the M7A commit — M6's merge will supply it. (Flagging for the orchestrator: M6 shows complete but its file is unreachable from the feat branch.)- Acceptance:
/tmp/m7a_spike(cs1503 sources retargeted at this worktree'smanaged/projects):Build succeeded. 0 Warning(s) 0 Error(s)— CS1503 gone. The main-repobuild/spikes/cs1503still fails because it references main-repomanaged/(old generator + missing 4.2 bindings there); that is a spike-path artifact, not a generator defect. - Emitted
Chunk_Spike_Mover.g.cs:ComponentSize,Faulted, andRegisterblocks byte-match the §4.6 reference modulo whitespace (incl.table->EngineSystemRegister(world, name, ids, access, 2u, fn, userData)andSpike.Mover\0name);RunChunksignature matches, body inert by design (_ = view; _ = dt;) — full body is M7B's problem.
M7B RunChunk emission (ILLU-M7B)
- Replaced the M7A inert
EmitChunkRunnerstub with the doc body (IMPLEMENTATION_PROMPT.md:2679-2732): unrunnable guard keeps_ = view; _ = dt;, runnable path emitscount/TermCount/s_faultedguard, per-termSpan/ReadOnlySpanunpacking (col0self type viaselfFqn),try/forrow loopcol0[i].Tick(...)withRowArgs,catch (Exception)latchingVolatile.Write(ref s_faulted, 1). managed/Engine.Analyzers/ComponentSystemGenerator.csis byte-identical to the doc fence (2076..2809);dotnet build Engine.slnx -c Release:Build succeeded. 0 Warning(s) 0 Error(s)(CS0649 gone — latch now written).dotnet build build/spikes/cs1503(copied into this worktree so refs resolve):Build succeeded. 0 Warning(s) 0 Error(s). EmittedChunk_Spike_Mover.g.csis byte-identical to the §4.6 reference (:154-235) modulo the Roslyn BOM; containstable->EngineSystemRegister(world, name, ids, access, 2u, fn, userData)andcol0[i].Tick(ref col0[i], in col1[i], dt);.
M8 driver test (ILLU-M8)
- Created
managed/Engine.Tests/GeneratorDriverTests.csverbatim fromdocs/IMPLEMENTATION_PROMPT.md:2830-3081doc fence (10077 bytes); registered inmanaged/Engine.Tests/Program.csper the:3086-3108edit block (GeneratorDriverTests.Run()afterGeneratorTests.Run()). - Driver passes the same
CSharpParseOptions(LanguageVersion.Preview)to both the input tree andCSharpGeneratorDriver.Create(Facts :133 pitfall — avoidsInconsistent language versions). dotnet build Engine.slnx -c Release:Build succeeded. 0 Warning(s) 0 Error(s).dotnet run --project managed/Engine.Tests -c Release --no-build:GeneratorDriverTests: all checks passed, no FAIL.- Sabotage check (
? "in dt" : "dt"→? "in dt" : "0f", rebuild, rerun): exact doc-expected 4 lines —[FAIL] Mover row call forwards in Health + dt,[FAIL] Mover RunChunk: X += Health*dt, Y += Speed*dt for 4 rows,[FAIL] Mover RunChunk ignores a view with the wrong term count,GeneratorDriverTests: 3 FAILED. Reverted + rebuilt green. - Worktree note: this worktree's branch was stale at M5B (M6/M7A/M7B lived on sibling dispatch branches); fast-forwarded with
git merge --ff-only efd55cb(merge-base confirmed HEAD was ancestor) per the reuse-feat/illusion-layer-abi-4.2convention before writing the test.
M9 sample + CTest gate (ILLU-M9)
- Created
samples/IllusionSample/verbatim fromdocs/IMPLEMENTATION_PROMPT.md:3139-3443fences (csproj 1442B, Components.cs 1887B, Program.cs 7836B); applied both doc edits (Engine.slnxIllusionSample line,native/tests/CMakeLists.txtengine_illusion_sample gate withPASS_REGULAR_EXPRESSION "ILLUSION-SAMPLE-OK"). - Worktree note: this worktree's branch was stale at M5B; merged M6/M7A/M7B/M8 branch (
game-engine/t_4cce9280-illu-m8-generator-driver-test, HEAD 61e99f6) into this worktree before writing files, per the reuse-feat/illusion-layer-abi-4.2convention. dotnet build Engine.slnx -c Release:Build succeeded. 0 Warning(s) 0 Error(s). Direct rundotnet samples/IllusionSample/bin/Release/net10.0/IllusionSample.dll <libengine_core.so>: all 21 checks[PASS],0 managed bytes over 100 frames x 100k rows (got 0),[info] 100k Mover rows: 0.176 ms/frame,ILLUSION-SAMPLE-OK, exit 0.ctest --preset ci-linux -R engine_illusion_sample --output-on-failure -V:ILLUSION-SAMPLE-OK,100% tests passed, 0 tests failed out of 2. Fullctest --preset ci-linux:100% tests passed, 0 tests failed out of 42.
M10/M11 repair verification (orchestrator, feat tip 7f6ac55)
- Repaired 2026-10-30: archived 3 malformed auto-decomposed cards (no workspace_path, spawn-fail loops); cherry-picked the M6-M9 line onto feat (
6a265a3d70a3c6ac6a04ef09dd637f6ac55); origin/main restored to3112291after an accidental push of ILLU commits (all work preserved on feat branch, pushed). - M10 AGENTS.md edits NOT applied — approval-protected path, needs operator (see M11b report below for exact text).
- M11b full matrix from clean tree (logs build/logs/m11b-*.log), feat tip
7f6ac55:- cmake cfg=0, build=0, 0 warning/error lines; ctest 42/42 passed.
- dotnet format --verify-no-changes: format=0; dotnet build slnx: 0; Engine.Tests exit 0, 0 FAIL lines, 10 suites
all checks passed. - codegen 44 entries, re-run diff clean (codegen-clean=0).
- Matches M0 baseline (no new failures; SDK 10.0.403 ships NativeAOT ILCompiler so export tests pass).
- Full M11 evidence (decisions table, spec check) lives in the t_56fafa04 worktree note (
de148b8); this entry confirms the feat tip reproduces it.
PR (ILLU-PR)
- End-to-end PR: https://github.com/Heretek-AI/game-engine/pull/52 —
[Illusion] ABI 4.2 end-to-end (M0-M11), base main, head feat/illusion-layer-abi-4.2 (15 commits M0-M11 + M10 AGENTS.md fix). - Body cites spec Verification + Decisions log, states all 10 decisions followed. Gates pending at open (CI running); do NOT merge — to qa-gate final review.
- Merged 2026-10-30 as eabfb74; ILLU-FOLLOW proceeds on rebased main.
ILLU-FOLLOW portability + format pass (ILLU-FOLLOW, pre-merge prep; rebased onto main eabfb74)
- Precondition met 2026-10-30: PR #52 merged as eabfb74; prep commit rebased onto new main.
- Merge-independent work done on feat base (worktree t_28b5f4dd):
- §4.8 item 1 audit:
generic_systems.cpppasses clang strict-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wshadow -Wundef -Werror(macOS-analog flags) clean; fullcmake --preset ci-linuxbuild with ENGINE_WERROR=ON green,engine_generic_systems+engine_abi_layout2/2 PASS. - One hardening committed:
register_systemname copy now checkssnprintfreturn and re-pins NUL on truncation (explicit for-Wformat-truncation/MSVC /W4 readers; behavior unchanged — snprintf already NUL-terminates). No warnings disabled, WERROR untouched. - §4.8 item 4:
dotnet format Engine.slnx --verify-no-changesclean on pinned SDK 10.0.403 (global.json10.0.100+ rollForward latestFeature; CI setup-dotnet10.0.x) — "Formatted 0 of 99 files". - CI-matrix (Windows/macOS), Sonar, CodeRabbit, follow-up PR: unblocked by merge; in progress on rebased main.
- §4.8 item 1 audit:
ILLU-FOLLOW findings on rebased main (post-merge, same branch)
- CI matrix on merge commit
eabfb74: ALL green at merge time — native + managed on ubuntu-24.04, macos-15, windows-2025 (run 41124983149 success). CodeRabbit's flagged MSVC C4244 (qd.terms[i].inoutnarrowing) was already fixed pre-merge by6f4abe3(static_cast<int16_t>); no live Windows/macOS failure remains. Same for the IsComponent-gate thread — fixed by6f4abe3. - Remaining CodeRabbit thread (Minor, still valid on rebased main):
CheckBombin GeneratorDriverTests readsstackalloc int[3]without init — fixed here withnew Span<int>(ticks, 3).Clear()(commitacb7e02), behavior unchanged, tests green. - Extra-strict clang audit on rebased main (compile_commands.json flags +
-Wimplicit-int-conversion -Wshorten-64-to-32 -Werror -fsyntax-only):generic_systems.cppandengine_abi.cppboth clean. Nostrncpy, no warning suppressions (/wd,-Wno-, pragmas) in the new code; WERROR (/WX/-Werrorvia ENGINE_WERROR=ON) untouched. - No Sonar integration in this repo: no sonar properties/config, no Sonar workflow, no Sonar findings on PR #52. Nothing to resolve — recorded as verified-absent.
- Linux verification on rebased branch:
cmake --preset ci-linuxbuild clean, ctest 42/42 PASS;dotnet format --verify-no-changesclean;dotnet build0 errors; Engine.Tests 10/10 suites pass (289 [PASS] lines, incl. GeneratorDriverTests with the fix). - §4.8 items 2-3 (NativeAOT with generated systems, >16-byte alignment): NOT touched — out of scope as risks, no evidence they surfaced.
ILLU-FOLLOW-4: Sonar S6494 fix on PR #53 (snprintf-free CopyName)
- SonarCloud on PR #53 reported exactly ONE finding: cpp:S6494 MINOR "Use std::format or std::format_to_n instead of snprintf" at
generic_systems.cpp:184(the return-checked snprintf hardening) — Quality Gate B vs required A. Earlier "Sonar verified-absent" note was wrong for the follow-up branch: SonarCloud IS integrated at org level and analyzes PRs without repo-side config. - Fix (branch
game-engine/t_28b5f4dd-illu-follow-windows-macos-sonar-pass): replaced both snprintf copies with a boundedCopyNametemplate (strlen+min(len, N-1)+memcpy+ NUL;(std::min)parens for the Windowsminmacro). Applied inregister_system(generic_systems.cpp) andApiRegisterSystem(engine_abi.cpp — that site was missing from PR #53). Includes:<algorithm>/<cstddef>added, unused<cstdio>dropped from both TUs. Behavior unchanged (clamped NUL-terminated copy, same as snprintf "%s"). No suppressions, WERROR untouched. - Verified Linux:
cmake --preset ci-linuxbuild clean, ctest 42/42 PASS, extra-strict clang (-Wimplicit-int-conversion -Wshorten-64-to-32 -Werror -fsyntax-onlyover compile_commands flags) CLEAN on both files,dotnet format --verify-no-changesclean. - CodeRabbit on PR #53: "No actionable comments" (latest review), 0 inline threads. Pushed to PR #53 head; CI + Sonar + CodeRabbit re-run on push.
- §4.8 items 2-3: not surfaced, untouched.
ILLU-FOLLOW-4b: S5813 + duplication fix (shared header)
- Push of the S6494 fix closed S6494 but Sonar opened 3 new items on the new code: cpp:S5813 MAJOR x2 ("make sure use of strlen is safe" — one per TU, since
strlenon an unbounded caller pointer can over-read before the clamp) + 76.3% duplication on new code (the helper was pasted into both TUs; gate requires <=3%). - Fix: single shared
native/bridge/src/name_copy.hppwithCopySystemName— bounded scan viamemchr(src, 0, N-1)so at most N-1 bytes are ever read (no pre-scan over unbounded input), thenmemcpy+ NUL. Both call sites use it; nostrlen/snprintfremain in the new code, one definition kills the duplication.engine_abi.cppcalls it qualified (engine::bridge::) since that TU is in global scope. - Verified: WERROR build clean, ctest 42/42 PASS, extra-strict clang audit CLEAN on both TUs, standalone unit check of the helper (empty/nullptr/exact-fit/63/64/300-char/tiny-buffer) PASS. Pushed to PR #53 head; Sonar re-runs on push.
ILLU-FOLLOW-4c: all gates green on PR #53
- Final state (head
2cd64d1): Sonar PASS (0 open issues; S6494 closed, S5813 x2 resolved by memchr-bounded scan, duplication cleared by shared header), CodeRabbit "no actionable comments" + 0 inline threads, CI native+managed PASS on ubuntu-24.04/macos-15/windows-2025, Analyze C++ PASS, CodeQL PASS, supply-chain PASS, merge state CLEAN/MERGEABLE. - Local: WERROR build clean, ctest 42/42, extra-strict clang audit CLEAN,
dotnet format --verify-no-changesclean,git statusclean. - PR #53
[Illusion] Windows/macOS + Sonar follow-upwas opened by sibling task t_28b5f4dd (done/reviewed); this card's implementation-phase work landed on the same branch (commitsdcff5beengine_abi parity,a3f3181S6494 fix,fbeffab+ this NOTES entry). No duplicate PR opened. §4.8 items 2-3 never surfaced.
ILLU-FOLLOW-4d: CodeRabbit doc-contract fix (memchr safety claim)
- CodeRabbit's first real review of PR #53 (after OSS rate-limit reset;
@coderabbitai reviewretried x4 until "Review triggered" then "Review finished") posted 1 Minor thread:name_copy.hppheader comment claimed unterminated input "can never drive an over-read", butmemchr(src,0,N-1)itself reads up to N-1 bytes — a shorter unterminated buffer would over-read. Valid finding, doc-only fix: header + function comments now state the contract (src must be NUL-terminated within its readable range or expose >= N-1 readable bytes; shorter unterminated input needs an explicit length this helper doesn't take). No behavior change; both call sites pass valid C strings. - qa-gate round 1 verdict: code correct, Sonar green, but CI matrix was still in_progress at reviewed HEAD
6adfcf3and CodeRabbit had never run (rate-limit). Re-review requires CI green at final HEAD + CodeRabbit run with threads resolved.
PARITY P1-01 CI GPU lane (P1-01, 2026-10-31)
- Added
native/tests/gpu_required.hpp(spec-verbatimgpu_missing); all four GPU tests now route no-device / no-Vulkan / SDK-absent / capability-fail paths through it (failures += gpu_missing(...)), soENGINE_REQUIRE_GPU=1turns them into[FAIL]+ nonzero exit.ENGINE_TEST_SLANGC-unset skip intest_slang_pbr.cppintentionally left as[SKIP](optional tooling per spec). - CI: Linux apt list now installs the full §3.1 list (libvulkan-dev, mesa-vulkan-drivers, vulkan-tools, glslang-tools, X11/audio/dbus/udev dev packages);
SDL_VIDEO_DRIVER=offscreen+ENGINE_REQUIRE_GPU=1set on the Test step via runner-gated expressions (Linux-only, so macOS/Windows keep stock env). - Verified locally (mesa-vulkan-drivers present):
SDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 engine_renderer_bootstrapprintsgpu: llvmpipe (LLVM 19.1.7, 256 bits) api=1.4.305, exit 0, zero[SKIP]lines; indirect/slang_pbr/gi_zero_bake all exit 0 with GPU paths[PASS](only remaining SKIP is the intentional slangc-unset one). Acceptance probeENGINE_REQUIRE_GPU=1 VK_ICD_FILENAMES=/nonexistent engine_gi_zero_bakeprints[FAIL] gi-gpu: no physical deviceand exits 1 (test_gi_zero_bake: 1 FAILED). - Configure message confirmed:
renderer: Vulkan SDK headers found — real volk/VMA path.
P0-01 fnptr reflection (parity/p0-01-fnptr-reflection, 2026-10-30)
- Fix:
managed/Engine.Tests/AbiLayoutTests.cs:96—f.FieldType.IsPointer→f.FieldType.IsFunctionPointer || f.FieldType.IsPointer(§4.8). Fordelegate* unmanaged[Cdecl]<int, void> F,IsPointeris false on .NET 10 (dotnet --version→ 10.0.403); onlyIsFunctionPointeris true. Message text unchanged;NativeApi.g.csuntouched. - Verified the shipped test binary actually carries the fix: IL member list contains
get_IsFunctionPointeronly after the edit+rebuild (stashed pre-fix build lacks it);md5sumof bin DLL was stale-identical across rebuilds due to deterministic build, so usedstrings/IL-grep, not hash, to confirm. - Before (fix stashed):
cmake -S . -B /tmp/oot-prefix-check ... && cmake --build ... && ctest -R "engine_compositor_live$"→AbiLayoutTests: 42 FAILED(all 42ApiTable.* is fnptrlines[FAIL]), plus the live-probeDllNotFoundExceptionlines. - After: same OOT command →
AbiLayoutTests: all checks passed; remainingengine_compositor_livefailure is only the live probe (CompositorApiTests: 1 FAILED,PhysicsApiTests: 1 FAILED,DllNotFoundException: Unable to load engine_core). - Root cause of residual (pre-existing, out of P0-01 scope):
TryPreloadEngineCoreHandle(CompositorApiTests.cs:208, PhysicsApiTests PhysicsLivePreload) only probes staged CWD/appdir siblings after an upward walk findsEngine.slnx; under/tmp/oot/.../compositor_livethe walk never reaches the repo so it returnsIntPtr.Zero, and bare-SONAMENativeLibrary.TryLoad("engine_core")fails (verified false even withLD_LIBRARY_PATHset; absolute-pathNativeLibrary.Loadsucceeds and exportsengine_get_api_table). In-tree preset works because the walk finds the repo and thenbuild/ci-linux/native. Gotcha #19 documents the OOT/in-tree difference. - In-tree acceptance (§3.2):
cmake --preset ci-linux && cmake --build --preset ci-linux && SDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 ctest --preset ci-linux→ full suite green,incl.engine_compositor_live(AbiLayoutTests: all checks passed,[LIVE] compositor live path engaged,CompositorApiTests: all checks passed,PhysicsApiTests: all checks passed).
P2-01 native input module (PARITY-79, 2026-10-31)
- Delta vs prompt: issue #79 is stricter — pins acceptance to SDL_PushEvent-injected W/mouse/gamepad checks and calls out the SDL_PushEvent surprise explicitly. Met the issue version.
- Surprise confirmed in test:
SDL_GetKeyboardStateignores pushed key-down (spike §4.6:keystate[W]=0after push); InputState keeps its ownstd::array<uint8_t,512>fed only fromSDL_EVENT_KEY_*. NoSDL_GetKeyboardStateanywhere in engine code. SDL_PollEventalso yields SDL window events (spike: 3 events for 2 pushed);process_eventfilters strictly by type, default-ignores the rest; test drains blindly like renderer pump code.- Gamepad: opened on
SDL_EVENT_GAMEPAD_ADDEDviaSDL_OpenGamepad; injected axis/button events for unknown joystick ids latch onto the first free slot so headless CI (no gamepads) still proves the normalisation path. Axis normaliser: -32768..32767 -> [-1,1] clamped. - Feed seam:
RendererWindow::pump_events(quit_out, InputState*)overload feeds every polled event into the input state;renderer_frame(P1-09, lands later) calls this overload so no event bypasses the tables. Null sink == plain pump. - Sonar rules: no
strlen/snprintf/memset/C arrays in new code (incl. test); numbers viastd::to_charsN/A (no formatting needed);std::array/std::span-shaped storage. - Verified: full build clean (Clang 19; container GCC 14 fails pre-existing
job_scheduler.cppCPU_ISSET sign-conversion, unrelated),ctest -R engine_input -Vall[PASS],engine_renderer_bootstrapstill green.
P0-04: engine_export_publish_aot is environment-only (2026-10-31 decision)
- Slice: P0-04 (issue #68). Question: is the local
NETSDK1047: Assets file ... doesn't have a target for 'net10.0/linux-x64'failure a repo bug or an environment-only SDK defect? - Verdict: environment-only. On a fresh clone with the Microsoft .NET 10 SDK, the full §3.2 sequence passes, including all 22
engine_export*tests. Nonative/tests/CMakeLists.txtchange needed; NOT adding the-r ${EXPORT_SAMPLE_RID}restore step. - Evidence (fresh clone
fresh/, HEADe763de6, Debian 13, SDK10.0.403at/usr/share/dotnet/sdk/10.0.403):cmake --preset ci-linux→ exit 0 (configure 4.3 s).cmake --build --preset ci-linux→ exit 0 (473/473 targets).SDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 ctest --preset ci-linux --output-on-failure→100% tests passed, 0 tests failed out of 42, Total Test time 11.19 s.ctest --preset ci-linux -R engine_export --output-on-failure→100% tests passed, 0 tests failed out of 22, Total Test time 2.38 s;engine_export_publish_aotPassed in 1.66 s.dotnet build Engine.slnx -c Release --nologo→ exit 0;dotnet run --project managed/Engine.Tests -c Release --no-build→ exit 0 (HotReloadTests: all checks passed).
- Mechanism: the failing setup ran Ubuntu's source-built
dotnet-sdk-10.010.0.114 (per issue #68), which lacks theMicrosoft.NETCore.App.Runtime.NativeAOT.linux-x64ILCompiler restore path — henceproject.assets.jsonhad nonet10.0/linux-x64target after a plain (RID-less) build, and the--no-build -r linux-x64publish failed with NETSDK1047. With the Microsoft SDK, the publish step's implicit restore pullsruntime.linux-x64.microsoft.dotnet.ilcompiler10.0.14 (present in~/.nuget/packages) andExportSample/obj/project.assets.jsoncontains bothnet10.0andnet10.0/linux-x64targets (12linux-x64references). - Follow-up: close issue #68 as not-planned with this PR. If a contributor must use the Ubuntu source-built SDK, the fix is a setup test running
dotnet restore -r ${EXPORT_SAMPLE_RID}before the--no-buildpublish innative/tests/CMakeLists.txt(engine_export_publish_aotblock) — deliberately not added here since CI (Microsoft SDK viaactions/setup-dotnet) and this machine both pass without it.
P4-00 spike: visual fidelity facts (2026-10-31, branch parity/p4-00-spike-facts)
- Spec: added §4.12 to docs/PARITY_IMPLEMENTATION_PROMPT.md (entry points + inputs for PbrPipeline/GiFrameGraph/GpuCull/SceneSsbo, slangc v2026.20 CI facts, lavapipe timestamp support, P4-01..12 file list). Docs-only; no slice code, no golden (harness defined by P1-05, not yet in tree).
- PbrPipeline::create_gpu is a deliberate deferred stub (kInvalidArgument "deferred to compositor frame setup (M3-04)"); P4-06 wires vis->shade->post through the compositor, never calls it directly. PostPush is 20B (README "post 24B" line is a typo; header static_assert pins 20B).
- slangc: NOT in apt; the P4-00 spike manually fetched the shader-slang GitHub release asset (CI does not download it today). Latest at spike: v2026.20 (2026-10-17). Linux x86-64 glibc-2.28 tarball sha256 84ebd777968caceea26b21bfd014341078be6047be6c5a963a69f984f896cebd.
slangc -vprints bare2026.20;--versionis rejected (E00017). All 10 slang_build.py entry points compile to magic 07230203 / SPIR-V 1.5. slang_build.py --slangc refuses out-of-tree shader dirs (embed step requires in-repo paths). - lavapipe timestamp queries WORK at the driver level: timestampPeriod=1.0ns, 64 valid bits, monotonic TOP/BOTTOM_OF_PIPE results (spike: raw-Vulkan g++ -lvulkan program, SDL_VIDEO_DRIVER=offscreen). Engine scene-pass profiler integration (P3-09) remains unverified.
- Evidence runs: engine_slang_pbr / engine_gi_zero_bake / engine_gpu_indirect all PASS on lavapipe (SDL_VIDEO_DRIVER=offscreen) from build/ci-linux binaries.
- Prior P4-00 attempt (d5bbfac/d21040d on feat/parity-p4-fidelity, never merged) was stale: header comment for create_gpu changed since, and its §4.12 claimed the golden harness "already exists" when golden_compare.hpp is not in tree on main. This PR re-verifies everything against current main.
- CodeRabbit round 1 (5 inline threads, all valid): destroy() runs BEFORE device shutdown (test destroys gi while dev alive); GpuCull create_gpu is sentinel marks, not VMA allocation — doc now names both counter buffers (CullPush::count_addr vs HizPush::draw_count_addr); second compute-to-DRAW_INDIRECT barrier in P4-06 frame order; slangc is a manual spike fetch, not CI; timestamp verdict scoped to driver-level probe.
P4-01: glTF metallic-roughness materials (2026-10-31, branch parity/p4-01-pbr-materials)
Materialasset + component:native/renderer/include/engine/renderer/pbr_material.hpp(PbrMaterialAsset,MaterialComponent64B blittable twin,PbrMaterialLibraryslot allocator,to_scene_record()packing the exact 64BSceneMaterialfield order with bit-exact u32 lanes,PbrValidateMaterial()fail-closed on NaN/Inf/blend+mask).- cgltf import:
material_import.hpp/.cpp(PbrImportMaterials/PbrImportMaterial, sRGB->linear factor decode, emissive_strength via theKHR_materials_emissive_strengthEXTENSION object — not a bareemissiveStrengthkey, which cgltf silently ignores). - Shade/post stages move from slang to GLSL (
pbr_shade.vert/frag,post_composite.vert/frag, glslangValidator--target-env vulkan1.3, blobs re-embedded into the same*_spv.incnames): slangc 2026.20 emits physical-addressing SPIR-V that lavapipe's spirv-val rejects (VUID-08737 OpStore not a logical pointer) — even the UNMODIFIED M3-03 blobs recompiled with 2026.20 fail, while glslang output passes clean. Visibility pass stays slang. P4-00's §4.12 slangc facts stand (version/sha/entry points); this adds the lavapipe-consumability caveat. - Bindless-set-0 reality check (validation layers): set 0 carries ONLY scene/material
SSBOs + texture/sampler arrays — no lights/grid/index. Light SSBOs + visibility image
ride the test-owned set 1 (shade set-1: vis STORAGE_IMAGE[0], albedo COMBINED array[2],
lights/grid/index SSBOs[3/4/5]). glslang maps texture-only
usampler2Ddecls to SAMPLED_IMAGE (not COMBINED) — storage-imager32ui readonly uimage2Dkeeps the type exact. - Root-cause gotcha (worth a §7 line): the SceneInstance ids row is UINT32 — declaring the
record as
vec4[]in GLSL reinterprets small ints as denormal floats, anduint(denormal)is 0, silently pinning every instance to material 0 (image rendered but flat/wrong). Fix:uvec4 sceneRows[]+uintBitsToFloatfor float lanes. Debugged via push-flag visualization (metallic/vis.x/row5.y/row4.w probes) + upload verification by mapping the staging buffers after submit. - Golden:
native/tests/golden/p4_01_spheres_320x240.png(5x5 grid, metallic x 0..1, roughness y 0.04..1.0, one point light, shade->post offscreen + readback) via newnative/tests/golden_compare.hpp+golden_stb.cpp(P1-05 contract: ch±2, 0.1% pixels,ENGINE_UPDATE_GOLDENS=1overwrites, actual/diff PNGs on failure). Vulkan NDC is already Y-down: the fullscreen-triangle vertex shaders pass pos through (no Y negation — a-pos.yflip samples the HDR target upside-down) so vUv 0 is top-left, matching readback rows. - Evidence:
ctest --preset ci-linux44/44 pass withSDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1;engine_pbr_materials+engine_pbr_goldenall PASS incl.[PASS] golden: p4_01_spheres_320x240and the metallic-varies sanity check; managedEngine.Testspass;slang_build.pyuntouched (slang sources it compiles — vis/gi/shadow — are unchanged).
PARITY P1-02 Fetch and statically link SDL3 3.4.18 (P1-02, 2026-10-31)
- Replaced the find_package/pkg-config SDL3 block in
native/renderer/CMakeLists.txtwith the spec §4.2 FetchContent block +URL_HASH SHA256=c4b08b950bd29d83caae0ab8d884298d27046a9d30700fb42841c50992665c90(computed viacurl -sSL -o /tmp/dep.tar.gz <URL>+cmake -E sha256sum). Exact §4.2 variable set (no extraSDL_TESTS):SDL_SHARED/OFF, SDL_STATIC/ON, SDL_TEST_LIBRARY/OFF, SDL_EXAMPLES/OFF, SDL_INSTALL/OFF, linkSDL3::SDL3-static, defineENGINE_RENDERER_HAS_SDL3=1, configure message verbatim. deps/README.md: added SDL3 pin row (tag + tarball URL + hash + static-link note). CreatedTHIRD_PARTY_NOTICES.md(first dependency PR owns it) with the Zlib license text from the release-3.4.18 tarballLICENSE.txt.native/tests/CMakeLists.txt:engine_renderer_bootstrapnow carriesENVIRONMENT "SDL_VIDEO_DRIVER=offscreen"so the real window path is exercised even when the developer forgets the variable;ctest --preset ci-linux -R engine_renderer_bootstrap -Vprints[PASS] window: valid + extent(bare run without the driver still passes via the oldcreate fails only for missing video devicebranch — that CTest property is what closes the §4.2 hard-rule gap).- Surprise (spec §4.2 predicted, confirmed live): SDL configure hard-fails on X11-extension dev gaps — this box (Debian 13, not the §3.1 Ubuntu image) failed first on
XTEST(Couldn't find dependency package for XTEST), fixed bysudo apt-get install -y libxtst-dev. Also installedlibxcursor-devper the §4.2 surprise. No CMake change needed: spec uses dynamic X11 (Video drivers: dummy kmsdrm(dynamic) offscreen wayland(dynamic) x11(dynamic)), so missing-devpackages fail configure rather than degrade silently. - Verified: configure prints
renderer: SDL3 release-3.4.18 (static) — real window path;SDL_VIDEO_DRIVER=offscreen engine_renderer_bootstrapprints[PASS] window: valid + extentand fullRENDERER_BOOTSTRAP_OK, exit 0;SDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 ctest --preset ci-linux42/42 PASS (8.29 s).
PARITY P1-03 Enable VK_KHR_swapchain in VulkanDevice (P1-03, 2026-10-31)
DeviceCreateInfo::enable_swapchainadded; when true, init() probesvkEnumerateDeviceExtensionPropertieson the CHOSEN physical device and failskDeviceCreateFailed+ exact"VK_KHR_swapchain missing"when absent, else passes the ext indc.enabledExtensionCount/ppEnabledExtensionNames.supports_present(VkSurfaceKHR)wrapsvkGetPhysicalDeviceSurfaceSupportKHR(physical_, graphics_family_, ...)(false on null/uninitialized).- Test: new
test_vulkan_swapchain_presentintest_renderer_bootstrap.cpp— offscreen window, SDLrequired_instance_extensionsasextra_instance_extensions, init withenable_swapchain=true, assertsdev.proc("vkCreateSwapchainKHR") != nullptr([PASS] device: swapchain ext enabled),create_surface+supports_present([PASS] device: queue supports present). No-device / no-video paths route throughgpu_missingsoENGINE_REQUIRE_GPU=1fails instead of skipping. - Verified:
SDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 engine_renderer_bootstrapprints both acceptance lines +RENDERER_BOOTSTRAP_OK, exit 0 (llvmpipe 1.4.305); same withVK_ICD_FILENAMES=/nonexistentprints[FAIL]lines, exit 1; full suiteSDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 ctest -j442/42 PASS (one parallel run showed acompositor_live_stage_native_linksymlink race that passes on retry — pre-existing fixture staging, unrelated to this change).
P4-02: HDR emissive + exposure tonemap (2026-10-31, branch parity/p4-02-hdr-tonemap)
- Gap found: both the slang (
pbr_shade.slang) and GLSL (pbr_shade.frag) shade passes fetched the SceneMaterial record but never read row 3 (emissive) — HDR values > 1 could never reach the R16G16B16A16_SFLOAT target. Fix:acc + emissivein the GLSL frag, blob regen'd withglslangValidator -V --target-env vulkan1.3(3190 -> 3238 words, magic 07230203 ver 00010600) viaembed_spv.py+ mirrored intoinclude/engine/renderer/shaders/. Slang source left untouched (P4-06 frame wiring owns its replacement; slangc-2026.20 blobs are lavapipe-unconsumable per P4-01). - New:
native/renderer/include/engine/renderer/hdr_camera.hpp(HdrExposureFromEv=std::exp2,HdrCameraComponent16B blittable,HdrTonemapCpumirror of post_composite.frag). Post pass already didhdr * exposure -> ACES -> sRGB, so no post change; the camera EV rides PostPush.exposure. - Math check: ACES(0.5) ~ 0.616 -> sRGB ~ 0.807 (S-curve lifts mid-tones; the first
hdr: grey mid-rangebound of 0.3-0.7 was wrong, fixed to 0.79-0.82 after a Python spot-check against the Narkowicz fit). - New test
engine_hdr_tonemap(test_hdr_tonemap.cpp + sharedgolden_vk_helper.hpploader/alloc/image/barrier/submit helpers so the third Vulkan TU does not trip Sonar duplication): left half emissive (10,2,1), right half diffuse grey under a zero-intensity light (emissive-only probe). EV-3 post. Acceptance: goldenp4_02_hdr_320x240.png+no white clip(r,g,b < 250) +emissive survives(r > 150) + GPU-vs-HdrTonemapCpu within 4/255. - Golden size is 3935 bytes (flat halves compress well — expected, not a blank image; the numeric probes prove content).
- Evidence:
ctest --preset ci-linux45/45 pass withSDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1(44 old + new hdr_tonemap). - HDR scene-target contract (2026-10-31, commits
cb6a744device helper +58380e1constants): shade writes and post samples the SAME float target via engine constantskHdrSceneFormat(= VK_FORMAT_R16G16B16A16_SFLOAT, TU-asserted)kHdrSceneUsage(COLOR_ATTACHMENT | SAMPLED, TU-asserted) inpbr_pipeline.hpp(stub stand-ins incl. value 91 in render_graph.hpp); test_hdr_tonemap.cpp consumes both constants; swapchain stays R8G8B8A8_UNORM LDR. Re-verified 2026-10-31:engine_hdr_tonemapall PASS (offscreen + ENGINE_REQUIRE_GPU=1) incl. golden p4_02_hdr_320x240, no-clip, CPU/GPU parity; fullctest --preset ci-linux45/45 green.
- Test hygiene (matches P1-05/P1-01 contract): golden via
golden_compare.hpp(ch±2, 0.1% pixels, RGBA with BGRA-swizzle handling); GPU tests usedevice_hint="llvmpipe"throughGoldenOpenDeviceand report [FAIL] not [SKIP] under ENGINE_REQUIRE_GPU=1 via thegpu_missingguard; no window is created (pure offscreen render, CI runs headless). - Open gates (need human): (1) Sonar duplication gate red — golden_vk_helper.hpp mirrors test_pbr_golden.cpp scaffolding until that P4-01-owned TU migrates onto the helper (flagged hotspot; P4-03 will also touch that file, needs orchestrator sequencing); (2) manual real-GPU check — this box is lavapipe only, no discrete GPU available.
- P4-02 Sonar dedup + real-GPU update (2026-10-31, branch parity/p4-02-hdr-tonemap):
test_pbr_golden.cpp + test_pbr_materials.cpp migrated onto golden_vk_helper.hpp
(GoldenVkLoad/AllocHost/MakeImage/Module/Barrier/SubmitWait/BuildShadePost/
AllocWriteFrameSets/RecordFrame + shared gpu_missing + scene/grid/material/
frame/readback/pixel-map helpers); net -672/+193 lines on the P4-01 TU.
Sonar duplication gate GREEN (0.0%). RADV descriptor-pool fix: set-0 bindless
4096 sampled-image array costs 2x pool on RADV (pool 8192/32; lavapipe accepts
oversize). Real-GPU evidence:
VK_ICD_FILENAMES=radeon_icd.json ENGINE_REQUIRE_GPU=1 engine_hdr_tonemapALL PASS on AMD Radeon Graphics (RADV GFX1151) incl. golden p4_02_hdr_320x240 + no-white-clip + CPU parity. (P4-01 p4_01_spheres golden diverges on RADV — pre-existing shade behavior delta, llvmpipe golden unchanged ALL PASS; P4-01 TU untouched by this slice beyond helper migration.) ctest 45/45 green (llvmpipe). - P4-02 remaining gate (needs human): Sonar maintainability B from ONE stuck S3776 on test_hdr_tonemap.cpp (complexity 32 vs 25, key AaGRAA3dXbG6mhdFCEMz). The anchor (:264) has pointed at 3+ different constructs across 10 extraction pushes without the count ever moving — stale/cached issue, not live code. test_gpu is 7 ifs + helpers; S5817 const notes fixed along the way. Please confirm in the Sonar UI or admin-close the stuck issue.
PARITY P1-06 First triangle through the forward renderer (P1-06, 2026-10-31)
- New:
native/renderer/include/engine/renderer/forward_renderer.hpp+src/forward_renderer.cpp(ForwardRenderer::create(VulkanDevice&, color, depth=UNDEFINED, vert/frag blob overrides) + record_triangle(cmd, view, extent); dynamic rendering, no vertex input, dynamic viewport/scissor, no culling/depth/blend; SDK-free stubs; destroy() safe after device shutdown).creatematches the spec slice signaturecreate(VulkanDevice&, VkFormat color, VkFormat depth)— depth is fail-closed today (only UNDEFINED; D32_SFLOAT arrives in P1-07), covered by a dedicated negative gate. - Shaders:
native/renderer/shaders/tri.vert(constant position/color arrays indexed by gl_VertexIndex: top red, bottom-left green, bottom-right blue) +tri.frag(passthrough), compiled offlineglslangValidator -V --target-env vulkan1.3(vert 360 words, frag 124 words) and embedded viaembed_spv.py. Verified: re-embedding the freshly compiled .spv produces WORDS_IDENTICAL arrays to the checked-in.inc. Checked in at bothnative/renderer/shaders/tri_*_spv.inc(spec §5 path) andnative/renderer/include/engine/renderer/shaders/tri_*_spv.inc(renderer include path used by the TU); both files are byte-identical. - Test:
native/tests/test_forward_triangle.cpp(engine_forward_triangle, offscreen 320x240) — bad format / bad blob / depth-format / double-build / null-record negative gates, one frame via record_triangle + capture,golden_check("triangle_320x240")folded into failures; golden checked in atnative/tests/golden/triangle_320x240.png(llvmpipe-rendered RGB triangle on black). - Verified:
SDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 engine_forward_triangleprints[PASS] golden: triangle_320x240+FORWARD_TRIANGLE_OK, exit 0; full suiteSDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 ctest --preset ci-linux -j444/44 PASS.
PARITY P1-07 Mesh, camera, depth and one light (P1-07, 2026-10-31)
- New:
native/renderer/include/engine/renderer/gpu_mesh.hpp+src/gpu_mesh.cpp(GpuMesh: 32-byte MeshVertex pos/normal/uv + u32 indices via EngineVmaCreateMeshBuffer host-visible upload; DepthBuffer: D32_SFLOAT-only image + view),vma_bridge.hpp/vma_impl.cppgains (MeshBufferBundle/DepthImageBundle, CreateMeshBuffer/CreateDepthImage/DestroyBuffer/DestroyImage), ForwardRenderer gainscreate_mesh(D32_SFLOAT-only, 160-byte push layout, depth LESS, no cull) +record_mesh+begin/continue/end_mesh_pass(CLEAR then LOAD). - Shaders:
native/renderer/shaders/mesh.vert(MVP transform, world normal) +mesh.frag(Lambert max(dot(n,-light_dir),0)*0.9+0.1, sRGB encode), compiled offlineglslangValidator -V --target-env vulkan1.3and embedded viaembed_spv.py(checked in at both shader paths, byte-identical). - Test:
native/tests/test_forward_mesh.cpp(engine_forward_mesh, offscreen 320x240) — negative gates (record/pass before build, non-D32 rejected, bad blob, zero extent, null upload), camera eye(2,2,3)/60deg/Vulkan clip space, unit cube 24v/36i, back cube offset along view ray in LOAD pass;golden_check("cube_lit_320x240")+ memcmp depth proof. - Surprises (both mine, in test math not renderer code): (1) perspective matrix had
m[10]=far/(far-near), m[11]=+1which puts w<0 for all forward-z and clips everything — golden came out solid black. Correct Vulkan form ism[10]=far/(near-far), m[11]=-1(verified all 8 cube corners inside clip). (2) light_dir sign: shader uses dot(n,-light_dir) so light_dir is the direction light TRAVELS; my initial (0.5,0.9,0.65)-normalized value lit only -X/-Y/-Z faces (all camera-facing faces ambient-only, flat brown cube). Negated so +X/+Y/+Z faces shade 0.47/0.77/0.58. - Verified:
SDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 engine_forward_meshprints[PASS] golden: cube_lit_320x240+[PASS] mesh: depth test hides back faces+FORWARD_MESH_OK, exit 0; golden visually confirmed (tan cube, 3 shaded faces, black bg); full suiteSDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 ctest --preset ci-linux -j445/45 PASS.
PARITY P1-08 Draw ECS entities (P1-08, 2026-10-31)
- New:
native/ecs/include/engine/ecs/ecs_render_components.hpp+src/ecs_render_components.cpp(Camera 16B / MeshRenderer 20B / DirectionalLight 16B, align 4, static_asserts;RegisterRenderComponentidempotent lookup-or-init with sep="" flat names + layout-mismatch 0 instead of Flecs abort).EcsWorld::register_render()registers all three underEngine.Runtime.*.GenericSystems::register_componentnow delegates to the same function (single implementation, gotcha 20 can't drift). - New:
native/renderer/include/engine/renderer/mesh_registry.hpp+src/mesh_registry.cpp(mesh_id = index+1, 0 reserved; find OOB/empty -> nullptr; noexcept, no try/catch — renderer OBJECT lib is -fno-exceptions). ForwardRenderer gainsrender_world(void* world, registry, cmd, out_view, out_stats)in newsrc/ecs_render.cpp(void* so the header stays flecs-free; stub in forward_renderer.cpp for SDK-less builds). Queries (WorldTransform, MeshRenderer), first is_main Camera + first DirectionalLight, per-draw record_mesh with memcpy model (NOT transposed) + view_proj. - Test:
native/tests/test_ecs_render.cpp(engine_ecs_render, offscreen 640x360) — shared cube 24v/36i, 10x10x10 grid at 2-unit spacing (1000 entities), camera eye (27,27,44) -> centre (9,9,9), far 200;golden_check("grid_1000_640x360")+ draws==1000 + move-entity-+3X/propagate memcmp-differ proof. - Surprises (mine, worth recording): (1)
engine::renderer::ecs_world_tvs global::ecs_world_t— the header'sstruct ecs_world_t;inside namespace engine::renderer declares a DIFFERENT incomplete type, so every flecs call failed overload resolution (cannot convert argument of incomplete type). Fix: render_world takesvoid*(header flecs-free, like audio_world.hpp), .cpp casts to the real::ecs_world_t*after including flecs.h. (2) Transposing WorldTransform for the model matrix is WRONG: ComposeLocal writes translation at [12..14] which is already the column-vector translation column; a transpose moves it into the w-row (w'=xpx+ypy+z*pz+1) and collapses all 1000 cubes to sub-pixel mush behind a fullscreen flat tan field (first golden). Fix: memcpy flat bytes. Camera view DOES need the rigid inverse (RowWorldToColView) — eye (9,9,22) was also inside the grid (spans z 0..18), pulled back to (27,27,44). (3) volk-first include order applies to ecs_render.cpp too (volk.h before flecs.h is fine, but vulkan.h via flecs first would hard-error the same way as vulkan_device.cpp). - Verified:
SDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 engine_ecs_renderprints[PASS] golden: grid_1000_640x360+[PASS] ecs-render: 1000 draws+[PASS] ecs-render: transform change visible+ECS_RENDER_OK, exit 0; golden visually confirmed (tan voxel-cube lattice on black, individual cubelets distinguishable); full suiteSDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 ctest --preset ci-linux -j446/46 PASS. No ABI change (native-only slice; P1-09 adds renderer_create/Rendering.cs), no deps change. - Post-commit fix (same slice, amended): flaky SIGSEGV in
engine_ecs_renderunder ctest/redirected stdout —ColMat(float[16], align 4) spilled/passed by value under renderer AVX2 flags emittedvmovaps; an under-aligned stack slot faulted intermittently (test TUs use base flags, so P1-07 math never tripped it). Fix:struct alignas(32) ColMat+ byte-loop copies for the eyeWorldTransform(ecs_get_idstorage) and the per-draw model matrix (Flecs user-component columns align only to the component's own alignment, not thealignas(64)declaration — never assume 64B). Byte loops compile tovmovups, zero cost next to a draw. Verified: 20/20 redirected offscreen runs green post-fix (was intermittent crash before).
PARITY P1-09 Rendering from C# (P1-09, 2026-10-31)
- ABI 4.4: appended renderer tail (
renderer_create/destroy/frame/mesh_create/screenshot) afterinput_get_state+ minor bump 4.3->4.4, regen viatools/abi_codegen, layout pins (EngineApiTable368->408B, renderer_create @368, screenshot @400) in bothtest_abi_layoutandAbiLayoutTests. - Native:
native/bridge/src/renderer_bridge.cpp(EngineRenderer owns SDL3 window + Vulkan device + swapchain + ForwardRenderer mesh pipeline + D32 depth + MeshRegistry;RenderOnemirrors the P1-08 test frame loop: barrier, begin_mesh_pass, render_world with swapchain aspect, end pass, present, capture_rgba8 for screenshot; skips draw when no is_main camera).render_worldgains anaspectparam (default 16:9 so P1-08 goldens don't move). - Managed:
managed/Engine.Runtime/Rendering.cs(Renderer IDisposable over the ABI 4.4 tail; Camera 16B / MeshRenderer 20B / DirectionalLight 16B inEngine.Runtimematching native layouts exactly) +samples/HelloCube(camera + light + rotating cube driven by a generated 2-termSpinner.Tick(ref Spinner, ref LocalTransform, dt)system +world_propagateper frame). - THE gotcha (worth recording): the HelloCube
LocalTransform/WorldTransformC# mirrors MUST live in the GLOBAL namespace soFullNameis exactlyLocalTransform/WorldTransform. AHelloCube.LocalTransformmirror forks a SECOND Flecs component (name-exact lookup):entity_setsucceeds (size matches) butworld_propagate's cascade query watches the real built-in id, so World rows stay zero, the draw query matches nothing (draws=0, black frames) while every sample check still prints PASS. Symptoms:ComponentId<WorldTransform>returns a fresh id on every new world instead of the built-in; entity_get reads zeros after propagate; renderer draws=0. Fix pins: global-namespace mirror structs with a singleVector512<float>field (size 64 + align 64 via the 64B vector member),Ecs.Setorder World-then-Local irrelevant, component ids resolved AFTERrenderer_create(register_render runs there idempotently) so the first bind doesn't fork live low ids. - CTest:
engine_hello_cube(offscreen,--frames 10 --screenshot <build>/hello_cube.png, PASS on HELLO-CUBE-OK) +engine_hello_cube_compare(loads that PNG,golden_check("hello_cube_10f_640x360"), P1-05 tolerance; HELLO_CUBE_PNG env;hello_cube_screenshotfixture chains them). Goldennative/tests/golden/hello_cube_10f_640x360.png(640x360, llvmpipe: tan square-cube face centered on black — camera looks straight down -Z at the cube so one face fills the frame; 248 nonblack px). - Verified:
engine_hello_cube+engine_hello_cube_comparePASS;engine_abi_layoutPASS;AbiLayoutTests"all checks passed";abi_codegen: 49 entries.
PARITY P1-10 Real engine-cli snapshot capture (P1-10, 2026-10-31)
CliDispatchergains a settableLiveRenderer(null = headless stub, unchanged envelope: out/pass/0x0/true). When set,snapshot.captureframes once (current scene, not a stale present) then callsrenderer_screenshotto the requested path and returns the renderer's real extent withHeadlessStub: false. Native failures throw InvalidOperationException -> InternalError (-32603), never a silent stub.Rendererrecords its create-time extent (Width/Heightprops) so the dispatcher reports the window size without new ABI.EditorSessionkeeps its borrowedEngineWorld*(publicWorld) so a host/test can attach a renderer to the same world.CliServerexposes itsDispatcherfor the same reason. No ABI change (uses the P1-09 4.4 tail as-is), no deps change.- Acceptance:
CliApiTests.RunRealSnapshot— own world + hidden offscreenRenderer(320x240) + seeded lit cube scene (camera eye (0,0,3), dir light, unit cube via global-namespaceLocalTransformmirror +world_propagate; newmanaged/Engine.Tests/TransformMirrors.cs, same global-namespace rule as HelloCube/Components.cs) + ownCliServerwithLiveRendererset, then the REAL engine-cli binarysnapshot capture --out <tmp>/s.png --pass finalasserts headlessStub=false, width/height == 320x240, PNG magic present. GPU-gated: renderer_create failure skips cleanly, fails closed under ENGINE_REQUIRE_GPU=1. - Gotcha (mine):
MeshRenderer.BaseColor/DirectionalLight.Directionare C# fixed buffers — write viamr.BaseColor[i]directly. My first draft cast&mrtofloat*and wrote through it, which AV'd (fixed-buffer pinning/aliasing); direct indexing is the HelloCube pattern and works. - Verified:
SDL_VIDEO_DRIVER=offscreen ENGINE_REQUIRE_GPU=1 ctest --preset ci-linux48/48 PASS includingengine_compositor_live([PASS] real-snapshot: engine-cli snapshot real PNG 320x240, file exists,[LIVE] real-snapshot: ... 3254 bytes, 320x240, headlessStub=false).