Restore complete numbered save state
This commit is contained in:
@@ -16,6 +16,7 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-
|
||||
| `0x1c34` | `mouse_wheel_delta` | `int` | signed WM_MOUSEWHEEL delta accumulated by age_main_window_proc; op 0x10d returns and clears it |
|
||||
| `0x4d7c` | `shared_profile_state` | `void*` | embedded shared SAVE.DAT state object; owns profile integer/settings tables and container timing metadata |
|
||||
| `0x5190` | `shared_profile_int_table` | `int` | open-addressing 12-byte string-key to 32-bit value table; op 0x1a2 stores, 0x1a3 loads, shared SAVE.DAT serializes it |
|
||||
| `0x144e0` | `sfx_channel_resource_ids` | `int` | base of ten packed resource ids retained by the sound-effect facade; op 0xb4 loads a channel, op 0xb6 clears it, and numbered-save layouts restore then reopen every positive id |
|
||||
| `0x14d54` | `gfx_obj_ptr_table` | `void*` | per-object pointer table (ops 0x212/0x213 write obj+0x64/0x68/0x6c) |
|
||||
| `0x14e08` | `gfx_default_object_slot` | `int` | op 0x80 selected slot; op 0x1d9 substitutes it when its explicit object-slot operand is zero |
|
||||
| `0x14ea0` | `text_line_spacing` | `int` | extra pixel leading between text lines; defaults to 6, op 0x8b writes it |
|
||||
@@ -99,6 +100,7 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-
|
||||
| `0x9c660` | `sys4ini_records` | `void*` | SYS4INI 80-byte record base at embedded FileDB+0x414; record = base + id*0x50 |
|
||||
| `0x9f274` | `mounted_aai_catalogs` | `void*` | base of 256-entry selector-keyed AAI catalog-pointer table inside the embedded FileDB; op 0x143 scans slots 1..255 from +0x9f278 |
|
||||
| `0x9f278` | `mounted_aai_catalog_selector_1` | `void*` | selector-one cell and op 0x143 scan start; subsequent dwords are selector 2..255 |
|
||||
| `0xa0b84` | `current_bgm_track_id` | `int` | direct-name BGM track id retained by the music facade; op 0xbf starts/replaces it, ops 0xc0/0xc3 get/set it, and numbered-save layouts restore it |
|
||||
| `0xa0cc0` | `screen_w` | `int` | logical screen width; constructor/default registry uses 640, then the SYS4INI SCREENX setting overrides it (Himegari 800) |
|
||||
| `0xa0cc4` | `screen_h` | `int` | logical screen height; constructor/default registry uses 480, then the SYS4INI SCREENY setting overrides it (Himegari 600) |
|
||||
| `0xa0cc8` | `screen_bpp` | `int` | screen bpp (8) |
|
||||
|
||||
@@ -1751,12 +1751,30 @@ diagnostic/extended-mode surface.
|
||||
|
||||
The port tracks the native global banks separately at runtime, captures the marked frame chain, serializes
|
||||
live surfaces and retained objects, and reloads host textures from the 20-byte surface records. Full load
|
||||
first replaces banks/history/gfx, unwinds the obsolete managed call chain, runs `CALLBACK_LOAD.BIN` when
|
||||
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,
|
||||
and finally resumes the terminal frame at its T1 boundary. Successful `0x19e` also flushes shared
|
||||
`SAVE.DAT`/`RT.DAT`, matching `context_state_serialize`.
|
||||
|
||||
The prefix boundary is observable in the installed file: integer count `0x6241b` stops before the
|
||||
initialization-authored unit/stage definition tables, and string count `0x315` stops exactly before
|
||||
`unit_story_display_names`. Native `save_data_deserialize_and_begin_restore@0x40fd10` zeroes only those
|
||||
counted regions; the port's former whole-dictionary clear erased the definitions needed by CHMENU,
|
||||
unit-management, and SELSTAGE after load.
|
||||
|
||||
The fixed audio fields are also resolved. Payload `+0x008` is
|
||||
`EngineCtx.current_bgm_track_id` (`ctx+0xa0b84`), maintained by
|
||||
`bgm_play_track@0x464750` and queried by op `0xc0`; the installed FORT save contains `0x18`, matching
|
||||
FORT's BGM024 command immediately before its T1 resume boundary. Payload `+0x00c..+0x033` copies
|
||||
`EngineCtx.sfx_channel_resource_ids[10]` (`ctx+0x144e0`), and
|
||||
`sfx_reload_saved_channels@0x482640` reopens each positive packed id after deserialization. The port now
|
||||
tracks both lifecycles through `0xbf`/`0xc2` and `0xb4`/`0xb6`, serializes them, and restores the active
|
||||
track plus saved SFX resources. The new EngineCtx fields and function annotations are applied to the
|
||||
saved `/v2` image.
|
||||
|
||||
### 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,
|
||||
@@ -1776,6 +1794,16 @@ coroutine-resume or call boundaries, including SC0000's main-loop resume sequenc
|
||||
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.
|
||||
|
||||
The first interactive installed-save continuation exposed a dispatch-label integration error rather than
|
||||
a native semantic error. Although `0xae` had this mapped semantic record and a VM case, its opcode-table
|
||||
label still used upstream placeholder `u00415130`; the VM switches on the label, so the active case was
|
||||
unreachable. The saved root entered `SYSTEM4.BIN` with restore state intact, but `0xae` fell through and
|
||||
SYSTEM4 ran its ordinary `LOGO.BIN → OP.BIN → INIT.BIN → TITLE.BIN` boot path. The source label is now
|
||||
`continue-save-load-stack-restore`. A real installed-save gate executes beyond the rendezvous and proves
|
||||
`SYSTEM4.BIN → FORT.BIN → CHMENU.BIN` restoration to the gameplay poll. The synthetic two-frame gate
|
||||
also asserts that its child enters with `FrameCause.SaveRestore`, preventing its ordinary call site from
|
||||
masking another inactive-branch regression.
|
||||
|
||||
### ADV read-message Skip and shared `RT.DAT` history (2026-07-18)
|
||||
|
||||
Read-message Skip is backed by an engine-owned `ReadTextDB`, not a VM-global flag and not ordinary numbered
|
||||
|
||||
@@ -167,6 +167,11 @@ Port status (2026-07-24): after the blocking host releases this wait, the VM que
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **evidence:** By-ear confirmed (2026-07-06): SC0000 real game plays BGM005 for play-bgm 0x5 and BGM008 for play-bgm 0x8 (we initially mis-played BGM006/BGM009 via the disproven scene-section model). Direct-name proven by play-bgm 0x23 -> BGM035.OGG, a real standalone track (the BGM set skips 030-034). Ghidra /v2 op_0xbf_handler@0x420390 forwards the numeric track to the BGM facade rather than asset_open_indexed_entry. Diagnostic: `Age.Cli audio SC0000.BIN`.
|
||||
|
||||
### 0xc0 `get-current-bgm-track` (get-current-bgm-track, argc 1)
|
||||
- **summary:** (track_out) - return the direct-name BGM track id retained by the music facade; the same value is restored from numbered saves.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **evidence:** Ghidra /v2 op_0xc0_get_current_bgm_track@0x428440 writes EngineCtx.current_bgm_track_id (+0xa0b84) to operand 1. op 0xbf's bgm_play_track retains the selected direct-name id at the same music-facade field; layout-3 serialization copies it to payload +0x008 and restoration copies it back. Installed SAVE00 stores 0x18, matching FORT's BGM024.
|
||||
|
||||
### 0xc2 `fade-bgm` (u0041D2B0, argc 2)
|
||||
- **summary:** (target_percent)(duration_ms) — block script service while linearly fading current BGM volume to 0..100%. Durations >=1000 ms use 100 steps; shorter fades use 10. Target 0 releases the current BGM source at completion.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
@@ -397,12 +402,12 @@ Implemented as a whole-stack root-reload boundary in the persistent VM. A reques
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **evidence:** Ghidra /v2: op_0xa3_handler@0x420060 formats operand 1, queries value_dispatch_lookup@0x419290, writes the matched or operand-2 default PC into the current frame, and clears the command type. Corpus pairs it with 0xa1/0xa2 in 12 generic switch sequences.
|
||||
|
||||
### 0xae `continue-save-load-stack-restore` (u00415130, argc 0)
|
||||
### 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
### 0xc8 `sleep` (sleep, argc 1)
|
||||
- **summary:** Pause the current script for <duration> milliseconds while retained presentation continues.
|
||||
@@ -1267,10 +1272,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting
|
||||
- **summary:** —
|
||||
- **grounding:** source=kelebek, confidence=low
|
||||
|
||||
### 0xc0 `u00415620` (u00415620, argc 1)
|
||||
- **summary:** —
|
||||
- **grounding:** source=kelebek, confidence=low
|
||||
|
||||
### 0xc5 `u0041D4A0` (u0041D4A0, argc 2)
|
||||
- **summary:** —
|
||||
- **grounding:** source=kelebek, confidence=low
|
||||
|
||||
@@ -3546,6 +3546,57 @@ and unnamed retained-gfx fields outside the confirmed Himegari compatibility cla
|
||||
This documentation-only closeout does not change runtime persistence. The next functional check remains
|
||||
continuing from the installed slot through the restored gameplay frame.
|
||||
|
||||
### Persistence implementation step 7 — installed gameplay continuation correction (2026-07-24)
|
||||
|
||||
Interactive loading of slot 000 reached `SYSTEM4.BIN` as a restore but then played `LOGO.BIN` and
|
||||
`OP.BIN` before returning to a broken TITLE state. The installed-save regression had stopped on frame
|
||||
entry, before executing the saved root's `0xae`, while the synthetic two-frame test accidentally made
|
||||
the same child call through its ordinary script path.
|
||||
|
||||
The restore state and decoded frames were correct (`SYSTEM4.BIN` root plus terminal `FORT.BIN`). The
|
||||
actual integration error was the opcode registry: `0xae` had the implemented semantic case but retained
|
||||
placeholder label `u00415130`, and VM dispatch selects cases by label. It therefore behaved as an
|
||||
unhandled stub. The source label is now `continue-save-load-stack-restore` and generated opcode
|
||||
references were rebuilt.
|
||||
|
||||
The synthetic regression now requires the child to enter with `FrameCause.SaveRestore` and rejects an
|
||||
ordinary `CallScript` entry. A new read-only installed-save continuation gate traverses the real
|
||||
`SAVE.BIN → CALLBACK_LOAD.BIN → SYSTEM4.BIN → FORT.BIN → CHMENU.BIN` path and stops at FORT's stable
|
||||
gameplay poll; the original AppData files remain untouched.
|
||||
|
||||
Validation: all 387 engine tests pass, opcode lint reports zero errors/warnings, the Godot C# build has
|
||||
zero warnings, and the threaded headless run reports `SELFTEST OK`. The next acceptance action is a
|
||||
visual retry of slot 000 in the rebuilt port.
|
||||
|
||||
### Persistence implementation step 8 — preserve initialized banks and restore retained audio (2026-07-24)
|
||||
|
||||
The first successful FORT continuation exposed a second load-state boundary. CHMENU retained party-slot
|
||||
shells but lacked names/definition data, SELSTAGE listed no stages, unit-management was blank, and the
|
||||
TITLE BGM continued playing. Reaching FORT through the developer launcher populated the same consumers,
|
||||
localizing the issue to restoration rather than their rendering opcodes.
|
||||
|
||||
Layout 3 stores mutable prefixes, not the complete initialized typed banks. The installed integer count
|
||||
is `0x6241b`, before unit/stage definitions including `0x66716` and `0xe8275`; the installed string count
|
||||
is `0x315`, exactly where `unit_story_display_names` begins. Native deserialization zeroes only the
|
||||
counted prefixes. The port instead cleared whole dictionaries, erasing the initialization-authored tail.
|
||||
Full load now removes and replaces only keys below each saved count, including saved zeroes, and preserves
|
||||
every initialized key at or above the boundary.
|
||||
|
||||
Native RE also resolves the fixed audio words. Payload `+0x008` is the current direct-name BGM track;
|
||||
the installed value `0x18` matches FORT's skipped BGM024 instruction. The following ten DWORDs are packed
|
||||
SFX resource ids; the installed save retains SE015 and SE020 in channels 1 and 2. VM audio operations now
|
||||
track these values, numbered saves emit them, and full restoration replaces the BGM plus reloads retained
|
||||
SFX channels. Opcode `0xc0` is mapped as the current-BGM query. Ghidra `/v2` names/comments the BGM
|
||||
getter/setter/player and saved-SFX reload worker, includes the two new EngineCtx fields, and is saved.
|
||||
|
||||
Regressions cover saved-zero replacement, preservation at integer/string prefix boundaries, BGM/SFX
|
||||
round trips, the `0xc0` query, and installed SAVE00 continuation with its matching append-install mask.
|
||||
Validation: all 387 engine tests pass, opcode and EngineCtx generation/lints are clean, the Godot C#
|
||||
build has zero warnings, and the threaded headless run reports `SELFTEST OK`. Manual slot-000 acceptance
|
||||
then confirmed that CHMENU, unit management, SELSTAGE, and FORT audio populate correctly. The remaining
|
||||
visible load-specific discrepancy is the reusable choice-box frame losing its top winged flourish after
|
||||
restoration; that is the next independent investigation slice.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -473,9 +473,13 @@ import/export and the packed-script/T1 ReadTextDB queue/commit/query lifecycle a
|
||||
including `message:ReadTextSkip` ops `0x1ca`/`0x1cb` and state query `0x1cc`. Numbered active-frame state
|
||||
is now implemented in native layout 3: metadata query, paired `.DAT`/`.STH` lifecycle, exact native BMP
|
||||
thumbnail I/O, six global banks, retained surface/gfx state, history, and nested frame restoration through
|
||||
the `0xae` rendezvous. The real `SAVE.BIN` script is covered end to end for listing and loading, including
|
||||
a read-only installed-save compatibility gate. JSON inspection/export, namespaced mod data, and migrations
|
||||
remain additive extended-mode work rather than 1.0 compatibility requirements.
|
||||
the `0xae` rendezvous. The real `SAVE.BIN` script is covered end to end for listing and loading; the
|
||||
read-only installed-save gate now continues through `CALLBACK_LOAD`, reconstructs
|
||||
`SYSTEM4.BIN → FORT.BIN`, and reaches FORT's `CHMENU` gameplay poll. Full restoration replaces only the
|
||||
serialized mutable bank prefixes (preserving initialized unit/stage/string definitions), restores the
|
||||
retained BGM/SFX state, and rebuilds graphics/history/frame state. JSON inspection/export,
|
||||
namespaced mod data, and migrations remain additive extended-mode work rather than 1.0 compatibility
|
||||
requirements.
|
||||
|
||||
### Phase C — Externalize & modding foundation
|
||||
- Add **editable named data overlays** mapped explicitly onto the VM's `*INIT`-produced state; external
|
||||
|
||||
@@ -198,8 +198,8 @@ The decoded layout-3 body begins:
|
||||
|---:|---:|---|
|
||||
| `0x000` | 4 | terminal saved-frame index, called `cutoff`; frame count is `cutoff + 1` |
|
||||
| `0x004` | 4 | saved frame-owner/context word |
|
||||
| `0x008` | 4 | engine-state word |
|
||||
| `0x00c` | `0x28` | ten engine/context state DWORDs |
|
||||
| `0x008` | 4 | current direct-name BGM track id |
|
||||
| `0x00c` | `0x28` | ten packed SFX resource ids, one per retained channel |
|
||||
| `0x034` | `0x4b0` | 100 resource-reload records of three DWORDs |
|
||||
| `0x4e4` | `0x4e20` | 1,000 surface-reload records of 20 bytes |
|
||||
| `0x5304` | `(cutoff + 1) * 0x414` | saved script-frame records |
|
||||
@@ -233,6 +233,13 @@ The integer and float arrays follow their counts. String count comes from bank c
|
||||
begins with an additional `string_blob_dwords` followed by that many bytes of concatenated NUL-terminated
|
||||
CP932 strings and DWORD padding. The three pointer-family DWORD arrays follow.
|
||||
|
||||
These counts are serialized mutable-prefix lengths, not declarations that every typed runtime bank ends
|
||||
there. Native deserialization zeroes and replaces only each counted prefix. Initialization-authored
|
||||
definitions after the prefix remain live: in Himegari the saved integer prefix ends at `0x6241b`, before
|
||||
unit/stage definition tables such as `0x66716` and `0xe8275`, while the saved string prefix ends at
|
||||
`0x315`, exactly where `unit_story_display_names` begins. Clearing the whole runtime dictionary during
|
||||
import therefore destroys data that is intentionally absent from the numbered file.
|
||||
|
||||
Retained graphics then uses:
|
||||
|
||||
```text
|
||||
@@ -252,7 +259,8 @@ records actually written (`0x2e1 + object_count * 0x2d8` DWORDs in the graphics
|
||||
zero/slack bytes after the meaningful range record.
|
||||
|
||||
The installed `SAVE00.DAT` validates the complete layout-3 decode: cutoff 1, global-bank counts
|
||||
`[402459,1,789,1,1,1]`, and 211 retained graphics objects.
|
||||
`[402459,1,789,1,1,1]`, current BGM id `0x18`, retained SFX ids `0x3321` (channel 1) and
|
||||
`0x2aea` (channel 2), and 211 retained graphics objects.
|
||||
|
||||
#### Appended text-history tail
|
||||
|
||||
|
||||
Reference in New Issue
Block a user