Complete native numbered-save round trips

This commit is contained in:
gamer147
2026-07-24 23:25:54 -04:00
parent d099a073a8
commit b8bbd5c1eb
17 changed files with 907 additions and 89 deletions

View File

@@ -1754,8 +1754,9 @@ live surfaces and retained objects, and applies the 20-byte surface records' exp
load first replaces each serialized mutable bank prefix while preserving initialization-authored cells
beyond its count, restores history/gfx and retained audio, unwinds the obsolete managed call chain, runs
`CALLBACK_LOAD.BIN` when
the mounted script provider resolves it, starts the saved root at its `0xae` rendezvous, recursively
reconstructs child frames, resumes parents after their saved T2 call sites,
the mounted script provider resolves it, loads each saved script at its ordinary entry so its frame-local
prologue runs, lets that script reach its own `0xae` rendezvous, recursively reconstructs child frames,
resumes parents after their saved T2 call sites,
and finally resumes the terminal frame at its T1 boundary. Successful `0x19e` also flushes shared
`SAVE.DAT`/`RT.DAT`, matching `context_state_serialize`.
@@ -1791,6 +1792,11 @@ separately gated by both `set:CreateObject` and `set:AutoFreeTex`; the registere
path. Opcode `0x259` is also a real lifecycle operation:
`op_0x259_script_entry_clear_surface_persistence_flags@0x417660` clears record `+0x08` and `+0x0c`
across both native tables at every script entry.
Opcode `0x258` then declares the exceptions:
`op_0x258_set_surface_persistence_flags@0x4250a0` calls
`gfx_surface_set_persistence_flags@0x4159c0`, which replaces record `+0x08` from flags bit 0 and
record `+0x0c` from bit 1 in both tables. The port models bit 0 because layout-3 restoration consumes
that reload policy; the adjacent bit-1 consumer remains unnamed.
Installed slot 15 records resource `0x3383` with reload flag zero. SYSTEM4 loads that atlas before the
save UI, and BUNKI cuts its reusable choice-box corners, borders, and winged top ornament from it. The
port formerly released all 1,000 host surfaces unconditionally, so post-load BUNKI retained its black
@@ -1800,6 +1806,67 @@ native all-release branch. Port-authored saves emit the exact resource/reload/cr
`-1` for unused records, and opcode `0x259` clears the modeled reload policy without releasing textures.
The corrected native functions are renamed/commented in the saved `/v2` image.
#### Port-authored round trips: restored frame boundary and retained-object encoding (2026-07-24)
An immediate `installed SAVE00 -> port SAVE03` round trip isolated two independent writer defects.
The source file contains two frames (`SYSTEM4.BIN -> FORT.BIN`), while the first port rewrite contained
four: the same gameplay pair plus `SAVE.BIN` and its nested helper. Native opcode `0x1ad` selects the
terminal gameplay frame before opening modal helpers. Full restore through `0xae` reinstates that saved
terminal context as the effective boundary. The managed restore cleared its temporary restore state but
did not restore `_saveResumeFrame`, so the next `0x19e` fell back to the currently deepest frame. The port
now marks the terminal restored `ExecFrame` before resuming at T1; a regression loads two frames, saves
from a nested helper, and proves the rewrite still contains only the original two.
The first fresh rewrite (`SAVE04`) then exposed the other half of that managed/native representation gap.
It had two frames, but its restored SYSTEM4 ancestor changed from native `resume=8, call=8` to
`resume=-1, call=-1`. Native `op_0xae_continue_save_load_stack_restore@0x416790` restores the parent
context's real T1/T2/T3 coordinates before activating its child. The managed port instead leaves the
parent physically parked at the synthetic `0xae` rendezvous while `RunFrame` recursively runs FORT.
Recomputing a save record from that synthetic PC cannot find a T1 or T2 table entry. FORT itself loads
and runs, but when stage launch unwinds through SYSTEM4, the missing T2 continuation enters ordinary
Eushully boot.
Each restored `ExecFrame` now shadows its original `NativeSavedScriptFrame` while a restored descendant
is active. An intervening `0x19e` serializes those original T1/T2/T3 indices; T3 also reconstructs the
live local-return stack, and the saved T1 coordinate seeds the frame's read-message state. The shadow is
discarded only when that child genuinely returns and the parent resumes at its restored T2 continuation.
Restored root-reload and process-exit outcomes also propagate through the synthetic recursion. A focused
load -> nested save -> reload regression proves the parent indices survive and the child is not invoked
again through ordinary `CallScript`.
Manual slot 005 acceptance then proved that an immediate base rewrite could load and later launch a
stage. Slot 006, authored immediately after entering that stage, still restored a black dungeon with
visible but noninteractive UI. Its serialized FIELD range, 704 retained objects, map textures, and
native-looking 0.8 transform were intact. A software replay reproduced the black result because FIELD
had changed every restored map object's scale to `(0,0,1)`: global zoom selector `G[0x7682]` was 3, but
the frame-local lookup table that should yield 80 was still zero.
Ghidra resolves the ordering precisely. `script_frame_restore_saved_layout@0x40f2d0` calls
`script_frame_load_resource@0x40e980`; that loader allocates and zeroes locals, loads the script body,
and initializes its PC to the script codebase, not to `0xae`. The restored script therefore executes its
ordinary entry prologue before reaching the rendezvous. FIELD's prologue constructs the inline zoom
table `[40, 50, 64, 80, 100, 124, 156]`; the port's direct jump to `0xae` skipped those copies and let
FIELD apply zero scale after the gfx record restore.
Restored managed frames now likewise begin at script offset zero while retaining their saved T1/T2/T3
coordinates for `0xae`. Synthetic root and child regressions prove both prologues execute. Replaying the
unchanged real slot 006 now selects zoom 80, leaves the map at 0.8 scale, produces a nonblack dungeon
raster, and reaches the gameplay poll. No slot rewrite is required for this correction.
The same comparison exposed native retained-gfx pointer arithmetic. `context_state_serialize@0x40d320`
writes a handle and copies the meaningful `0xb5` DWORD / `0x2d4`-byte record, then advances its DWORD
cursor by `0x2d5`; `save_data_deserialize_and_begin_restore@0x40fd10` mirrors that stride. Early port
output packed entries contiguously. In addition, `gfx_object_init_default@0x472810` seeds six identity
matrices and three `0xffffffff` color defaults before the loader overwrites the complete record; the old
writer began with zero bytes and only filled modeled fields. It also wrote translation vectors over the
first row of the native 4x4 matrices instead of entries 12..14.
`NativeNumberedSaveCodec` now emits the native `0xb54`-byte entry stride and recognizes the old tight
experimental layout for compatibility. `NativeGfxPersistenceCodec` begins new records from the exact
native defaults, writes row-vector translation matrices correctly, and retains the original raw record
behind the semantic object so still-unnamed fields survive native load/save cycles. The `/v2` serializer
and deserializer comments record the corrected stride and overwrite behavior.
### Opcode `0xae` continues numbered-save stack restoration (2026-07-20)
Opcode `0xae` is the load-side rendezvous paired with serialized script-frame state. Its handler,
@@ -1816,6 +1883,12 @@ T1/T2/T3 indices back into live PC/call/return offsets. At the saved terminal co
the restore flag and reinstates the saved context/return state. The corpus placement supports that control-flow role: 305 calls overwhelmingly follow
coroutine-resume or call boundaries, including SC0000's main-loop resume sequence.
The load order is significant: `script_frame_restore_saved_layout` delegates script creation to
`script_frame_load_resource@0x40e980`, which initializes the new frame PC to the script codebase.
Consequently the script runs its normal frame-local initialization and reaches `0xae` itself. Starting
a restored frame directly at `0xae` is not native behavior; in FIELD it leaves the local zoom table
zeroed and collapses the restored dungeon to a black point.
The port now implements both branches: an ordinary one-instruction no-op outside restoration, and the
T1/T2/T3-driven managed-frame reconstruction described above while a full numbered load is active.
@@ -2534,7 +2607,24 @@ backbuffer zero instead and record current target `-1` at manager `+0xb530` (`En
`0x20e` then calls `d3d_clear_render_target_black@0x471460`, which invokes `IDirect3DDevice9::Clear` with no
rectangles, `D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER`, color zero, depth 1.0, and stencil zero. The port tracks
the selected target and forwards the clear to the host; the retained compositor reconstructs its main
backbuffer from black at publication boundaries, while offscreen clears also discard modeled text pixels.
backbuffer from black at publication boundaries, while offscreen clears discard both modeled pixels and
text draws.
Opcode `0x222` publishes its retained handle range into whichever target `0x20d` selected; it is not
merely an onscreen repaint request. SAVE.BIN provides a compact end-to-end example. It creates slot 2 at
800x600, selects and clears it, then publishes handles `[0,0x130b0)` to capture the underlying gameplay
without the save-menu objects. It next creates/selects/clears slot 192 at 112x84, binds slot 2 through
handle `0x15f90`, scales that object to 14%, publishes the two-handle range, and passes slot 192 to
`0x1ae`. Dungeon scripts use the same selected-target publication to construct map surfaces.
The port originally retained only an object-list snapshot for transitions and requested an ordinary
backbuffer repaint from `PresentObjectRange`; the selected surface's pixels remained its cleared black
allocation. That produced completely black port-authored `.STH` files and caused restored dungeon map
surfaces to turn black when their rebuild published offscreen. Godot now uses the platform-neutral
software affine compositor for selected-target `0x222`/`0x20c` publication, including source surfaces,
scaling, transforms, tint/opacity/blend, handle-range filtering, and real pixel clears. Backbuffer
publication remains on the existing renderer. `gfx_present_object_range@0x482230` records the refined
target ownership in the saved `/v2` image.
This trace also corrects an important base-pointer assumption in earlier graphics notes. Offsets `+0x408`
and `+0xb550` are relative to the retained-gfx manager at `EngineCtx+0x46614`, not to `EngineCtx` itself.

View File

@@ -405,9 +405,9 @@ Implemented as a whole-stack root-reload boundary in the persistent VM. A reques
### 0xae `continue-save-load-stack-restore` (continue-save-load-stack-restore, argc 0)
- **summary:** () - during serialized save restoration, replace the current frame PC with its saved resume/call target and advance through the saved script-context stack; otherwise a no-op.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0xae_continue_save_load_stack_restore@0x416790 first tests ctx+0x53d24 (set by save_data_deserialize_and_begin_restore@0x40fd10). When clear it returns. When set, it selects the serialized frame layout through set:SaveVersion1/2, restores the current PC from that layout's saved return/call target, advances through contexts with script_frame_restore_saved_layout@0x40f2d0, and clears the restore flag on reaching the saved terminal context. Its 305 corpus sites overwhelmingly follow coroutine-resume/call boundaries, which provide the rendezvous points used while reconstructing the stack.
- **evidence:** Ghidra /v2: op_0xae_continue_save_load_stack_restore@0x416790 first tests ctx+0x53d24 (set by save_data_deserialize_and_begin_restore@0x40fd10). When clear it returns. When set, it selects the serialized frame layout through set:SaveVersion1/2, restores the current PC from that layout's saved return/call target, advances through contexts with script_frame_restore_saved_layout@0x40f2d0, and clears the restore flag on reaching the saved terminal context. The restore helper calls script_frame_load_resource@0x40e980, which initializes each new frame PC to its script codebase; the script therefore executes its ordinary prologue before reaching 0xae. Native parent contexts retain restored coordinates while the child runs. SAVE00 -> SAVE03 proved the port must retain the terminal 0x1ad boundary; SAVE00 -> SAVE04 proved a managed parent parked at synthetic 0xae must serialize original resume=8/call=8 rather than synthetic -1/-1; slot 006 proved direct entry at 0xae skips FIELD's zoom-table prologue and collapses the dungeon map. Its 305 corpus sites overwhelmingly follow coroutine-resume/call boundaries.
Layout 3 frame d259 indexes SYS4 T1 read-message reset sites, d260 indexes T2 call-script sites, and the saved local return stack indexes T3 local-call sites. Port status (2026-07-24): the active path reconstructs the saved recursive frame chain and resumes the terminal frame at its T1 boundary. The installed SAVE00 continuation gate proves SYSTEM4 -> FORT restoration reaches FORT's CHMENU gameplay poll; the synthetic gate asserts the child frame enters with SaveRestore rather than ordinary CallScript cause.
Layout 3 frame d259 indexes SYS4 T1 read-message reset sites, d260 indexes T2 call-script sites, and the saved local return stack indexes T3 local-call sites. Port status (2026-07-24): each saved script is loaded at its ordinary entry, runs its frame-local prologue, and reaches 0xae itself; the active path then reconstructs the saved recursive frame chain, preserves each synthetic ancestor's original T1/T2/T3 coordinates while its restored child is active, reinstates the terminal restored frame as opcode 0x1ad's save boundary, and resumes it at T1. The installed SAVE00 continuation gate proves SYSTEM4 -> FORT restoration reaches FORT's CHMENU gameplay poll; the unchanged port-authored dungeon slot 006 proves FIELD's pre-rendezvous zoom table is initialized; synthetic gates assert both root/child prologue execution, SaveRestore rather than ordinary CallScript entry, exclusion of nested SAVE helpers from a re-save, and reload of that rewrite without ordinary boot re-entry.
### 0xc8 `sleep` (sleep, argc 1)
- **summary:** Pause the current script for <duration> milliseconds while retained presentation continues.
@@ -790,9 +790,9 @@ Implemented through IHost.PlayModalMovieToSurface. Its operand uses the same nat
- **evidence:** Ghidra 0x47ecc0 calls matrix builder 0x48afb1 for target obj+0x1ac. Consumer 0x472f00 uses delay obj+0x44, duration obj+0x58, current obj+0x16c, target obj+0x1ac, shared start obj+0x34, and retained-gfx frame time owner+0xb550 (EngineCtx+0x51b64).
### 0x222 `present-gfx-object-range` (present-gfx-object-range, argc 2)
- **summary:** (first_handle)(count) - flush/present retained graphics objects in the selected handle range and clear their pending update flags.
- **summary:** (first_handle)(count) - flush/present retained graphics objects in the selected handle range into the currently selected backbuffer or offscreen render target, then clear their pending update flags.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x222_handler@0x4235e0 calls gfx_present_object_range@0x482230. The worker enters the graphics service, walks the retained-object map, processes flagged objects whose handles fall in [first,first+count), clears pending flags, and finalizes the render batch. HISTORY.BIN uses (0,60000) after rebuilding its retained presentation.
- **evidence:** Ghidra /v2: op_0x222_handler@0x4235e0 calls gfx_present_object_range@0x482230. The worker enters the graphics service, walks the retained-object map, processes flagged objects whose handles fall in [first,first+count), clears pending flags, and finalizes the render batch in the D3D target previously selected by op 0x20d. HISTORY.BIN uses (0,60000) for the backbuffer. SAVE.BIN instead renders [0,0x130b0) into 800x600 slot 2, then handle 0x15f90 at 14% scale into 112x84 slot 192; op 0x1ae writes slot 192 as the numbered .STH thumbnail.
### 0x223 `queue-surface-alpha-transition` (queue-surface-alpha-transition, argc 8)
- **summary:** (command_key)(target_slot)(range_a_start)(range_a_count)(range_b_start)(range_b_count)(delay_ms)(duration_ms) — queue a type-0 timed alpha transition command in the separate ctx+0x414 command map. This is render-target/surface presentation state, not an object affine matrix. The render frame composites the two handle ranges into target_slot and ramps alpha 0->1 after delay over duration.
@@ -924,6 +924,13 @@ The setter get-or-creates the object and writes the complete operand. During ret
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x24e_handler@0x425070 writes operand 1 directly to EngineCtx.gfx_animation_service_flags at +0x51b80. The mapped field is also read by op 0x243: bit 1 suppresses its force-complete/clock-reset request.
### 0x258 `set-surface-persistence-flags` (set-surface-persistence-flags, argc 2)
- **summary:** (surface_slot)(flags) - replace the native surface record's numbered-save persistence flags; bit 0 controls asset reload on restore and bit 1 controls the adjacent still-unnamed field.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x258_set_surface_persistence_flags@0x4250a0 reads the slot and flags operands and calls gfx_surface_set_persistence_flags@0x4159c0. The worker writes flags&1 to record +0x08 and (flags>>1)&1 to +0x0c in both 1,000-record tables. This corrects the old upstream address/name association: 0x422fe0 is opcode 0x20f movie playback, not 0x258. Corpus declaration chains follow opcode 0x259 at script entry.
Scripts place declaration chains immediately after opcode 0x259 clears both fields. The port models bit 0 because it is consumed by layout-3 restoration; bit 1 is retained as an identified native field but has no known runtime consumer yet.
### 0x259 `script-entry` (script-entry, argc 0)
- **summary:** zero-arg script/prologue entry; clears surface-record persistence fields +0x08 and +0x0c across both 1,000-record native tables before the declaration chain continues
- **grounding:** source=investigation, confidence=high
@@ -1150,10 +1157,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting
- **summary:** 1 imm; mov->0x21b->stmt-end; near save/load-messkip — likely line/stmt id, verify not msg-control
- **grounding:** source=harness, confidence=med, noop_headless=True
### 0x258 `decl?` (u00422FE0, argc 2)
- **summary:** 2 imm; runs in a chain right after script-entry 0x259, enumerating ids — prologue declaration/registration?
- **grounding:** source=harness, confidence=low, noop_headless=True
## unknown
### 0x2 `exit` (exit, argc 0)

View File

@@ -3628,6 +3628,111 @@ mapped and implemented as the script-entry clear of record `+0x08`/`+0x0c` (the
Regressions cover native-default preservation, opt-in all-release, flag-for-flag port-authored output,
script-entry clearing, and the installed all-zero reload-bit oracle.
### Persistence implementation step 10 — publish selected offscreen render targets (2026-07-24)
The first port-authored dungeon save exposed two symptoms with one graphics cause: its 112x84 `.STH`
was completely black, and loading it showed the dungeon map for one frame before the map surface became
black while the UI/minimap remained live.
SAVE.BIN's bytecode makes the missing contract explicit. It renders the underlying gameplay handle range
into created 800x600 surface 2, binds that surface to handle `0x15f90`, scales it to 14% into created
112x84 surface 192, then asks opcode `0x1ae` to serialize surface 192. Both render passes use `0x20d`
target selection, `0x20e` clear, and `0x222` range publication. Dungeon map construction uses the same
offscreen path. Godot had modeled selection and black allocation, but `PresentObjectRange` only requested
an onscreen recomposition, so neither target ever received pixels.
Selected-target `0x222` and `0x20c` now publish through a platform-neutral retained-surface rasterizer.
It filters the requested handle range and reproduces affine/range transforms, scaling, tint, opacity,
blend mode, color keys, created/source surfaces, and black clears before storing the target pixels.
Backbuffer publication is unchanged. A focused regression reproduces SAVE.BIN's scaled texture capture
and proves objects outside the range cannot leak into the target. The original black SAVE01 thumbnail is
a preserved diagnostic artifact; a newly overwritten save is required for visual acceptance of the
fixed `.STH`. The subsequent immediate-resave test showed that a separate numbered-state round-trip defect
still prevented the rewritten `.DAT` from resuming interactively.
### Persistence implementation step 11 — repair port-authored numbered-state round trips (2026-07-24)
The clean comparison target was `SAVE03`: load native slot 0 and save immediately, without entering a
dungeon. Loading that rewrite was completely black, proving the remaining failure was not dungeon-map
construction. The source slot has two saved frames; the rewrite had four and incorrectly included the
currently open save-menu/helper frames. Full `0xae` restoration had not re-established the terminal saved
frame as opcode `0x1ad`'s ongoing save cutoff, so the later `0x19e` fell back to the deepest active helper.
The terminal restore now retains its `ExecFrame` as `_saveResumeFrame`. A regression reconstructs a
two-frame save, invokes `0x19e` from a nested helper, and proves the new slot still contains only the two
gameplay frames.
Binary comparison also corrected the retained-gfx writer. Native records are not tightly packed:
each handle plus meaningful `0x2d4`-byte record begins on a `0x2d5`-DWORD / `0xb54`-byte stride. The port
now emits that sparse layout and imports its earlier packed experimental files. New records start from
the exact `gfx_object_init_default` identity-matrix/color state; decoded native records retain an opaque
byte template so unnamed fields survive rewrites; and translation is encoded at row-vector matrix
entries 12..14 rather than over the matrix diagonal. Focused tests cover the native stride, all six
identity matrices, translation placement, opaque-byte preservation, and the restored save boundary.
The `/v2` serializer/deserializer annotations and canonical format notes are updated. The next acceptance
action is to rebuild, load native slot 0, immediately overwrite a fresh slot, and load that fresh rewrite;
older slot 1/2/3 files remain useful compatibility inputs but retain the already-written bad frame chain.
Validation: all 391 engine tests pass; opcode lint/tooling tests are clean; the Godot C# build has zero
warnings; the 211-object SAVE03 compatibility rewrite decodes with the native stride; and threaded
headless execution reports `SELFTEST OK`.
### Persistence implementation step 12 — preserve restored ancestor continuations (2026-07-24)
Fresh slot 004 passed the immediate load gate, but selecting a stage played the Eushully intro. Its frame
count was fixed at two, yet a decoded comparison found the decisive remaining difference:
native slot 000's SYSTEM4 parent stores `resume=8, call=8`, while slot 004 stored `-1,-1`.
Native `0xae` restores each parent context to its real T1/T2/T3 coordinates before activating the next
saved child. The managed reconstruction instead recursively runs FORT while its SYSTEM4 `ExecFrame` is
physically parked at the synthetic `0xae` instruction. Re-save capture derived coordinates from that
synthetic PC, found neither a T1 nor T2 table entry, and wrote `-1`. FORT could initially run, but the
later stage-launch unwind had no SYSTEM4 T2 continuation and fell into ordinary boot.
Restored frames now shadow their original serialized coordinates while a restored descendant remains
active. Re-saves use those T1/T2/T3 values; T3 rebuilds the live local-return stack; T1 initializes the
read-message coordinate; and the shadow clears only when the child genuinely returns to its parent.
Root-reload and process-exit results now propagate through this synthetic recursion as they do through
ordinary `call-script`. The existing two-frame load/re-save regression now asserts the ancestor's resume
and call indices and loads the rewrite again, rejecting any ordinary re-entry into the restored child.
The `0xae` opcode source/reference, engine RE, roadmap, status memory, and `/v2` plate comment record the
refinement. Existing slot 004 retains its already-written `-1,-1`; acceptance requires another fresh
slot created from native slot 000, followed by load and stage launch.
Validation: engine **392/392**, clean opcode lint/tooling, zero-warning Godot build, and threaded
`SELFTEST OK`. The remaining gate is interactive confirmation with a newly authored slot; the fix cannot
retroactively repair slot 004's serialized parent coordinates.
### Persistence implementation step 13 — run restored script prologues before `0xae` (2026-07-24)
Manual slot 005 acceptance passed both immediate base restoration and later stage launch. The next
dungeon-authored rewrite, slot 006, loaded with its UI present but a black, noninteractive map. Binary
and software-renderer inspection showed that the slot itself was healthy: it contained FIELD's saved
range, 704 retained objects, map textures, and a native-looking 0.8 transform. During restore, however,
FIELD overwrote the map objects with zero scale. Global zoom selector `G[0x7682]` was 3, while the
frame-local zoom table entry that should have returned 80 was zero.
Native `script_frame_restore_saved_layout@0x40f2d0` calls
`script_frame_load_resource@0x40e980`, which creates each saved script frame at its ordinary codebase.
The script runs its entry prologue and reaches `0xae` itself. The managed restore instead created frames
directly at `0xae`, skipping FIELD's inline initialization of `[40, 50, 64, 80, 100, 124, 156]`.
Restored frames now start at script offset zero while retaining the serialized T1/T2/T3 coordinates
consumed at `0xae`. Synthetic root and child tests require their pre-rendezvous prologues to execute.
The unchanged real slot 006 then replays with zoom 80, 0.8 object scale, a nonblack dungeon raster, and
the gameplay poll; the fix does not require rewriting that save.
The adjacent surface declaration gap is also closed. Native opcode `0x258` sets surface-record `+0x08`
from flags bit 0 and `+0x0c` from bit 1 after `0x259` clears both tables. The port now applies bit 0 as
the numbered-load reload policy; bit 1 remains identified but has no known consumer.
Validation: engine **393/393**, clean opcode lint/tooling, zero-warning Godot build, threaded
`SELFTEST OK`, and exact slot-006 software replay with zoom 80 and a nonblack dungeon raster.
Acceptance: rebuild and interactively load existing slot 006. The expected result is the restored
dungeon map and controls without a re-save.
## Data-semantics sidebar: focused append EBINIT inspection (2026-07-24)
The static INIT surface now accepts a universal packed script id for focused append inspection.

View File

@@ -480,7 +480,19 @@ serialized mutable bank prefixes (preserving initialized unit/stage/string defin
retained BGM/SFX state, preserves initialized flag-zero system surfaces while overlaying explicit saved
reload records, reproduces the native configuration-gated all-surface release when explicitly enabled,
and writes native per-slot reload/created metadata rather than treating every texture as reloadable.
Opcode `0x259` supplies its real script-entry reload-policy clear. JSON inspection/export,
Opcode `0x259` supplies its real script-entry reload-policy clear. Selected offscreen render targets now
receive actual retained-object pixels, closing the black-thumbnail half of the first port-authored-save
failure. The following immediate-resave oracle exposed and fixed two separate numbered-state defects:
`0xae` now re-establishes the saved `0x1ad` gameplay-frame boundary before a later save UI opens, and
retained objects use AGE's sparse stride plus native-initialized matrix/default bytes while preserving
unnamed fields. The next fresh rewrite also proved that reconstructed managed ancestor frames must retain
their original T1/T2/T3 coordinates while parked at synthetic `0xae`; preserving those coordinates fixes
the later SYSTEM4 unwind that otherwise re-entered the Eushully intro. Slot 005 subsequently passed the
base-load and stage-launch round trip. Dungeon-authored slot 006 exposed one further native-ordering
requirement: restored scripts must begin at their ordinary entry, run frame-local prologues, and reach
`0xae` themselves. Matching that order restores FIELD's 80% zoom table entry and produces the dungeon
map from the unchanged slot; interactive slot-006 confirmation is the remaining visual gate.
JSON inspection/export,
namespaced mod data, and migrations remain additive extended-mode work rather than 1.0 compatibility
requirements.

View File

@@ -245,13 +245,23 @@ Retained graphics then uses:
```text
0x00 gfx_record_size:u32 # 0x2d4
+0x04 gfx_object_count:u32
+0x08 repeated { handle:u32, record[0x2d4] }
+0x08 repeated sparse entries:
handle:u32
record[0x2d4] # 0xb5 meaningful DWORDs
padding[0x87c]
entry stride = 0x2d5 DWORDs = 0xb54 bytes
... range_first:u32
... range_count:i32
... range_transform_record[0x2d4]
... native allocation slack
```
The sparse stride is not an allocator-only artifact. Both native writer and reader hold a DWORD pointer:
after the handle, they copy `0x2d4` **bytes** but advance by `0x2d4` **DWORDs**. Consecutive handles are
therefore `0x2d5` DWORDs apart. Early experimental port saves incorrectly packed
`{handle,record}` contiguously; compatibility import recognizes that layout, while all new output uses
the native sparse form.
The 1,000 surface records preserve the native 20-byte metadata cells verbatim:
| Offset | Size | Meaning |
@@ -270,9 +280,13 @@ surfaces expected to remain live across the load. The installed file is decisive
are zero, while SYSTEM4's reusable choice-frame atlas remains recorded as resource `0x3383` in slot 15.
A separate configuration-gated path can release all 1,000 surfaces before this loop, but Himegari's
registered `CreateObject=1`, `AutoFreeTex=0` defaults leave it inactive. A retained `0x2d4`
record is structurally complete but not every internal graphics field is semantically named. The native
allocation is larger than the records actually written (`0x2e1 + object_count * 0x2d8` DWORDs in the
graphics sizing term), leaving zero/slack bytes after the meaningful range record.
record is structurally complete but not every internal graphics field is semantically named. Native
`gfx_object_init_default` initializes six embedded 4x4 matrices (current/target scale, rotation, and
translation) to identity, packed colors at `+0x60/+0x64/+0x240` to `0xffffffff`, and other fields to zero.
Deserialization initializes an object and then overwrites all `0x2d4` bytes, so a compatibility writer
must emit or preserve those defaults rather than zero-fill unnamed fields. The graphics sizing term remains
larger than the meaningful sparse entries and range record
(`0x2e1 + object_count * 0x2d8` DWORDs), leaving final allocation slack as well.
The installed `SAVE00.DAT` validates the complete layout-3 decode: cutoff 1, global-bank counts
`[402459,1,789,1,1,1]`, current BGM id `0x18`, retained SFX ids `0x3321` (channel 1) and