diff --git a/docs/engine-re.md b/docs/engine-re.md index 5385693..503470e 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -169,6 +169,17 @@ and pinning the *real* handler via the dispatch table above corrects two layers call-script / script-load dispatch (`name-resolution.md §1`), the next target for this loop (now armed with the dispatch table to resolve the call-script handler directly). +**Port audit consequence (2026-07-20): `0x1a2` is not faithfully implemented.** The VM still dispatches +the opcode through its legacy `gfx-cmd-register` label to `GfxState.Register(Read(a[0]))`. That method adds +only the operand's current numeric value to `_operandRegistry`, a `HashSet`; it does not retain the +lvalue descriptor key used by AGE. `IsRegistered` has no runtime callers, so the set currently has no +gameplay or rendering effect (and its removal during gfx-object erase is likewise inert). Therefore this +mistake does **not** invalidate the completed retained-object, animation, History, or ADV work, whose +semantics came from independent workers/fields/traces. It does mean the opcode table's syntactic `impl` +classification overstates compatibility for `0x1a2`: keep it as explicit follow-up debt until the native +descriptor table's consumer is identified, then move the model out of `GfxState` and preserve descriptor→value +semantics in the appropriate VM/service subsystem. + **Lesson:** never analyze a native op by its Kelebek `u00XXXXXX` VA directly — always resolve the real handler through the dispatch table (`ctx[0x26c93 + op]`). The raw VA is off by whole functions. diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index 261baa9..1c81314 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -293,6 +293,8 @@ Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_t - **grounding:** source=investigation, confidence=high - **evidence:** Ghidra: handler 0x42d360 fetches operand 1's value and lvalue descriptor separately, formats the descriptor key, then calls FUN_0042cf70. By contrast op 0x215 passes ctx+0x46614 to gfx_object_query_source_slot@0x47f280, which searches the retained object map and returns obj+4. +Port status (2026-07-20): not faithfully implemented. The legacy gfx-cmd-register dispatch currently adds only the operand's numeric value to an otherwise runtime-unread HashSet in GfxState; it neither preserves the native descriptor key nor models a known consumer. Do not treat its presence in the VM switch or coverage's impl bucket as semantic completion. The native table's consumer must be identified before choosing the correct host subsystem. + ### 0x1ad `mark-save-resume-frame` (mark-save-resume-frame, argc 0) - **summary:** Mark the current script context as the highest frame serialized by numbered-save layouts 2/3. The native serializer saves frames 0 through this boundary and strips the boundary frame's return target so loading resumes it as the top frame. This opcode performs no file I/O itself. - **grounding:** source=investigation, confidence=high diff --git a/docs/phase-a-slice-plan.md b/docs/phase-a-slice-plan.md index a2a2689..39e871b 100644 --- a/docs/phase-a-slice-plan.md +++ b/docs/phase-a-slice-plan.md @@ -293,9 +293,11 @@ script-context records for gfx objects — was wrong: it observed the wrong stru **Resolution had TWO halves** (canonical decode in `docs/engine-re.md`, op `0x215` + "The render drift's SECOND half"; don't duplicate here): -1. **Native gfx ops (b):** all 14 command-buffer ops (`0x1a2`,`0x1f7`,`0x1fa`,`0x1ff`,`0x202`,`0x203`, - `0x212`,`0x213`,`0x215`–`0x21a`) reversed + implemented against a host-side `GfxState` (VM execution - state; `engine/Age.Engine/Model/GfxState.cs`). `0x215` now returns distinct per-object slots. +1. **Native gfx ops (b):** the retained-object command-buffer ops (`0x1f7`,`0x1fa`,`0x1ff`,`0x202`,`0x203`, + `0x212`,`0x213`,`0x215`–`0x21a`) were reversed + implemented against a host-side `GfxState` (VM execution + state; `engine/Age.Engine/Model/GfxState.cs`). `0x215` now returns distinct per-object slots. **Correction + (2026-07-20):** `0x1a2` never belonged to this family; its current inert `GfxState` approximation is not a + faithful implementation. See `docs/engine-re.md` § op `0x1a2` for the compatibility-debt audit. 2. **Missing system-boot state (a):** the CG handle array `G[0x62455..]` is set by boot script **INIT2** (via entrypoint `SYSTEM4.BIN`), which a cold single-scene run skips → all CGs collapsed onto object 0. Supplied via **`Age.Cli gfx --boot`** and **Godot `--boot`** (run `INITCONFIG/INIT2/INIT` through @@ -2004,3 +2006,13 @@ largest repeated non-persistence cluster still unclassified. Validation: all 206 engine tests pass; opcode and EngineCtx tests/lints, vm0 RECOVER, and `git diff --check` are clean. SC0000 coverage deliberately remains 119/129 distinct opcodes handled (92.2%), with `0x1ad`'s six calls retained as a gap until numbered saves serialize active execution frames. + +**Follow-up audit — `0x1a2` compatibility status (2026-07-20).** Correcting the shared instruction-length +field does not invalidate any completed visible graphics/animation/UI behavior: every affected native opcode +has independent worker/field/trace evidence, and the port does not emulate `ctx+0x53d88`. The exception is +`0x1a2`, whose legacy VM case stores only a numeric value in an unread `GfxState` set instead of AGE's +lvalue-descriptor→value entry. It has no present runtime consumer, so this is latent compatibility debt rather +than a current visual regression. Coverage currently counts the case as `impl`; do not interpret that bucket +as proof of semantic completion for this opcode. Canonical details and the eventual investigation target live +in `docs/engine-re.md` and `vm-map/opcodes.toml`. Documentation validation: regenerated opcode references, +opcode build/lint and tests, and `git diff --check` are clean. diff --git a/docs/scjump-progression.md b/docs/scjump-progression.md index 9820e27..55b9b82 100644 --- a/docs/scjump-progression.md +++ b/docs/scjump-progression.md @@ -47,7 +47,8 @@ separate open-addressing table. Its write of 3 at `ctx+0x53d88` is the instructi command type. So that snippet is a **value-registration operation, not the decision→scene dispatch**. The real decision→scene mechanism is **still unidentified** and belongs with the call-script / script-load dispatch (`name-resolution.md §1`). See `docs/engine-re.md` for the -verified handler analysis and the opcode-dispatch table that will crack call-script next. +verified handler analysis, including the explicit note that the port's legacy inert `GfxState` approximation +is not a faithful implementation, and the opcode-dispatch table that will crack call-script next. ## See also - `vm-map/globals.toml` — the named globals SCJUMP switches on (chapter_mode, progress counters, flags). diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index 0083216..e330493 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -3467,6 +3467,7 @@ abi_source = "kelebek+decode-validated" name = "register-lvalue-value" category = "control" summary = "0x1a2 (value) — register operand 1's current value under a key derived from its lvalue descriptor in the open-addressing table at ctx+0x5190. The write of 3 at ctx+0x53d88 is only this instruction's encoded dword length, not a graphics command type. This structure is separate from op 0x215's retained gfx-object map; op 0x215 does not query it. NOT save/scene." +details = "Port status (2026-07-20): not faithfully implemented. The legacy gfx-cmd-register dispatch currently adds only the operand's numeric value to an otherwise runtime-unread HashSet in GfxState; it neither preserves the native descriptor key nor models a known consumer. Do not treat its presence in the VM switch or coverage's impl bucket as semantic completion. The native table's consumer must be identified before choosing the correct host subsystem." noop_headless = false source = "investigation" confidence = "high"