Files
OpenMaidEngine/docs/phase-b-framework.md
2026-08-15 10:30:32 -04:00

1445 lines
114 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase B Framework — Natural Boot to First Gameplay
Phase B broadens the proven ADV vertical slice into a naturally booted, stateful play session and then into
the first narrow gameplay loop. This document is a sequencing framework, not a task-level implementation
plan. Phase A remains active until SC0000 meets its completion criteria.
The architectural preference is:
```
complete SC0000
-> persistent session and scene coordinator
-> faithful system/data boot
-> title and New Game happy path
-> SC0000 under naturally initialized state
-> natural first-dungeon transition
-> bounded first-dungeon gameplay slice
```
This order makes gameplay failures attributable to gameplay rather than to missing boot state, discarded
globals, or manually inherited host state.
## Entry criteria from Phase A
Phase B may begin when SC0000 is a reliable presentation baseline:
- A normal windowed playthrough is visually and audibly coherent from entry to natural exit.
- Reproducible presentation inconsistencies have been resolved or explicitly classified with evidence.
- Every SC0000-executed effectful opcode is implemented, or its lack of a host-visible effect is supported
by native/script evidence. Remaining unrelated corpus gaps do not block entry.
- The run no longer relies on unexplained timing, input, layer, or resource workarounds.
- Automated VM/host regressions and a repeatable manual playthrough form the acceptance baseline.
- SC0000's terminal global state and control-flow boundary can be captured for comparison once natural
scene chaining exists.
The Phase A implementation/result history remains in `docs/phase-a-slice-plan.md`.
## Principles
1. **State correctness before systems breadth.** Dungeon logic depends on initialized unit, item, skill,
progression, configuration, and heroine state. Establish their natural producers before debugging their
consumers.
2. **Follow script control flow.** The bytecode owns game rules. Implement the effectful operations and
lifecycle services it calls; do not replace dungeon/combat logic with a parallel rules engine.
3. **One natural path first.** Boot → title → New Game → SC0000 → first dungeon is the initial spine.
Alternate menu branches and broad gameplay coverage grow from it later.
4. **Persistent session, replaceable scenes.** Globals and game/profile state survive scene changes while
script frames, retained presentation state, and scene-owned resources observe proven lifecycle rules.
5. **Demand-driven opcode work.** Investigate an unknown opcode when the chosen path executes it or evidence
connects it to a reproduced defect.
6. **Bound every slice by an observable transition.** Each stage starts from a known state and ends at a
visible screen, input boundary, scene handoff, or gameplay action.
7. **Escalate persistent or cross-subsystem discrepancies.** Start with the smallest evidence-backed local
investigation. If a discrepancy crosses a subsystem boundary, or survives one focused local correction,
stop making local patches and apply the subsystem escalation rule below.
### Subsystem escalation rule
When the escalation trigger is met:
1. Bound the affected native subsystem and map its relevant entry points, lifecycle, owned state, and
interactions with adjacent subsystems. This is a focused map of the failing contract, not an exhaustive
decompilation of `AGE.EXE`.
2. Define the observable contract at stable coordinates and capture native evidence for it. Prefer a
repeatable differential oracle such as VM/global snapshots, save/resume state, retained-surface
fingerprints, audio/video timestamps, or input/yield transitions.
3. Compare the native evidence with the port and localize the first meaningful divergence before changing
the implementation again.
4. Implement the smallest correction consistent with the mapped lifecycle, then retain the differential
comparison as an automated regression where practical and finish with the slice's manual acceptance check.
End the escalation once the reproduced discrepancy and its relevant contract are explained. Do not expand it
into reconstructing unused handlers, recovering the whole native engine, or producing a byte-matching C build.
### DEBUGMAP P008/P009 GDI worker-end regression corrected (2026-08-15)
A live `SYSTEM4 > FIELD > SC0270` run first reported `[vm] ended: unknown` while the locator still showed P008
(`SC0270@0x5b22`). The coordinate is an ordinary `wait-for-input`; its next instruction begins P009. A
corpus-backed regression and automated portable-text runs crossed that boundary, but those runs did not exercise
the exact Windows GDI rasterizer used by normal Windows play. The `unknown` message also hid the actionable
exception because the worker's `finally` set the completion flag while the main loop printed only the unset VM
halt reason. Godot now retains and prints the complete worker exception and automatically writes a
`worker-failure` diagnostic snapshot at the last traced script/opcode coordinate.
The next live failure and that snapshot localized the defect to P009's `show-text` at `SC0270@0x5bea`. This line
contains an ideographic full-width space (`U+3000`, CP932 `0x8140`). With ` 明朝` at the active 24-pixel
style, `GetGlyphOutlineA(GGO_GRAY4_BITMAP)` returns the spacing glyph's advance and a nominal 1x1 metric box but
reports a zero-byte bitmap requirement. The adapter incorrectly required the reported buffer size to equal the
four-byte aligned metric box and threw before P009 could be published. It now treats only a zero-byte result as
a zero-ink glyph: the GDI placement and advance metrics are retained and the implied mask is filled with
transparent coverage. Nonzero short or oversized payloads remain hard failures. A focused Windows regression
reproduces the original 0-versus-4 result and verifies that the ideographic space advances without drawing ink;
the SYSTEM4/DEBUGMAP boundary regression remains in place. The user then repeated the normal exact-GDI
DEBUGMAP entry and confirmed that the dialogue now continues into the map without the worker failure, accepting
the fix end to end.
## Stage B0 — Ground-truth reconnaissance
Before changing runtime architecture, record the original game's path from process start through the first
meaningful dungeon interaction. The goal is an answer key, not exhaustive reverse engineering.
Capture:
- Script/load order across system boot, title, New Game, SC0000, and first dungeon entry.
- Which initialization scripts run and which global banks or native/profile values they establish.
- Retained graphics/audio state that survives each boundary.
- The title selection and New Game dispatch path.
- SC0000's natural terminal decision and the corresponding next loaded script.
- The first dungeon's executed opcode/call-script families, assets, and obvious state dependencies.
Detailed progression semantics remain canonical in `docs/scjump-progression.md`; native loader findings
belong in `docs/engine-re.md` and `docs/name-resolution.md`.
### Initial B0 result (2026-07-20)
Static SYSTEM4/INIT2 control flow plus an existing native opcode trace establishes the first natural spine:
`SYSTEM4 → config load/init → INIT2 (+23 nested data initializers, then TUNE) → optional LOGO/OP
→ INIT → TITLE → GAMESTART → UNITECH/CALCARR → TUNE → TITLE return → SYSTEM4 → SC0000`.
SYSTEM4, not an opaque native dispatcher, is the long-lived scene coordinator. It maps the SCJUMP decision
through a global resource-id table, places the result in `G[0x699]`, and uses computed `call-script`; the
initial zero decision falls back to raw SYS4INI id `0x22`, `SC0000.BIN`. The current VM already supports
computed nested call-script frames. Consequently B1 should preserve one VM and host rooted at SYSTEM4,
letting script-owned setup/cleanup surround child scenes, rather than invent an out-of-band replacement
protocol. Full process-start observation remains useful for profile/default and retained host-state evidence,
but is no longer needed to guess the script coordinator architecture.
The current headless C# runner already follows this root naturally: one SYSTEM4 run enters INITCONFIG,
INIT2 and all 23 of its base data-initializer children, APPEND01's AUTORUN and 22 append initializer
children, TUNE, INIT, and TITLE (51 nested script calls total), then
remained in TITLE's input-poll loop because the diagnostic host supplies no user input. Direct opcode
coverage is 100% for all 23 data initializers, CALCARR, and TUNE; the remaining direct coverage is SYSTEM4
64/82, INIT2 10/12, TITLE 61/65, GAMESTART 43/47, and UNITECH 29/31. B0/B1 should therefore make the
SYSTEM4-rooted path visible and interactive in Godot, then investigate only the gaps actually reached on
that route instead of treating every static gap as a prerequisite.
**Append boot implemented (2026-07-24).** Mounted `APPEND01` has
`$1$AUTORUN.BIN` at packed id `0x01000000`; that script calls 22 append INIT deltas and registers the
append's class-change, SCJUMP, message, stage, and scenario resources. INIT2's sole op `0x143` site,
after its 23 base initializer children and registry setup and before TUNE, scans mounted selectors in
ascending order and serially executes each selector's record zero. The port now snapshots the mounted
selectors through its script-provider seam, constructs each `selector << 24` id, and runs those scripts
through normal nested VM frames. A natural SYSTEM4 regression proves the shipped order
`BTANINIT2 -> $1$AUTORUN -> $1$EBINIT -> TUNE`; focused regressions cover multiple-selector ordering,
deduplication, caller resumption, and missing record zero. The packed sequence is canonical in
`docs/asset-resolution-re.md`; native queue/frame mechanics are canonical in `docs/engine-re.md`.
**Godot root landing (2026-07-20).** The no-argument Godot/run-godot path now starts SYSTEM4 directly and
does not apply the direct-SC0000 layout/surface bootstrap or the diagnostic `--boot` prefix. A windowed run
reaches and renders TITLE using SYSTEM4-owned retained state. A real-script integration test drives TITLE's
Game Start input, GAMESTART's release-gated default selection, and proves the same VM enters SC0000 through
SYSTEM4's computed resource id `G[0x699]=0x22`; `G[0]=1` and the script-produced ADV-chrome flag
`G[0x6c1]=1` are present at that boundary. `--scene SC0000 --boot` remains available only as the explicit
single-scene diagnostic harness. This lands the boot/title/New Game entry half of B1B3; proving a completed
scene return plus boundary cleanup still belongs to B1 completion.
**TITLE SFX packed-raw correction (2026-07-20).** Hover and activation callbacks were already executing their
scripted `0xb5` starts. The load failed earlier because op `0xb4` uses universal packed SYS4INI/AAI ids,
while Godot treated them as active-script manifest ids. The packed resolver maps TITLE's
`0x2aea`/`SE020.WAV` hover, `0x3321`/`SE015.WAV` activation, and GAMESTART's `0x2aeb`/`SE013.WAV` cancel
through the existing channel players. A synchronized TITLE→GAMESTART→TITLE trace records every load/start
with its filename, and manual validation confirms they are audible; BGM remains unaffected.
**TITLE-to-SC0000 BGM fade ownership corrected (2026-07-28).** SC0000's `0xc2@0x7c1` blocks the VM for
the three-second title-music fade, then starts `BGM005` at `0x7fa`. Godot created the actual gain tween
through a deferred main-thread call, however, so its start could lag the already-running virtual deadline
by one frame. On the unlucky schedule, `BGM005` restored unity gain just before that stale title tween
finished and wrote `-80 dB`. Voice ducking exposed the diagnosis: a voice wrote the configured 50-percent
level, making the live BGM audible, then restored the captured silent level. The Godot backend now owns
exactly one explicit BGM fade, cancels it before replacing the track or starting another fade, and suppresses
voice ducking while the fade owns the envelope as native AGE does. The threaded selftest starts a live
fade and proves a replacement BGM cancels it and retains normal gain.
**Pre-title video sequence implemented (2026-07-20).** SYSTEM4 already owns the native sequence; the
port did not lose an executable-side launcher. Its sole op `0x130` call returns an engine initial-root flag
that is one at context construction and cleared only when op `0x9` resets/reloads root script id zero.
SYSTEM4 calls `LOGO.BIN` and `OP.BIN` only while that flag is nonzero. The former stubbed-zero output
explained the direct jump to TITLE. LOGO and OP then use the modal movie op
`0x20f` with packed catalog movies `0x335f`/`LOGO.AGF` and `0x3364`/`OP.AGF`; existing `0x236` is the distinct
non-modal movie-to-surface path and uses the same packed lookup. The VM now models the initial-root flag and clears
it at the op-`0x9` whole-stack root-reload boundary. Godot resolves a typed MPEG asset, reuses the asynchronous decoder
and retained compositor, and parks the VM until EOF or mouse/Accept/Cancel input. Focused natural-boot tests
prove `LOGO -> OP -> INIT -> TITLE` ordering and exact movie operands. MPEG audio was explicitly deferred
at this milestone; the engine-owned synchronized audio/volume contract landed on 2026-07-25 as recorded below.
## Stage B1 — Persistent session and scene coordinator
Replace the single-SC0000-root assumption with an application-owned session that runs SYSTEM4 as its root.
SYSTEM4's computed `call-script` is the authoritative scene coordinator: child scenes return to that frame,
while globals, the host, and intentional retained state remain owned by the same live VM session.
**Root-reload boundary implemented (2026-07-20).** Ordinary op `0x2` child exits still return to their
calling SYSTEM4 frame. Op `0x9` is the distinct native reset path: it discards the complete active script
stack, clears scene-owned graphics/input/ADV state, cancels deferred SFX starts while preserving active
audio, preserves global banks and process-owned host state, and starts raw script resource zero
(`SYSTEM4.BIN`) at offset zero. The implementation propagates the boundary
through nested calls without running any caller continuation and records the new root frame with
`FrameCause.RootReload`. Native RE and the one intentional history-lifetime exception are documented in
`docs/engine-re.md`; the history backlog remains preserved until its ownership is proven rather than guessed.
Required responsibilities:
- Own global integer/string banks and any proven external/profile state across scenes.
- Preserve the SYSTEM4 root while distinguishing ordinary child frames from scene-boundary children for
diagnostics and lifecycle assertions; do not perform host-driven top-level replacement.
- Define scene-owned versus session-owned host state and tear each down at the correct boundary.
- Preserve intentional system-owned surfaces, configuration, and audio while releasing scene-local state.
- Expose deterministic transition evidence: outgoing scene, reason/decision, incoming scene, and state
summary suitable for tests.
Completion evidence now present: SYSTEM4 reaches computed child scripts in one VM; ordinary children return
to SYSTEM4; op `0x9` performs a tested whole-stack reload of SYSTEM4; selected globals and process-owned state
survive; and scene-owned presentation/input state is released. Manual validation of a natural gameplay
route through the first `0x9` remains deferred: Himegari's readily accessible return-to-title choice belongs
to the still-unimplemented frontend exit-request policy, while the other known natural paths require later
gameplay, game over, or completion. Do not use TITLE's post-`0x1` developer menu as evidence; native `0x1`
is non-returning, and the port now propagates that exit request instead of falling through into the hidden
bytecode. The opt-in `--native-debug-menu` diagnostic deliberately restores fall-through for exploring that
retained developer UI, but does not qualify as native lifecycle validation. See `docs/engine-re.md`.
**Godot debug scene launcher (2026-07-20; implemented and manually validated).** The first version
is deliberately narrower than arbitrary hot swapping:
- Expose an F4-style Godot overlay only while `TITLE.BIN` is the persistent VM's active SYSTEM4 child.
- Resolve the chosen `.BIN` through the existing SYS4 catalog, then ask the VM to return the current TITLE
child frame with the game-authored coordinator writes (`G[0]=1`, `G[0xaba5c]=-1`, `G[0x62ccf]=0`, and
selected packed id in `G[0x699]`) applied on the VM thread.
- Let SYSTEM4 resume at `0x2b0` and execute its real entry wrapper and computed `call-script`; do not replace
the VM root or call the selected scene directly from Godot.
- Disable switching while another scene is active. That scene must reach its own terminal cleanup and then
either return through SYSTEM4's post-child cleanup or execute its genuine op `0x9`. A separate clean
relaunch remains the escape hatch for a stuck/incomplete scene.
The runtime now has a generic debug-only "return this exact active child frame with queued global writes"
request, thread-safe frame-generation/stack reporting, and the distinct `DebugReturned` trace outcome. TITLE
does not park in ADV op `0x72`: its visible menu continuously polls input and executes a 1 ms op-`0xc8` sleep
at `TITLE@0xe5`. The request therefore targets the observed active frame generation and is consumed by the
VM thread at its next completed opcode boundary, before another TITLE opcode can execute. `SignalInput` is
used only if the target happens to be in a real ADV wait, avoiding a stale signal that could advance the
selected child. Synthetic coordinator tests cover both an ADV wait and TITLE's sleep/poll shape, selected-
child dispatch, ordinary SYSTEM4 continuation, stale/ineligible request rejection, and selected-child
op-`0x9` whole-stack propagation.
This launcher would provide the real visible TITLE→selected scene sequence and preserve the coordinator
boundary, but it cannot manufacture valid late-game state. The current direct harness and opcode coverage
suggest early ADV scenes and `DEBUG.BIN` are plausible targets; later scenarios, GAMECLEAR, battle/map, and
profile-dependent scripts may still require progression data or missing opcodes. A startup-only/direct-scene
selector is cheaper, but it is merely a UI for `--scene ... --boot` and provides no transition-lifecycle
evidence. An unrestricted in-process switch would additionally require VM cancellation, task joining,
movie/audio disposal, locator/trace regeneration, and an explicit global-state policy, so it is not a quick
or trustworthy first version.
**Menu population and selection contract.** The runtime SYS4 catalog—not `build/` inventory—is the
source of truth. Himegari currently has 481 unique base-catalog `.BIN` records: 136 `SC####`, 164 `SP*`, 8
`DEBUG*`, 29 initializer-named scripts, and 144 other named scripts. Each menu row keeps the packed resource
id as its identity and carries display name, pack selector, raw index, archive, size, and category; names are
labels rather than keys so future append-pack collisions remain representable. Population should enumerate
base `Catalog.Files` plus every mounted append catalog, exclude placeholders/non-BIN records, and compute
`packed_id = (pack_id << 24) | raw_index` without parsing all scripts up front. The selected script is decoded
and validated only when Launch is pressed; an unsupported decode reports an error and leaves TITLE running.
The currently mounted append pack contributes 39 additional `.BIN` records, so the shipped launcher smoke
test sees 520 distinct packed script ids.
The initial UI groups entries rather than implying every BIN is a standalone scene:
- **Scenario:** `SC####.BIN`, naturally sorted by number.
- **Secondary/event:** `SP*.BIN`, naturally sorted by name and suffix.
- **Debug:** `DEBUG*.BIN`.
- **Other/expert:** every remaining script; the separate **All** filter includes every category. Initializers,
callbacks, data routines, and modal UI scripts may require caller-owned state and may immediately return
or corrupt the live session.
`SYSTEM4.BIN` and `TITLE.BIN` are not launchable in the first version; recursively dispatching either through
SYSTEM4's child slot is not a scene test. Search is case-insensitive over name and hexadecimal/decimal packed
id. The detail pane shows name, category, packed/raw id, archive, size, and the fixed warning that launch uses
the current live global/profile state. Compatibility or opcode-gap badges are deferred until coverage logic
has an engine-owned runtime API; the menu must not parse generated Markdown or call Python tooling.
The implementation should leave one explicit extension point for future test sequences:
`DebugLaunchPreset(label, packed_script_id, extra_global_writes, note)`. Catalog rows use only the four
coordinator writes above; profile-authored presets may later add proven story/progression globals without
turning the menu into a free-form state editor or save backend. Arbitrary PC/offset jumps are out of scope.
**Implementation order.** (1) Add catalog script-entry enumeration with packed ids and unit coverage for
base/append mounts, placeholders, duplicate names across packs, and category/sort/filter behavior. (2) Add a
generic VM debug request targeted at an exact active frame generation; it applies an immutable set of global
writes on the VM thread and returns that child at the next opcode boundary. Test SYSTEM4→TITLE→selected child,
ordinary child return/cleanup continuation, op-`0x9` propagation, TITLE's sleep/poll loop, and stale/ineligible
request rejection. (3) Add the Godot F4 overlay (`PopupPanel`, search/category controls, `ItemList`, detail
pane, Launch/Cancel), consume all overlay input, and enable Launch only for the active `SYSTEM4 > TITLE`
stack. (4) Add a Godot smoke test for catalog population and request wiring, then
manually validate `TITLE -> DEBUG -> 0x9 -> SYSTEM4 -> TITLE` before expanding the selectable categories or
adding presets.
Steps 14 are complete. F4 opens the Godot `PopupPanel` only for the exact active
`SYSTEM4.BIN > TITLE.BIN` stack; search, category filters, packed-id metadata, guarded Launch, and Cancel are
live. `SYSTEM4.BIN` and `TITLE.BIN` remain visible but unlaunchable. While the panel is open, AGE gameplay
input is not forwarded. Launch reparses the selected packed id before queuing any writes. The threaded Godot
selftest constructs the catalog and panel and currently reports 520 unique packed scripts. Manual validation
confirmed `TITLE -> F4 -> DEBUG.BIN`: its four scripted ADV pages at `0xc7`, `0x110`, `0x17b`, and `0x1ed`
were presented, its terminal op `0x9` at `0x1fb` ran, and SYSTEM4 reconstructed the visible TITLE menu. No
launcher/session-lifecycle discrepancy was observed. DEBUG-specific content oddities are not acceptance
failures for this developer route and remain out of scope unless they reproduce in a normal game script.
**TITLE Extra Room entry (`ROOM.BIN`, 2026-07-20 through 2026-07-21).** The title dispatch itself is correct. The first manual
entry exposed a scheduler discrepancy: ROOM's stable input poll uses `sleep 0`, which native `0xc8` clamps
to a one-millisecond timer. Godot previously treated it as a zero-duration no-op, letting the script consume
the 20-million-step guard and display `-end-`. The interactive host now preserves the native one-tick yield;
Godot also prints the VM halt reason and step count before its generic end marker so future terminations are
distinguishable. Manual follow-up confirmed the room remains interactive and its genuine op-`0x9` returns to
TITLE, then exposed three presentation gaps. Native RE resolved and implemented all three underlying
contracts: `0x60` selects the four room variants through CRT `rand()%4`; corrected `0x6c` zero-fills the
13-cell character-voice profile table instead of writing scalar 13 into its first cell; and `0x25` performs
a blocking target-over-source crossfade between the two frames ROOM captures through offscreen `0x20c`
presents. Entry/menu transitions use timing argument 10, the final black transition uses 30, and the port
now waits for the terminal captured frame before surface release or SYSTEM4 reload. The release script's
fourth random presentation variant intentionally has no voice ids; the first three have distinct
greeting/farewell pairs. ROOM now has 55/56 distinct opcodes and 420/421 instructions handled or proven
safe; its sole remaining gap is the already classified one-shot `0x1fe` current-rotation setter on a
decorative object, unrelated to these transition/audio paths. Manual visual/audio parity remains to be
rechecked on the updated build. The first recheck confirmed the transition was visible but exposed two
follow-up corrections: the decompiled alpha-step branch had initially been read backwards (`arg<=64`
means step 16, not step 1), making argument 10 about 160 ms and argument 30 about 480 ms; and ROOM's raw
voice ids were reaching `play-voice` but failing the port's SC-section-only lookup because ROOM owns no SC
section. The initial compatibility fix added a raw fallback, but later native RE proved the broader rule:
voice and frontend texture operands are already universal packed catalog ids and never receive a scene base.
**Extra Room CG/H-scene unlock database (`CGMODE.BIN` / `HMODE.BIN`, 2026-07-28).** The copied
`user://SAVE/SAVE.DAT` was intact: native decode finds 7,966/13,208 valid base-catalog markers and
75/81 append markers, including all 851 CGINIT gallery-image ids and all 118 SPINIT H-scene script ids.
The encyclopedia and New Game settings looked correct because they restore selected integer cells through
`0x1a3`; CGMODE/HMODE instead filter their entries through opcode `0x19d`, which the port still stubbed and
therefore left as zero/locked.
Native RE identifies `0x19d` as the profile catalog-resource unlock predicate. Successful catalog opens
write a deterministic per-index stamp into a live marker table and its modular-exponent encrypted
SAVE.DAT table; shared-profile load decrypts base and selector-keyed append arrays before the query runs.
`SharedProfile` now owns that decoded set, the VM implements the query, and a shared VFS wrapper marks only
successful script/texture/audio/movie/cursor opens. Saving emits native-decodable base and append tables,
so imported unlocks display immediately and newly encountered resources persist. Focused regressions cover
exact native ciphertexts, base/append queries, successful-open timing, round-trip import/export, and known
installed CG/H-scene entries.
**Natural Game Start diagnostic gate (2026-07-21; captured).** The opt-in `-StartupDiagnostics` route kept
the persistent `SYSTEM4` root and native exit semantics and added no seeds, boot prefix, timing changes, or
input automation. A user-driven cold boot traversed the complete initialization family, `LOGO`/`LOGO.AGF`,
`OP`/`OP.AGF`, `INIT`, `TITLE`, `GAMESTART`, its nested unit-data setup, and finally raw script `0x22` /
`SC0000`. It stopped at the first stable wait, `SC0000@0x83c`, whose preceding text instruction is
`SC0000@0x834` (`――かつて、戦いがあった。`). The 97,730-event timeline contains 96,908 opcode steps and
129 distinct opcodes; the Godot log has no warnings/errors, both movies start and stop normally, no audio
resource is unresolved, and no VM halt occurs.
The 137 fallback events are not a single boot blocker. Most are declaration/statement/line markers already
proven safe, or deliberately deferred profile/read-text operations (`0x1a2`, `0x1a3`, `0x1cb`). The reached
effectful unknowns divide into SYSTEM4 layout setup and unit-data initialization. Native follow-up identifies
`0x194` as a string-equality predicate reached in `INIT2` and `GAMESTART`, and `0x1b0` as a dword-block copy
paired with still-unresolved pointer-preparation opcode `0x63` in `UNITECH`/`CALCCC`. `0x194` is now
implemented for every supported SYS4 string operand form, with focused equality/inequality and
compare-then-branch regressions. A step-traced natural-boot test reaches SC0000 without a `0x194` fallback;
static coverage now reports 10/12 INIT2 opcodes and 44/47 GAMESTART opcodes handled, with the remaining
GAMESTART profile operations still deliberately deferred. The reached `0x63`/`0x1b0` unit-data pair is
also implemented: native `0x63` aliases a typed backing-cell address into a pointer, while `0x1b0` copies a
counted dword span through direct or pointer endpoints. The traced natural boot reaches both without
fallback; UNITECH is now 31/31 handled and CALCCC 14/15, with only deferred profile op `0x1a2` remaining.
SYSTEM4's paired `0x79`/`0x1c1` setup is now natively resolved and implemented: `0x79` configures the
cursor restored by later layout resets, while `0x1c1` configures layout-local right/bottom overflow
boundaries. `AdvTextHistory`, VM dispatch, the direct-scene SYSTEM4 bootstrap, and ordinary/history Godot
label geometry now share that script-owned state; the former slot-1 hardcoding is removed. The next
reached SYSTEM4 cluster is also resolved: `0xfe` establishes ten logical input actions, while `0x107`,
`0x10b`, and `0x10c` configure joystick-button, mouse-button, and DIK keyboard mappings consumed by
`0xff`/`0x100`. The port now owns the native seven-action defaults, executes all four setters, translates
Godot physical key/mouse/joy events through the resulting map, scans only actions below the configured
count, and invokes callback slot `count` for the empty-mask release path. Direct-scene diagnostics replay
SYSTEM4's same 16 immediate configuration calls. This is independent of profile/save ownership. Closing
Godot currently releases a parked ADV wait before process teardown, so the page map may contain one trailing
shutdown-only page; the final timeline `input-wait` is the authoritative stop.
**SC0000 character-menu roster discrepancy (2026-07-21; investigated).** A natural-boot state probe at
SC0000 entry shows that GAMESTART/UNITECH already created party slot 2 with flags `0x13`, character id 2,
and selected slot 2. The empty Character Info page is therefore not missing boot data. CHMENU gathers that
active slot, constructs two 100-cell sort-key arrays, calls opcode `0x12f` at `0x1c9b`, and reads the
populated tail of the resulting index permutation. The port had been falling through `0x12f`, leaving the
output zero-filled and making CHMENU select empty slot zero. Native RE fully identifies `0x12f` as a stable
ascending index sort by the signed sum of two key arrays. The generic opcode is now implemented with native
32-bit overflow and stable ordering. Focused regressions cover equal keys, overflow, and zero count; a
real-script regression carries the naturally booted state into CHMENU and proves the first roster sort keeps
slot 2 selected with no `0x12f` fallback. This uses no seed or menu-specific injection. Remaining acceptance
is manual: open Character Info at the first stable SC0000 page and confirm the initial character is visible.
## Stage B2 — Faithful full boot
Replace `--boot`'s diagnostic seeding and separately injected inherited surfaces with normal boot execution.
First prove the installed game's actual ordering; do not assume the current helper lists are complete.
The boot path must cover two existing categories:
- System/session initialization currently approximated by `INITCONFIG`, `INIT2`, and `INIT`, including
host-visible side effects that `CaptureHost` discards.
- Game-data initialization represented by the `*INIT` family used by the headless boot/session tools.
- Mounted append initialization: implement op `0x143` at INIT2's natural bytecode position by enumerating
mounted selectors 1..255 in ascending order and executing packed record zero serially, preserving
base-versus-append order and provenance rather than treating same-suffix INIT fragments as filename
replacements.
Completion evidence:
- A fresh application reaches the same initial title state without `--boot`, `--seed`, or manual surface
injection.
- Required globals come from executed scripts or clearly identified profile/native defaults.
- Inherited retained state has a traceable owner and lifecycle.
- A boot snapshot is reproducible for tests, but the shipped path performs the real boot rather than loading
a developer snapshot.
This stage may reveal platform/install-selection work; track that separately in
`docs/platform-portability.md` rather than folding cross-platform export into Phase B.
## Stage B3 — Title and New Game happy path
Implement only enough menu behavior to choose New Game naturally and enter the story. This is primarily a
state-initialization and dispatch slice, not a mandate to complete every submenu.
In scope:
- Title/main-menu presentation required by the executed path.
- Keyboard/mouse selection and the menu-specific coroutine/hotspot forms actually reached.
- Configuration defaults that affect New Game or the subsequent runtime.
- New Game initialization and its transition request.
- Natural empty-save-state behavior when no saves exist.
Deferred to bounded follow-ups unless the happy path requires them:
- Full configuration UI and every setting.
- Remaining configuration/save UI branches beyond the native compatibility floor. Shared and numbered
codecs, real SAVE.BIN listing/load, port-authored round trips, and clean-shutdown profile persistence
landed on 2026-07-24.
- Extras, galleries, replay modes, and unrelated submenus.
- Menu visual polish that does not obstruct correct selection or state production.
Completion evidence: launching the application, selecting New Game, and reaching SC0000 with no manual
state seeds. The resulting SC0000 opening state must match the Phase A visual/audio baseline.
## Stage B4 — Natural progression through SC0000
Run the completed scene inside the persistent session and honor its real terminal transition. This stage
closes the currently unidentified decision-to-scene boundary described in `docs/scjump-progression.md`.
Completion evidence:
- Title/New Game reaches SC0000 through actual script/native dispatch.
- SC0000 completes without the developer auto-advance harness.
- The outgoing decision, selected next script, and persistent global changes agree with the original run.
- The first dungeon scene starts without reconstructing the VM or reseeding state.
## Stage B5 — First-dungeon vertical slice
Do not scope “gameplay” as maps + units + items + magic + combat + AI + win/loss all at once. After B0
identifies the first real interaction, select the smallest end-to-end loop that exercises authoritative
script state. A likely target is:
- Load and render the first map and its initial UI.
- Populate the units required for the opening state.
- Select one unit and display its relevant state.
- Perform one legal move or scripted action.
- Resolve one combat or event interaction if the natural opening reaches one.
- End one player action/turn and return to a stable input boundary.
The exact acceptance path must follow the installed game's first dungeon rather than forcing this example
shape. Items, skills, magic, AI, win/loss, deployment, and progression are added only as the selected path
requires them.
Completion evidence combines original-game observation, executed-opcode/call traces, visible map/UI output,
and before/after global-state comparisons for the action.
### DEBUGMAP field-entry result (2026-07-21; manually validated)
The shipped `DEBUGMAP.BIN` path is useful for the first bounded field slice, but it is not treated as a
replacement for the natural campaign path. It performs substantial script-authored setup itself: it creates
the test units, writes stage id `0xa5` to `G[0x4dfbc]`, fills the field-mode globals, writes system-flow
request `G[0]=3`, and returns so SYSTEM4 enters `FIELD.BIN`. Runs launched from TITLE also retain the real
SYSTEM4/INIT tables. A future discrepancy in party, inventory, stage, or progression state may still be an
unpublished debug-level prerequisite; do not invent a seed unless its missing producer is proven.
The first observed field discrepancy was a black central map while the surrounding field UI and minimap
input remained alive. `DRAWMAP.BIN` is 23/23 opcodes handled and `RENDERMAP.BIN` is 31/31. FIELD's missing
opcode `0x249` loads universal raw map sheets `0x32da..0x32dd`
(`SO005`/`SO007`/`SO008A`/`SO007A`) into surfaces `0x3e..0x41`, after which DRAWMAP binds its generated tile
objects to those surfaces. Skipping the loader left valid retained objects pointing at empty surfaces.
The first retest after adding `0x249` showed the complete SO005 sheet enlarged over a grey field. The native
mode-1 class is now fully identified as a large-image tiled wrapper over the same decoded pixels, ruling out
a special spritesheet or blend interpretation. Two independent presentation gaps caused the retest:
- native treats a zero-area draw-texture source rectangle as an empty draw; the port incorrectly expanded
it to the entire source image, exposing FIELD's intentionally invisible SO005 prototype object;
- FIELD's camera depends on the shared retained-object range transform. Corrected `0x229` selects the map
handle range and anchor (it is not a per-object position opcode), while newly implemented `0x22a`,
`0x22c`, and `0x22d` apply immediate zoom, immediate translation, and animated zoom to that range without
moving the surrounding UI.
The related native `0x22b`/`0x22e` range-rotation setters have zero Himegari corpus calls and need no runtime
implementation yet. The remaining `DRAWMINIMAP` gap is `0x207` (eight calls) and is confined to minimap
work; FIELD's other 14 static gaps do not produce the main terrain layer. Installed-asset decode,
range-isolation/animation, VM dispatch, full engine tests, and the threaded Godot selftest pass. Manual
acceptance confirms that DEBUGMAP now displays the dungeon map correctly; the earlier full-sheet overlay is
gone and the field presentation remains operational after the camera-transform correction.
Drag-panning exposes one bounded presentation follow-up. FIELD op `0x86` at `0x2e4d` selects raw cursor
`0x32ce` on pan entry and op `0x87` clears it on exit. That catalog entry is the installed 4-bpp color
`CURSOR09.CUR`, whereas the port originally decoded only the eight 1-bpp ADV cursors. This was a
decoder-format gap, not missing or EXE-embedded artwork. The CUR decoder now handles both installed 1-bpp
and 4-bpp formats with independent XOR/AND strides; archive-backed pixel/hotspot tests pass. Manual
acceptance confirms the gripped cursor now displays correctly during drag-panning.
A follow-up full-corpus analytics audit corrected a metadata artifact: ops `0x228`, `0x22f`, `0x231`,
`0x232`, `0x239`, and `0x23f` had detailed native RE but still retained opaque semantic names, while
`0x1a8` was an unnamed explicit no-op. Their semantic names are now current, and native proves `0x1a8`
is a structural marker whose handler only records instruction length. The same audit exposed a real runtime
gap, now closed: `0x23f` queries a loaded surface's DirectShow stop position in truncated integer
milliseconds (all 23 sites are associated with a preceding `0x236`). On a freshly opened graph the stop
position normally equals its duration; FIELD uses it for 16 ms animation scheduling. The port now queries
that value during synchronous movie-graph initialization and retains it per movie surface. Empty movie
slots return -1; unavailable timing metadata warns and returns -1 rather than exposing native's undefined
failure output.
The next visual comparison found all field-HUD numbers absent while their labels, bars, and source artwork
were present, plus apparently blank unit/weapon text. Native and script-side tracing resolves this into two
bounded opcode gaps rather than missing game state:
- `0x13a` registers one of 11 `(surface, atlas x/y, digit width/height)` styles, and `0x23b` expands a
decimal value into retained per-digit objects with zero-pad/center/left/right layout flags. `DRAWCHP.BIN`
contains eight style registrations and 22 numeric draws covering the visible turn/control/mana/level and
HP/SP/FS fields. Skipping both opcodes precisely explained the blank values.
- `DRAWCHP.BIN` already reads and submits the unit and weapon strings to `0x204`, but its two preceding
`0x1a6` centering calls were unimplemented. Native `0x1a6` returns `strlen(CP932_bytes) >> 1`; skipping it
places the strings at x=257 on a 263-pixel scratch surface, so the existing compositor clips them.
The exact native contracts and addresses live in `docs/engine-re.md`; opcode-source metadata is in
`vm-map/opcodes.toml`. The shared implementation is now landed in the VM/retained-graphics model: the style
registry holds the 11 EngineCtx records, decimal glyphs become ordinary retained objects, and `0x1a6`
measures the configured native encoding (CP932 for SYS4). Focused coverage plus the full 278-test engine
suite, zero-warning Godot build, and threaded frontend selftest pass. The next field action is the manual
visual recheck of the same `DEBUGMAP` HUD before pursuing any state seeding.
That recheck confirms the numeric HUD and centered unit/weapon strings are restored. The next visible
discrepancy is shared menu text placed too far right, reproduced by both FIELD's three-choice wait/retreat
popup and TITLE's shipped developer menu. Both routes use `BUNKI.BIN`'s temporary-surface renderer rather
than the ordinary VN overlay. Native RE identifies the missing input as opcode `0x2c5`, raw byte-string
length: BUNKI uses it to size the panel and compute a common primary-label x origin. With the opcode skipped,
the destination stays zero; the FIELD popup shifts 53 pixels right and the longer developer menu incorrectly
remains at its 240-pixel minimum. Corrected native/port screenshots reveal a separate exact one-row vertical
shift: BUNKI uses missing opcode `0x195` (`string-not-equals`) to test its optional title against the empty
string. Because a skipped opcode leaves its destination untouched, the final test reuses a nonzero graphics
handle and falsely advances the choice cursor by 30 pixels. Both shared operations are now implemented with
focused CP932/NUL, operand-resolution, and stale-destination regressions. All 281 engine tests, the zero-warning
Godot build, and threaded selftest pass. The next bounded action is a visual recheck of the DEBUGMAP popup and
TITLE developer menu.
The subsequent DEBUGMAP deployment-picker comparison exposed a separate numeric renderer in
`DRAWENP.BIN`. Its red unit-information card draws labels and `/` separators through `0x204`, but draws
level, HP/SP/FS pairs, and both stat columns through 16 calls to opcode `0x205`. Native `0x205` formats a
fixed-width signed decimal field, applies zero-pad/alignment/half-width flags, adjusts the x anchor for
suppressed leading cells, and rasterizes the result into the same temporary surface using current text
style. The shared VM implementation now covers that path; focused tests use DRAWENP's exact level call and
the observed half-width, zero-padded, and full-width variants. DRAWENP is now 611/611 instructions handled;
all 284 engine tests, the zero-warning Godot build, and threaded frontend selftest pass. The manual
deployment-card comparison now passes.
### Post-DEBUGMAP script audit and next slice (2026-07-21)
The next slice should prove one complete **player attack and battle presentation** from the working
DEBUGMAP field: deploy a unit, enter its action flow, choose a reachable target, resolve one `BTL` exchange,
observe the HP/damage presentation settle, and return to an interactive `FIELD`. This is a stronger next
gate than expanding save/configuration or polishing isolated menus because the underlying gameplay scripts
are already much closer to complete than their presentation suggests:
- `CALCSCOPE`, `CALCARR`, `CALCBTPARAM`, `CALCREVISE`, `CALCOCC`, and `CALCDMG` are fully handled; so are
`BTANINIT`, `SETEN`, `REMOVECH`, and the ordinary map renderers.
- `BTL` has 2,194 of 2,215 static instructions handled (including one safe no-op) and 78 of 85 distinct
opcodes handled. Its remaining cluster is presentation-oriented: elapsed-time sampling (`0xd0`),
frame-time sampling (`0x23c`), movie
completion polling (`0x23a`), animation-service flags (`0x24e`), result-string construction (`0x193`),
profile bookkeeping (`0x1a2`), and delayed combat voice playback (`0x2c0`).
- `SELACT` has only five calls to missing `0x191`, now proven to be signed absolute value. `ADDEXP` is
similarly blocked mainly on string concatenation/decimal conversion plus two frame-time samples.
- FIELD itself has only 31 unhandled instructions out of 7,933. Save/load/profile and read-skip services
account for several of those and need not block the first attack loop.
The implementation order inside that slice should be evidence-driven and checkpointed: first `0x191` plus
the battle clock/frame cluster (`0xd0`, `0x23c`, `0x23a`, `0x24e`), then `0x193`/`0x1c8` for battle and EXP
messages. The sole `0x2c0` site is now proven audio-only, so delayed combat voice can follow the visible
exchange rather than block it.
The existing Godot `FrameClock` is already the correct millisecond timebase, so this does not require a new
scheduler. Defer `0x1a2` profile persistence unless the attack trace proves its value affects same-session
combat state.
One independent low-risk cleanup is now precisely scoped but should not displace the attack gate: opcode
`0x207` is a clipped surface-to-surface rectangle copy. Its 15 corpus calls restore eight minimap marker
copies plus STATUS/READICON/DRAWTIP composition. It is a good first commit or visual checkpoint within the
next work period, but it does not unlock combat state. After the first player attack returns cleanly to
FIELD, the next decision gate is enemy-turn/end-turn behavior, then stage-clear/win-loss transition—not
save/profile breadth.
The subsequent manual movement check found and closed a prerequisite earlier in that path. `MVSEEK`'s three
effectful gaps (`0x132`-`0x134`) are native reset/enqueue/try-dequeue operations on one of 11 engine integer
FIFOs. With the trio stubbed, queue 0's packed-coordinate flood fill wrote only the origin and exited because
the dequeue-success local stayed zero. That produced the exact observed state: Wait remained available on
the current tile, but no blue range overlay or other clickable movement target existed. The VM now owns the
11 FIFO slots and implements all three operations. Focused service tests plus seeded executions of the real
`MVSEEK.BIN` and `ATSEEK.BIN` prove movement costs and attack distances expand to neighboring tiles; both
scripts are now 100% handled. All 298 engine tests, the zero-warning Godot build, threaded selftest, opcode
validation, and full-corpus decode validation pass. The manual movement/overlay recheck is the next acceptance
gate, followed by the pending one-attack return to FIELD.
That first recheck remained broken because the direct worker tests bypassed SYSTEM4's persistent service
frames. SYSTEM4 uses missing `0x06` to preload ATSEEK, SETROUTE, and MVSEEK into frame slots `0x1d`-`0x1f`;
FIELD uses missing `0x08` to invoke them. Native RE proves that `0x06` allocates a script in a selected slot
without running it, while `0x08` restarts that slot at codebase and returns to the caller without discarding
its local bank. Both are now implemented as reusable VM frames and cleared on root reload. A focused test
proves restart/local persistence, and a bridge regression drives the real MVSEEK through the exact
`0x06(0x3381,0x1f)` / `0x08(0x1f)` ABI. All 300 engine tests, opcode validation, the zero-warning Godot
build, and threaded selftest pass. Manual DEBUGMAP acceptance now confirms that the complete live path shows
reachable-tile overlays, accepts movement, and reaches working player combat. The next bounded work is to
investigate the concrete combat discrepancies found during that acceptance run.
### Player-attack runtime frontier implemented; combat reached (2026-07-21)
The selected attack-path opcode cluster is now implemented. `0x191` preserves native signed-32-bit absolute
value behavior (including `INT_MIN`); `0xd0` and `0x23c` sample the shared monotonic/frame clock; `0x23a`
queries live movie-surface completion; and `0x24e` retains animation-service flags, including bit 1's
suppression of `0x243`. `0x193` and `0x1c8` now build aliased battle/progression strings, while `0x2c0`
replaces and services one delayed combat-voice request on the Godot frame clock.
The independent `0x207` checkpoint also landed as a real mutable-surface path. Created textures own RGBA
buffers, static sources apply their load-time color key before copying, paired source/destination clipping
is platform-neutral, overlapping self-copies use stable source pixels, and the compositor resolves generated
surfaces by slot even though they have no asset resource id. This covers minimap markers and the shared
STATUS/READICON/DRAWTIP background composition family.
Static coverage after implementation is: SELACT 59/59 opcodes and 1,099/1,099 instructions; ADDEXP 45/45
opcodes handled (including its two proven safe markers) and 310/310 instructions; DRAWMINIMAP, STATUS,
READICON, and DRAWTIP are all 100%; BTL is 84/85 opcodes handled and 2,213/2,215 instructions handled when
its safe marker is included. BTL's only remaining gap is two `0x1a2` shared-profile bookkeeping writes.
Those remain intentionally deferred until save/profile ownership is implemented because neither write feeds
the current exchange.
Focused regressions cover exact dispatch, signed edge cases, string aliasing, clock rollover truncation,
movie polling, animation reset suppression, delayed voice operands, paired clipping, overlap, and colorkey
transparency. Manual DEBUGMAP acceptance reached player combat and exposed the next concrete frontier:
combat-effect movies do not play. The resolver/decoder diagnosis is canonical in
`docs/asset-resolution-re.md`; packed resolution is now corrected, with manual BTL validation and the
separate decoder decision remaining before enemy-turn/end-turn breadth.
**Universal packed resource resolver implemented; combat movie validation pending.** BTL's
`0x236@0x2b21` consumes universal packed MVB ids, exactly as texture, voice, script-load, and modal-movie
consumers do. Native Ghidra analysis shows that none of those paths applies an SC section base or fallback;
the port's former scene-first compatibility resolver was therefore generally wrong, not merely incomplete for BTL.
That explains each `movie unresolved BTL:...` warning and the secondary attempt to decode MPEG-backed
`MVB914.AGF` as a still image. A packed-catalog decoder probe also found a separate backend wall: the current
DirectShow graph handles `MVB914` (400x400) but rejects the reached 280x352 MVB001/MVB004/MVB955/MVB958
assets with `0x80040217`. Broader samples tie current compatibility to 16-aligned display widths, while 125
installed MVB assets use 280x352. Texture, voice, and both movie consumers now use universal packed
resolution; VM surface state and Godot caches retain the full selector. Focused tests prove SC0010's low
texture/voice ids and append-pack identity; 302 engine tests, a zero-warning Godot build, and threaded
selftest pass. Manual combat acceptance confirms the split: 400x400 MVB908 plays and completes, while
280x352 MVB961/MVB238 resolve correctly but DirectShow rejects their graph connection with `0x80040217`.
The user observed a stall after the sequence. Static BTL tracing proves failed `0x23f == -1` cannot create
an infinite callback count: the total horizon starts at `base_time + 1000`, and the explicit post-callback
loop polls `0x23a` until surfaces 7..10 are inactive. MVB908's stop log occurs after that loop. As a bounded
safety correction, decoder failure now becomes an explicitly completed zero-duration effect, while started
movies have a stop-time-based completion watchdog so a missing EOF cannot hold `0x21c` forever. **NEXT:**
replace DirectShow through the bounded FFmpeg plan below. The user confirmed that combat can still stall, but
that symptom remains tabled until the replacement removes DirectShow as a variable. Preserve created destination
dimensions and failed-movie identity through the backend change.
### Portable FFmpeg movie backend selected; bounded implementation plan (2026-07-21)
FFmpeg is selected as the single movie backend for stock content and future profiles. The exact native ABI,
dependency pin, corpus codec inventory, timing contract, and packaging rules live in
`docs/platform-portability.md`; this section owns execution order and acceptance.
1. **Completed — land the seam without changing behavior.** Add `IMovieDecoder` plus an injected factory, type
`MovieRuntime` against the interface, and adapt DirectShow temporarily. Cover synchronous metadata, frame
handoff, EOF, failure-as-completed, replacement, cancellation, and disposal with fakes. This commit must retain
current live behavior and gives the FFmpeg work a testable boundary.
2. **Completed — prove the native ABI in isolation on Windows x64.** Add the C shim and pinned shared FFmpeg dependency,
then open VFS-owned bytes without a temporary file. The initial gate decodes changing RGBA frames and positive
stop times from one formerly failing 280x352 effect (`MVB961` or `MVB238`), aligned `MVB908`, and 800x600
`CHAPTER`. It must also reject a truncated payload with a bounded diagnostic and survive repeated open/close.
Do not switch the live runtime at this step.
3. **Completed — implement managed pacing and switch the factory.** Decode on a cancellable worker using FFmpeg timestamps
and a monotonic clock, publish only due frames, delay EOF completion through the final presentation interval,
and retain the existing watchdog. Make FFmpeg the default with no dimension/effect dispatch and exercise both
non-modal `0x236` and modal/cancellable `0x20f` paths.
4. **Completed — run the installed-corpus gate.** Every one of the 213 MPEG payloads must open, report its expected display
dimensions and a positive stop time, produce a correctly sized RGBA frame, maintain nondecreasing timestamps,
reach EOF, and dispose within a bound. Normal tests use small project-authored 280-wide and aligned MPEG
fixtures so decoder behavior is never disabled when the original install is absent; the full local sweep is
an additional release/acceptance gate. Spot comparisons against DirectShow-compatible assets establish timing
tolerance before DirectShow is removed.
5. **Validate the live sequences, then delete DirectShow.** Recheck LOGO/OP cancellation, SC0000 CHAPTER,
aligned and 280-wide battle effects, combat cleanup, and the previously observed post-effect stall. Once those
pass, delete the COM declarations/temp-file path and Windows platform annotation rather than retaining a
fallback. If combat still stalls with FFmpeg, capture the VM/service coordinate after BTL cleanup and treat it
as a separate battle-timeline defect.
6. **Complete distributable packaging.** Bundle pinned dynamically linked libraries and exact license/source
provenance per target, validate loader isolation from machine-installed codecs, then add Linux/macOS build and
smoke gates. Timestamped PCM delivery and Godot `AudioStreamGenerator` integration were assigned to a later
movie-audio slice; that slice landed on 2026-07-25 as recorded below.
Steps 1 and 2 landed on 2026-07-21 without changing live playback: `Main` owns an
`IMovieDecoderFactory`, `MovieRuntime` is backend-neutral, and the default factory still constructs DirectShow.
The isolated Windows-x64 C ABI opens VFS bytes through seekable custom AVIO and decodes changing RGBA frames from
both formerly failing 280x352 effects (`MVB961` = 500 ms, `MVB238` = 866 ms), aligned `MVB908` (333 ms), and
800x600 `CHAPTER` (12016 ms). Truncated input is rejected with a bounded error and ten repeated open/decode/close
cycles pass. During the full-suite gate, direct swscale output into a managed array exposed native heap corruption;
conversion now targets an FFmpeg-owned aligned frame and copies only the exact visible RGBA rows across the ABI.
The complete 313-test suite, three additional 312-test concurrency-sensitive repeats, zero-warning Godot build,
and threaded selftest pass. Step 3 is the next bounded slice; do not infer that combat is fixed until the paced
backend is selected live and revalidated.
Step 3 landed on 2026-07-22. `FfmpegMovieDecoder` owns the native session on a cancellable background thread,
decodes at most one frame ahead, waits against a monotonic `Stopwatch` clock for each normalized PTS, preserves
newest-frame-wins handoff, and reports EOF only after the larger of declared stop time or the final frame interval.
Disposal interrupts a far-future frame wait and joins the worker; asynchronous decode failure records a diagnostic
and completes the decoder so AGE cannot remain blocked. `Main` now selects `FfmpegMovieDecoderFactory`, while the
Godot build stages the shim and its five local shared-library dependencies beside `Himegari.dll`. DirectShow stays
in-tree but is no longer selected; delete it only after the corpus and live acceptance gates. Deterministic fake-
clock tests cover due-frame publication, final completion, cancellation, and failure. Real paced probes cover
formerly failing 280x352 `MVB961` and aligned `MVB908`. A natural headless SYSTEM4 smoke played and released
`LOGO.AGF` at its reported 7288 ms, then opened 106919 ms `OP.AGF` and published its first frame before bounded
shutdown. The user then confirmed the opening movies play correctly in a normal windowed run. The complete suite
is 318 tests, the Godot build is warning-free, and threaded selftest passes. That established the prerequisite
for the installed-corpus gate recorded below.
Step 4 completed on 2026-07-22. `tools/movie-corpus-gate` selects MPEG program streams by signature from the
complete native-order VFS catalog and runs the unpaced FFmpeg session to EOF. The acceptance run discovered the
expected 213 assets and passed all 213 under the 30-second per-item bound; every stream reported independent
matching dimensions, positive duration/frame rate, correctly sized RGBA frames, nondecreasing timestamps,
changing imagery, EOF, and clean teardown. The run decoded 15,788 frames across all twelve installed dimension
variants in 8.1 seconds; the longest item, 263-second `ED.AGF`, decoded in 4.4 seconds. Detailed compatibility
evidence lives in `docs/platform-portability.md`, and reproduction/report options live in
`docs/tools-reference.md`. This moved acceptance to step 5's windowed SC0000 `CHAPTER`, aligned plus 280-wide
combat effects, combat cleanup, and prior-stall checks. DirectShow remains unselected but in-tree until those
manual checks pass.
The first post-corpus combat recheck still stalled after an apparently absent effect. The ordinary Godot log
ruled out an FFmpeg decode/EOF failure: both reached battle batches opened, published first frames, and stopped
(`MVB005`/`MVB952`/`MVB913`, then `MVB921`/`MVB126`/`MVB953`) with no decoder failure or watchdog. F6 was added as
an observe-only bounded JSON snapshot of the current/recent VM path, host waits, movie/surface lifecycle, and
blocking finite gfx channels.
The first F6 capture localized the stall exactly to BTL's `0x2492..0x2515` movie polling loop, parked at the
16 ms sleep `BTL@0x250a`. Gfx had no blocking timed presentation, and the only real decoder (`MVB033`, surface
44) was complete. Surfaces 7 and 8 nevertheless retained incomplete/no-frame registrations for already stopped
`MVB953` and `MVB126`, so op `0x23a` could never clear. The user's observation that the last effect frame remained
until replacement was the same ownership defect: movie frames, completion, and decoder dictionaries were keyed
by shared resource id even though BTL can bind/restart one asset through multiple surfaces. Releasing one binding
removed shared state while another surface registration survived forever.
Movie ownership is now keyed by a unique playback instance; resource id is asset identity only. Each surface
resolves its own instance frame/completion, replacing a surface invalidates late callbacks from only its prior
instance, and releasing one of two same-resource playbacks cannot affect the other. Blank pre-roll surfaces no
longer borrow a concurrent instance's frame, and console/timeline diagnostics include playback ids. Duplicate-
resource and replacement regressions pass, as do all 327 engine tests, the zero-warning Godot build, threaded
selftest, and the 213/213 unpaced corpus gate. A quick repeat of the same combat exchange no longer stalled.
The run did expose false `AGF decode failed ... expected an ACGF image` lines after successful first frames and
immediately before teardown. This was not FFmpeg failure: the compositor could retain a pre-release `GfxState`
snapshot for one frame after the host detached the movie binding, then send the `.AGF`-named MPEG id through the
still-image fallback. Runtime-learned movie resource typing now survives instance teardown and suppresses only
that invalid fallback; it does not retain the frame or decoder. The focused cleanup regression, all 328 engine
tests, zero-warning Godot build, and threaded selftest pass. **NEXT:** perform a longer combat/return-to-FIELD
acceptance run, then delete DirectShow if the remaining live gate stays clean.
An independent long-standing console-spam issue was also localized at the same SC0000 third-CG boundary.
`play-sound-effect 0x28` loads `E0808.WAV`, whose RIFF `LIST/INFO` metadata uses CP932; Godot assumes those
unused fields are UTF-8 and logged each invalid byte twice because the script loaded the effect on two channels.
This was neither the AE glow renderer nor corrupt PCM. The complete extracted corpus contains 61 affected INFO
chunks among 238 valid WAVs, which also explains intermittent combat spam. A Godot-only adapter now strips only
INFO metadata from the transient buffer immediately before `AudioStreamWav.LoadFromBuffer`; the shared engine,
VFS bytes, and functional RIFF chunks are unchanged. Synthetic preservation tests and the real E0808 regression
pass, as do all 331 engine tests, the zero-warning Godot build, and the headless loader selftest with no Unicode
warnings.
**Mutable-surface fill/blend regression corrected.** The first visual recheck exposed BUNKI's menu interior
as transparent. SYSTEM4 creates 800x600 surface 3 and fills it opaque white through `0x20b`; the metadata-only
host fill left the new pixel buffer transparent. Implementing the fill alone made the panel solid gray and
covered FIELD's paper minimap backing, exposing the second half of the contract: native created/render-target
surfaces are not loaded mode-0 textures and are not surfaceless fills. BUNKI draws created surface 3 with
alpha `0xd0`; FIELD draws it beneath the minimap with alpha `0x40`. Created-surface mode 0 now uses packed
alpha as opacity and packed RGB as multiplicative modulation, while loaded textures retain their established
opaque/alpha-inert behavior. Created surfaces also carry no-colorkey `-1` rather than key-black zero. Focused
regressions cover clipped RGBA replacement and the exact created-surface blend classification; all 295 engine
tests, the zero-warning Godot build, and threaded selftest pass. Manual recheck confirms both the translucent
BUNKI panel and minimap paper are correct. The movement/overlay recheck described above is now the next manual
gate on the one-player-attack acceptance path.
### MPEG movie audio implemented; audible acceptance in progress (2026-07-25)
The FFmpeg shim ABI is now version 3. Each in-memory MPEG payload feeds independent seekable video and audio
demuxers, preserving concurrent decode without temporary files, and exposes timestamped interleaved stereo
float PCM resampled at the source rate. Video and audio timestamps share the same normalized media origin.
The managed decoder owns bounded video and PCM queues plus separate workers; audio-bearing movies use the
Godot audio-device clock as the presentation master, while video-only movies retain monotonic stopwatch pacing.
Completion waits for the final video interval and for decoded PCM to be submitted through the shared zero-based
presentation endpoint.
ABI v3 adds a synchronous dual-demuxer initial seek used by opcode `0x241`. Native seek lands on preceding
indexed packets; managed preroll selects the video frame active at the requested position, sample-trims audio
to the same boundary, and rebases remaining pacing and completion. This preserves CALLBACK_LOAD's terminal
frame restoration without decode-from-zero latency or stale pre-seek audio.
Godot creates a per-playback `AudioStreamGenerator`, compensates for output latency, handles timestamp gaps and
overlaps, and tears it down with the corresponding playback instance. Native movie flags route sound to the
Movie bus by default, with exact overrides for mute (`0x10000`), Music (`0x20000`), SFX (`0x40000`), and Voice
(`0x80000`). F6 diagnostics now expose audio format, decode/submission state, route, clock, and underruns.
The full installed-corpus gate passes all 213 movies: 29 audio-bearing assets decode 17,537 blocks /
18,185,856 stereo PCM frames at 44.1 kHz with nondecreasing timestamps and non-silent signal, while all 184
video-only assets remain audio-free. At the initial landing, focused real-stream tests covered MP2 and MP1
content, all 404 engine tests passed, the Godot build was warning-free, threaded selftest passed, and a natural
headless boot completed LOGO with its audio path active before opening OP. This moved acceptance to audible,
synchronized LOGO/OP and representative CHAPTER/MVS playback in a normal windowed run; DirectShow remained
unselected but in-tree until that live gate.
The first normal windowed LOGO/OP run confirmed that audio reaches the intended output, but it sounded
crackly/warbled. Corpus endpoints showed continuous decoded PCM; the fault was presentation alignment.
Audio PTS crosses the ABI in whole milliseconds, losing up to 44 samples of precision at 44.1 kHz, while the
initial sink inserted or dropped that tiny discrepancy at every MPEG block boundary. Established timelines
now tolerate 2 ms of timestamp quantization and remain sample-contiguous, while initial offsets and material
later gaps/overlaps still insert silence or trim PCM. A regression simulates all 4,093 OP blocks without a
splice and separately pins real discontinuity handling. The repeat LOGO/OP run and SC0000 CHAPTER playback are
audibly clean. Together with signal-bearing decode of all 29 audio streams, this closes the movie-audio acceptance
gate: missing, distorted, or unsynchronized audio is now a runtime bug. The unused DirectShow implementation,
COM/temp-file adapter, compatibility test, and managed Windows platform annotations are deleted. **NEXT:** add
Linux/macOS FFmpeg builds and packaging smoke gates when work returns to cross-platform distribution.
Post-cleanup validation passes all 405 engine tests, the warning-free Godot build, threaded selftest, opcode
lint/tooling, and the 213/213 installed video/audio corpus gate.
The shared Windows/Linux residual A/V report was investigated on 2026-08-11. A read-only native LOGO trace
proved DirectShow supplies AGE zero-based 33.3667 ms video samples and contiguous 26.1224 ms audio samples; an
early hook measured the first non-silent native PCM block at 2,351 ms. Sparse native video fingerprints match
FFmpeg frames at equal timestamps and expose changing native frames during 0--600 ms. The shim had skipped those
frames by rewinding to the video stream's declared 600 ms `start_time`, then managed pacing displayed frame 600 at
time zero while audio retained the common origin. Rewinding video to the format timeline origin recovers 218 LOGO
frames beginning at PTS 0 instead of 200 beginning at 600. Its audio onset around 2,380 ms again precedes the
first `E` at PTS 2,502 ms. Completion separately retains the terminal image until queued audio drains through the
full graph endpoint. Focused and real-asset regressions pin both corrections; manual LOGO/OP confirmation remains.
**CORPUS-WIDE OPCODE HARDENING PASS STARTED (2026-07-28).**
With numbered-save implementation complete and further live save testing explicitly deferred, the active
breadth pass now ranks every Himegari-observed VM fallthrough across all 481 decoded scripts. The starting
inventory has only 25 distinct effectful gaps. The first tranche closes the two widest independent holes:
- `0xba` occurs 334 times in 100 scripts. Native `op_0xba_sfx_start_loop@0x420290` calls the same retained
SFX start worker as one-shot `0xb5`, but with logical loop mode 1. The host seam now preserves that mode
and Godot rewinds the WAV stream at EOF.
- `0x1fe` occurs 187 times in 47 scripts. Native
`op_0x1fe_set_rotation_current@0x422700` immediately replaces the retained object's current axis-angle
rotation. The existing compositor already sampled that field; the VM now writes it directly.
Together these handlers close 521 formerly skipped instructions and reduce the corpus-wide effectful
fallthrough inventory from 25 to 23 distinct opcodes. Focused regressions distinguish one-shot/looping SFX
starts and prove immediate rotation does not arm the delayed `0x21f` channel. The opcode registry and
generated references are current, and the native `/v2` image is annotated and saved.
**Cyclic scale `0x233` implemented (2026-07-28).** The retained model now owns the native channel
independently of immediate and delayed one-shot scale. It samples the exact triangular
identity-to-target-to-identity phase, remains frame-driven during visible waits, survives object cloning,
and enters the separately anchored cyclic product after one-shot translation and before cyclic rotation.
The VM handler closes all 31 skipped sites across DEBUGADV and eight ordinary ADV scenes, reducing the
effectful fallthrough inventory from 23 to 22 distinct opcodes. Focused tests cover dispatch, percentages,
odd/even period timing, continuous-presentation classification, and matrix order; the full engine suite,
warning-free Godot build, and Himegari-targeted threaded selftest pass (468 engine tests).
**CONFIG mixer tranche implemented (2026-07-29).** `0xc5`/`0xc6` now get/set master, music, SFX,
voice, and movie volume in native basis points; `0x1ba`/`0xc7` set/get the four non-master route flags.
The registry is shared across fresh scene VMs and persisted in AGE's native CP932 `SYS4REG.INI`. A shared
path resolver models SYS4INI's independent `SAVEPATH`/`REGFILEPATH` choices; Godot redirects Himegari's
related profile root to `user://`, yielding `user://SAVE` and `user://SYS4REG.INI`. The compatibility
writer updates only the nine audio keys and preserves every unrelated option. Raw music state retains
native `2 ↔ -1` band toggling. Godot applies
gains through its nested audio buses, so master and category volume compose, and route changes immediately
stop/mute active playback according to the native worker distinctions. All 37 CONFIG sites now dispatch,
reducing the effectful fallthrough inventory from 22 to 18 distinct opcodes. Eight focused tests cover
native defaults, category isolation, getter/setter round trips, idempotent routes, cross-VM state, invalid
selectors, native and redirected path resolution, CP932 INI preservation, and persistence. All 476 engine tests, the
warning-free Godot build, opcode lint, and the Himegari-targeted threaded selftest pass.
**Post-tranche rerank:** a fresh 481-script aggregate found exactly 18 effectful gaps totaling only
39 instructions. The widest were `0x22` (eight sites across seven scripts), `0x230`
(six sites across two scripts), then `0x21` and `0x1b2` (three sites each); every other gap had at most two
sites. CONFIG itself is now 1659/1665 instructions implemented, with four unrelated calls remaining
(`0x142` twice, `0xb7`, and `0xb8`).
**Next-opcode investigation complete (2026-07-29):** `0x22` is the mode-1 half of AGE's blocking
single-surface black-fade pair, not a larger subsystem. `0x21(surface,timing)` fades black to the captured
surface; `0x22(surface,timing)` fades the captured surface to black. They share `0x25`'s already-modeled
timer conversion and full-frame snapshot lifecycle. All 11 combined Himegari sites use `(1,30)`, about
480 ms, across menu entry/exit paths. The existing `LegacyScreenTransition` host seam can own the pair,
but it needs an explicit black endpoint rather than its mode-4 missing-surface fallback.
**Black-fade pair implemented (2026-07-29):** `0x21` and `0x22` now dispatch through the shared blocking
full-frame transition path. The compositor represents the solid-black side explicitly as an empty captured
object list over its normal opaque-black clear: source-empty for black-to-surface and target-empty for
surface-to-black. This preserves mode-4 `0x25`'s separate missing-target fallback, shares the native timing
conversion and exact terminal-frame publication, and closes all 11 sites. The remaining effectful inventory
is 16 distinct opcodes / 28 instructions. All 477 engine tests, opcode build/lint, the zero-warning Godot
build, and the Himegari-targeted threaded selftest pass.
**Legacy-transition Skip/click lifecycle corrected (2026-07-29):** Ghidra's outer engine tick exposes the
shared mechanism behind native skippable effects. With ADV fast-forward already active, `0x21`, `0x22`, and
`0x25` publish their terminal endpoint without starting the timed service. Otherwise run-state bit 8 owns
the transition; when `SYS4REG.INI` `system:EffectSkipOnClick` is enabled, logical action 4 is consumed and
the active effect tick receives delta `0x10000000`, forcing that same endpoint. The port now carries the
fast-forward state into legacy-transition dispatch and prioritizes action 4 over retained ADV hotspots
while a transition is parked. A focused regression covers forced black-fade and crossfade dispatch.
Validation passes 519/519 engine tests, opcode build/lint, the zero-warning Godot build, clean diff
checking, and the Himegari-targeted threaded selftest.
**Retained CG-fade click completion corrected (2026-07-29):** the legacy service above was only one of two
native paths and did not cover the story CG fades observed in the port. The outer tick's separate
run-state-`0x400` service applies `EffectSkipOnClick` to the complete ordinary finite retained-animation set:
queued type-0 surface commands plus object color/alpha, scale, rotation, and translation channels. The port's
click handler had incorrectly forced only the surface-command subset, so CGs implemented as object alpha or
matrix fades still consumed their full duration. Interactive advance now completes the complete native set;
activating Ctrl/Skip during the wait uses the same endpoint path. Ambient loops, op-`0x242` detached channels,
and movie masks remain unaffected. Focused exclusions and endpoint regressions pass in the 523-test engine
suite, and the Godot build is warning-free.
**Retained transition wait ownership corrected (2026-07-29):** playthrough testing with Skip held exposed
that endpoint completion alone was insufficient. Skip could force an ordinary fade invisible, yet the host
continued waiting on the broad presentation predicate until a movie-backed/protected channel or the original
duration ended. Native clears run-state `0x400` before requesting endpoint completion, so interpreter resume
is independent of whether excluded presentation remains active. The host now records that explicit service
bypass, exits the transition wait immediately when the action is accepted, and reapplies an already-active
Skip state at every new retained-transition boundary. Focused coverage distinguishes service-flags bit 0
(reject the click) from bit 1 (accept resume but suppress endpoint forcing) and proves movie masks continue
asynchronously after wait bypass. A direct `SC0000@0xc01` runtime trace—the page before the ritual CG
sequence—shows its four click-skipped retained waits completing in 14, 14, 14, and 34 ms despite scripted
durations of 150/890/300 ms; the later 1.587-second interval is the next page's ordinary glyph reveal at
`SC0000@0xe04`, not an invisible transition wait.
**Held-Skip pacing corrected (2026-07-29):** comparing held logical action 6 against the click trace at
`SC0000@0xc01` exposed a separate delay outside the transition service. The Godot host was waiting for one
rendered-frame pulse after every VM opcode while Skip was active, stretching hundreds of burst-fast CG
setup/cleanup instructions into an invisible multi-second hold. Native live evidence instead measures roughly
7,738 operand fetches/sec under Ctrl, and Ghidra shows the physical action-6 mask polled independently of the
`0x19b`/`0x19c` persistent-Skip lifecycle. Per-op Skip pacing is removed, and a held Ctrl/C/Backspace channel
now survives `0x19b`; presentation, sleep, and input boundaries remain the only wall-clock owners. The
post-fix held-action trace reaches page 14 from `SC0000@0xc01` in 83 ms and takes the native
`0x243`+`0x20c` endpoint branch at every intervening transition. The engine suite passes 525/525.
**Nested ADV input ownership corrected (2026-07-29):** the first-dungeon boss event reaches ordinary
dialogue at `SC0600@0x2343` and parks at `wait-for-input@0x234b`, but FIELD's registered timed mouse
callback remains stored in its suspended parent script frame. The frontend incorrectly treated any stored
raw callback as current modal input ownership. It consequently hid the wait indicator and withheld normal
mouse/keyboard/joy advance signals from the nested SC0600 page, producing a softlock immediately before
combat.
Raw callback ownership is now frame-relative: it is active only while its owning script frame is the
currently executing frame. The dormant FIELD registration survives the nested call and becomes active
again when FIELD resumes, while SC0600 retains its ordinary ADV marker and input path. A synthetic
parent-callback → child-dialogue regression preserves both halves of that lifetime. The engine suite passes
526/526, the Godot build has zero warnings, and the Himegari-targeted threaded selftest passes.
**Post-boss `A1215.WAV` lockup corrected (2026-07-29):** after the first boss,
SC0010 executes `play-sound-effect 0x125` at `0xea1`. The packed `DATA1/A1215.WAV` entry is 323,009 bytes:
a valid 157,940-byte RIFF, then a multipart-upload header, then a second valid RIFF. Godot's whole-buffer WAV
importer walks into the appended header, treats `Cont` as a chunk id with an impossible size, and loops through
`FileAccessMemory.seek` errors on the main thread.
AGE does not special-case this resource. Its WinMM decoder (`wav_decoder_open@0x488790`) descends into the
first `RIFF/WAVE`, finds `fmt ` and `data` within that parent, saves the first `data` chunk's declared size,
and `wav_decoder_fill_buffer@0x4889b0` stops after exactly that many bytes. The appended upload material and
second RIFF are unreachable.
`RiffWaveSanitizer.PrepareForGodot` now applies that first-RIFF boundary to the transient decoder buffer after
validating every declared child, then performs the existing INFO-metadata removal within that boundary.
Archive/VFS bytes remain pristine, and invalid RIFFs are returned unchanged. Synthetic concatenation and
installed-A1215 regressions prove the exact prefix and unchanged PCM payload. Validation passes 528/528 engine
tests, a zero-warning Godot build, and the Himegari-targeted threaded selftest, which loads the real A1215
buffer through Godot and reports `first-riff-boundary=ok` without seek spam.
**Stage 01-01 cumulative STEP-LIMIT corrected (2026-07-29):** after a reveal-zone event, the persistent
SYSTEM4 run reached its artificial 20,000,000-instruction cap. `SYSTEM4 P592` correctly identifies the last
ADV boundary (`SC0600@0x33fd`, followed by cleanup and return), but the page map cannot identify code that
runs after that page. The final page was recorded about 29 seconds before the halt, so the existing report
cannot distinguish an SC0600 cleanup loop from resumed FIELD processing.
Godot now retains the deepest frame chain before a halted frame unwinds and automatically emits a bounded
STEP-LIMIT report: exact script/offset/opcode, nested frame chain, hottest sites in the final 128 instructions,
and the final 16-instruction sequence. It also writes the ordinary full stall snapshot as
`user://diagnostics/step-limit-<timestamp>.json` and copies its coordinate/path. Focused formatter/stack
regressions made the next reproduction decisive: the cap fired at `DRAWMINIMAP@0xc4` under
`SYSTEM4 > TITLE > SAVE > SYSTEM4 > FIELD > DRAWMINIMAP`, 127.452 seconds into the run. Its final trace is
the ordinary bounded minimap scan: X increments at `0x77`, exits after 25, and the outer scan spans a fixed
101 rows. `DRAWMINIMAP` was only where the boot-to-session counter happened to reach exactly 20,000,000;
FIELD polling and redraw calls had accumulated those steps normally.
`GodotVmOptions` now gives persistent interactive runs `long.MaxValue` rather than a test-harness lifetime
ceiling. Selftest and CLI/corpus paths keep their bounded diagnostic budgets, and the automatic halt report
remains available for bounded Godot modes. The focused policy regression and all 531 engine tests pass; the
Godot build is warning-free and the Himegari-targeted threaded selftest passes.
**Numbered save/load playthrough acceptance confirmed (2026-07-29):** following the native-layout
restoration, retained-gfx, frame-continuation, script-prologue, and shared-profile lifecycle fixes, the user
has exercised repeated saving and loading throughout the live early-dungeon playthrough without another
persistence failure. This closes the generic “richer dungeon save stress” acceptance item for the current
slice. Native-layout gameplay save/load is provisionally accepted; future failures should be tracked as
specific edge cases rather than leaving the entire persistence effort unconfirmed. Extended JSON
inspection/export, mod-data namespacing, migrations, and broader platform packaging remain separate work.
**Cyclic reset implemented (2026-07-29):** `0x230(handle)` now gets or creates the retained object,
disables the four looping channels represented by the compositor, and clears the complete native
start/period block—including the preserved raw state for the currently unmodeled second cyclic matrix.
Base/current transforms, cyclic targets, and finite one-shot channels remain intact. This closes its six
sites (five DEBUGADV demonstrations and one FIELD movement setup), reducing the remaining effectful
inventory to 15 distinct opcodes / 22 instructions. All 479 engine tests, opcode build/lint, the
zero-warning Godot build, and the Himegari-targeted threaded selftest pass.
**Diagnostic-output trio implemented (2026-07-29):** `0x1b2(value)` now formats string, signed-integer,
pointer, and float operand families into an EngineCtx-lifetime accumulator; `0x1b3` appends exact CRLF;
and `0x1b4` appends the native interpreter coordinate, synchronously presents it, then clears only after
the host returns. The release image's optional debug-name/line tables are never populated, so the port
matches its deterministic `LINE=-1 COMMAND=-(436)` suffix while retaining live `FILE`, dword `ADDRESS`,
and zero-based frame `DEPTH`.
`GameSession` carries the accumulator across fresh scene VMs, preserving FIELD's unpresented invalid-state
line and SYSTEM4's post-clear newline. Interactive Godot dispatches `OS.Alert` on the main thread and parks
the VM until dismissal; headless `CaptureHost` records rather than drops the effect. Focused regressions
cover operand formatting, append/show/clear ordering, context formatting, headless presentation, and
cross-scene lifetime. All 482 engine tests, opcode/EngineCtx build and lint, the zero-warning Godot build,
and the Himegari-targeted threaded selftest pass. The six instructions are closed, reducing the remaining
effectful inventory from 15 distinct opcodes / 22 instructions to 12 / 16.
**NEXT:** rerank the remaining 12 effectful opcode gaps and investigate the widest coherent slice.
**Remaining-gap rerank and forced-BGM investigation (2026-07-29):** a fresh scan of all 481 scripts
confirms 12 effectful gaps totaling 16 instructions. `0xb7`, `0xb8`, `0x142`, and `0x24d` occur twice
each; `0xb9`, `0x137`, `0x144`, `0x149`, `0x241`, `0x248`, `0x2c6`, and `0x2c8` are singletons. Raw
frequency no longer identifies a subsystem, but native RE does: `0xb7`/`0xb8`/`0xb9` are the complete
forced-BGM lifecycle around the already-implemented `0xbf`/`0xc2` path.
`0xb7(track_or_zero)` force-restarts looping playback, including the same retained track and the zero
operand's "restart current" form; `0xb9(track_or_zero)` is the corresponding one-shot start; and `0xb8`
stops/releases and clears the retained track. Each first completes/cancels a pending BGM fade. CONFIG's
BGM029 preview needs the force-loop/stop pair, CALLBACK_LOAD uses zero to restart restored music, MMODE
stops current music on entry, and STAGECLEAR uses the one-shot form for BGM025. The existing Godot music
player and retained VM track provide the whole implementation seam; it needs an explicit loop/restart
request while ordinary `0xbf` remains same-track-idempotent.
**NEXT:** implement and test `0xb7`/`0xb8`/`0xb9` together. The likely follow-up is INPUTNAME's CP932
string/UI cluster (`0x144`, `0x2c6`, `0x2c8`), but its modal host contract should be finished only after
the bounded BGM tranche lands.
**Forced-BGM lifecycle implemented (2026-07-29):** `0xb7` and `0xb9` now force-replace the active BGM
stream with logical loop and one-shot mode respectively, including zero's native alias to the retained
track; `0xb8` stops/releases and clears that track. Ordinary `0xbf` remains idempotent for same-track
FIELD cleanup. Godot cancels deferred fades on replacement, applies the start mode through the OGG loop
flag, and makes stop release the stream rather than leaving a silent source. The same stop seam now closes
`0xc2` target zero after its blocking fade, matching native source release instead of retaining a muted
Godot stream.
All five sites now dispatch, reducing the effectful-gap inventory from 12 opcodes / 16 instructions to
9 / 11. A focused VM regression covers forced same-track restart, zero reuse, both modes, replacement,
stop/clear, and restart after stop. All 483 engine tests, opcode/EngineCtx lint, the zero-warning Godot
build, and the Himegari-targeted threaded self-test pass; the self-test now also validates OGG loop mode
and true stop/release.
**NEXT:** investigate INPUTNAME's `0x144`/`0x2c6`/`0x2c8` cluster as one modal name-entry and CP932
string slice.
**INPUTNAME string/input slice investigated (2026-07-29):** all three singleton handlers are now exact.
`0x2c6(out,string)` is Japanese-locale `_mbstrlen`; `0x2c8(out,string,start,count)` slices by CP932
multibyte-character positions while keeping lead/trail pairs intact. INPUTNAME uses them together to split
the current name into its eight script-owned display/edit cells.
`0x144(result_inout,initial_text)` is the separate native keyboard-entry button. AGE copies both strings
into bounded buffers and calls runtime host command 10. A read-only live probe resolved that callback to
`AGERC.DLL+0x1050`; the shipped DLL is now imported and annotated in Ghidra. Its command-10 resource dialog
initializes from operand 2, accepts at most 16 CP932 bytes, rejects every non-double-byte character, writes
valid accepted text to operand 1, and leaves operand 1 unchanged on cancel. This resolves the former
runtime-pointer uncertainty and also identifies AGERC as the owner of the previously studied command-8
LABEL service used by `0x140`.
The port has a bounded implementation seam: the two pure helpers use `VmOptions.NativeStringCodePage`,
while the modal op follows the existing diagnostic-message pattern (VM worker waits, Godot main thread owns
the dialog, accept/cancel signals completion). Implementing the trio removes 3 of the remaining 9 opcode
gaps and 3 of 11 instructions; no persistence or save-format work is involved.
**NEXT:** implement and test `0x144`/`0x2c6`/`0x2c8` together, including CP932 mixed-width helper cases,
the native 16-byte/full-width acceptance rules, accept/cancel behavior, and INPUTNAME's split/rejoin shape.
**INPUTNAME string/input slice implemented (2026-07-29):** the VM now dispatches all three opcodes.
`0x2c6` counts CP932 characters rather than UTF-16 units or bytes, and `0x2c8` slices encoded character
spans with native end clamping. A shared validator applies command 10's 16-byte limit first and then
requires every accepted cell to be a valid CP932 lead/trail pair.
`0x144` uses an explicit synchronous host result, preserving operand 1 on cancel and operand 2 in both
paths. Godot parks the VM worker while a main-thread modal editor owns input; invalid submissions leave
the editor open with AGERC's original error text. Headless hosts cancel safely by default. Focused
regressions cover mixed-width count/slicing, end clamping, valid and invalid names, accept replacement,
cancel preservation, and operand-2 preservation. The slice removes three gaps and three instructions,
leaving 6 effectful opcodes / 8 instructions. Validation passes 493/493 engine tests, opcode and
EngineCtx lint, a zero-warning Godot build, clean diff checking, and the Himegari-targeted threaded
`SELFTEST OK`.
**NEXT:** rerank the remaining six gaps; begin with the two-site `0x24d` and check whether adjacent
`0x248` belongs to the same native subsystem before choosing the next implementation boundary.
**MOVIE-MASK/TILED-SURFACE INVESTIGATION COMPLETE (2026-07-29):** `0x24d` and `0x248` are unrelated.
`0x248(edge)` sets the legacy mode-1 tiled-surface edge length; SYSTEM4 supplies 128, while the port's
contiguous RGBA surfaces do not need to reproduce AGE's old texture-size partitioning. It is an exact,
small state-parity implementation.
`0x24d(new_key,movie_slot,old_start,old_count,x,y,width,height,mode,movie_id,delay_ms,duration_ms)` is a
movie-driven per-pixel transition. Native retimes the MPEG to `duration_ms`, allocates a byte mask at the
requested size, and registers a blocking retained command that composites the old and new ranges through
successive movie frames. Both sites are confined to DEBUG.BIN's optional TEST.AGF pages; one uses signed
x=-184. The port's explicit F4 diagnostic route makes those pages reachable, but implementing the opcode
requires a new mask-frame/compositor seam rather than reuse of the existing scalar crossfade.
The remaining inventory is still 6 opcodes / 8 instructions because this slice changed metadata only.
`0x248` is the sensible next implementation: it is exact and closes SYSTEM4's singleton without imposing
obsolete D3D tiling. After that, investigate the two normal CONFIG sites of `0x142` before deciding whether
the developer-only `0x24d` transition justifies its larger compositor slice. The `/v2` handlers,
transition helpers, and tiled-size global are named/commented and the program is saved.
**NEXT:** implement `0x248` as retained graphics configuration/state parity, then investigate CONFIG's
two-site `0x142`; keep exact `0x24d` semantics documented for a later diagnostic-compositor slice.
**TILED-SURFACE EDGE `0x248` IMPLEMENTED (2026-07-29):** the VM now retains AGE's signed-dword tile-edge
configuration in `GfxState`. It starts at zero, SYSTEM4 sets 128, later writes replace the complete value,
and scene-context reset does not clear it. Like native, the setter does not dirty presentation or rebuild
existing mode-1 surfaces. The portable `0x249` path still uses one contiguous RGBA image, preserving every
script-visible result without reproducing the legacy D3D tile allocation.
Two focused regressions cover SYSTEM4's exact setting followed by a mode-1 texture load, native signed
replacement, and reset lifetime. The slice removes one instruction and leaves 5 effectful opcodes / 7
instructions. Validation passes 495/495 engine tests, opcode and EngineCtx lint, a zero-warning Godot
build, clean diff checking, and the Himegari-targeted threaded `SELFTEST OK`.
**NEXT:** investigate CONFIG's two-site `0x142`; keep developer-only `0x24d` for a later dedicated
movie-mask compositor slice.
**SYSTEM-MENU GUARD `0x142` INVESTIGATION COMPLETE (2026-07-29):** the opcode directly replaces
`EngineCtx.system_menu_actions_enabled`. Scene reset defaults it to one. CONFIG's first instruction writes
zero; its second and final call restores one immediately before UI/surface/SFX teardown and exit.
AGE exposes the field through `AGE:IAGEService` virtual offset `+0xb4`. The shipped AGERC menu refresher
calls it and, when false, grays seven settings/save-related native menu actions so they cannot reenter
configuration while CONFIG owns the scripted settings screen. No interpreter, renderer, gameplay, or save
consumer reads the field. The port has no AGERC native menu, so the exact implementation is a small
EngineCtx-lifetime boolean retained for parity and available to gate any future host equivalent; it should
not block game input or change persisted options. The remaining inventory stays 5 opcodes / 7 instructions
until implementation.
**NEXT:** implement `0x142` as the isolated system-menu availability guard. Afterwards rerank the four
remaining opcodes, with SYSTEM4 singleton `0x149` the likely next native investigation.
**SYSTEM-MENU GUARD `0x142` IMPLEMENTED (2026-07-29):** the VM now retains AGE's complete signed-dword
field, starts it at one, replaces it on every call, and restores one on root scene-context reload.
CONFIG's exact `0 -> 1` bracket is handled without routing the flag into game input, options persistence,
or a synthetic host service. The public retained value is available if a future frontend grows an
equivalent application menu.
Three focused regressions cover the native default and signed replacement, CONFIG's exact pair, and
root-reload lifetime. Both shipped sites are closed, reducing the remaining effectful inventory to
4 opcodes / 5 instructions. Validation passes 498/498 engine tests, opcode and EngineCtx build/lint, a
zero-warning Godot build, clean diff checking, and the Himegari-targeted threaded `SELFTEST OK`.
**NEXT:** rerank the final four gaps and investigate SYSTEM4's singleton `0x149`; keep developer-only
`0x24d` for its dedicated movie-mask compositor slice.
**SYSTEM-MENU SHOW DELAY `0x149` INVESTIGATION COMPLETE (2026-07-29):** the opcode replaces the unsigned
millisecond threshold at `EngineCtx.system_menu_show_delay_ms`; scene reset defaults it to zero and
SYSTEM4's sole call sets 1000. The exact, unobserved paired getter is opcode `0x148`.
AGE's Win32 window procedure starts a 100-millisecond timer while the pointer dwells in the top two client
rows. Once unsigned elapsed time is strictly greater than the configured threshold, with no mouse button
held, windowed ScreenMode, and no active AGERC host UI, it reveals AGERC's modeless full-width system-menu
strip. Alt reveals the same strip immediately and bypasses the threshold. This is the same settings/save
menu whose actions opcode `0x142` guards against CONFIG reentrancy, but it is unrelated to the adjacent
`0x248` graphics configuration.
The current Godot frontend has no equivalent application-menu strip. The bounded implementation is exact
state parity: retain the full unsigned dword, reset it to zero with scene context, implement the paired
getter, and expose it for a future host strip without changing game input, the script-authored right-click
menu, or persistence. This investigation changes metadata only, so the inventory remains 4 opcodes /
5 instructions. The `/v2` and `/support/AGERC.DLL` handlers, reveal/timer/dialog paths, typed EngineCtx
field, and key globals are annotated and saved.
**NEXT:** implement the exact `0x148`/`0x149` state pair. Afterwards rerank `0x137`, `0x241`, and the two
developer-only `0x24d` sites.
**SYSTEM-MENU SHOW DELAY `0x148`/`0x149` IMPLEMENTED (2026-07-29):** the VM now retains the complete
unsigned threshold, returns the same native dword through signed script integer cells, and restores zero
on root scene-context reload. SYSTEM4's 1000-millisecond setup is handled without inventing an `IHost`
effect for an AGERC strip the Godot frontend does not have.
Three focused regressions cover the exact SYSTEM4 setter/getter sequence, high-bit dword preservation, and
root-reload lifetime. The shipped setter site is closed; the unobserved getter is also available for
broader AGE compatibility. The remaining effectful inventory is 3 opcodes / 4 instructions. Validation
passes 501/501 engine tests, opcode and EngineCtx build/lint, a zero-warning Godot build, clean diff
checking, and the Himegari-targeted threaded `SELFTEST OK`.
**NEXT:** rerank `0x137`, `0x241`, and developer-only `0x24d` ×2 before choosing the next slice.
**FINAL OPCODE INVESTIGATION COMPLETE (2026-07-29):** all three remaining contracts are now
implementation-ready.
- `0x137(stack_id)` replaces one of AGE's ten integer LIFOs with an empty 0x100-dword stack. Its exact
unobserved siblings are `0x138(stack_id,value)` push and `0x139(stack_id,out_success,out_value)` pop.
AGE's handlers accidentally admit id 10 even though reset constructs only slots 0..9, aliasing adjacent
EngineCtx storage; Himegari uses only id 0. The sole call is `CALLBACK_LOAD@0xf2`.
- `0x241(movie,surface,flags,delay_ms,position_ms)` is `0x236` plus
`IMediaPosition::put_CurrentPosition` before playback configuration. CALLBACK_LOAD passes the
per-layer stop time recorded by `0x23f`, minus one, so its shipped job is restoring the terminal movie
frame after numbered load rather than resuming an arbitrary saved cursor.
- `0x24d` retains the already-decoded type-1 movie transition ABI. The remaining uncertainty is closed:
AGE copies each decoded RGB24 pixel's green byte into the transition mask, then applies that byte to
captured pixel alpha inside the supplied rectangle. Completion flips the mask to its terminal fill and
releases the blocking command. Both calls remain DEBUG.BIN-only.
The implementation order is correspondingly clear. First land the complete `0x137`-`0x139` stack family:
it is a small VM-only analogue of the existing FIFO and closes the normal load-path gap. Next land `0x241`
with one reusable FFmpeg initial-seek primitive for video and audio, including keyframe-preroll discard.
Finish with `0x24d` as the dedicated diagnostic compositor slice: publish movie green-channel masks,
capture old/new ranges, use the software renderer as the per-pixel oracle, and fall the GPU path back while
the type-1 command is active.
The inventory remains 3 opcodes / 4 instructions because this was investigation only. Opcode, global, and
EngineCtx sources and generated references are current; `/v2` names/comments and the applied EngineCtx
fields are saved.
**NEXT:** implement the coherent `0x137`-`0x139` integer-stack family, then `0x241`, then the DEBUG-only
`0x24d` compositor slice.
**INTEGER STACK `0x137`-`0x139` IMPLEMENTED (2026-07-29):** the VM now owns eleven safely isolated,
handler-addressable LIFO slots. They are constructed at VM scene entry, reconstructed empty on whole-root
scene reload, and individually replaced by `0x137`; `0x138` pushes signed dwords and `0x139` pops in LIFO
order. Empty pop writes success zero while preserving the prior value destination instead of leaking
native's reused internal EngineCtx pointer. Slot 10 is intentionally independent logical state rather than
AGE's adjacent numeric-glyph-table corruption.
Three focused regressions cover independent LIFO ordering and signed values, reset plus empty-output
behavior, safe slot 10, pre-reset availability, and root-reload lifetime. `CALLBACK_LOAD`'s shipped
`0x137` is closed, while unobserved `0x138`/`0x139` are implemented for wider AGE compatibility. The
remaining effectful inventory is 2 opcodes / 3 instructions: singleton `0x241` and DEBUG-only `0x24d` x2.
Validation passes 504/504 engine tests, opcode/global/EngineCtx build and lint suites, the zero-warning
Godot build, clean diff checking, and the Himegari-targeted threaded `SELFTEST OK`.
**NEXT:** implement positioned movie playback `0x241` with synchronized FFmpeg initial seek, then finish
the DEBUG-only `0x24d` green-channel movie-mask compositor.
**POSITIONED MOVIE `0x241` IMPLEMENTED (2026-07-29):** the opcode now uses the existing nonmodal
movie-to-surface ownership and stop-time metadata path while passing its fifth operand through a dedicated
initial-position host seam. FFmpeg ABI v3 seeks the independent video and audio format/decoder pipelines
before worker start. Video keyframe preroll selects the frame active at the requested position and retains a
later decoded frame for normal pacing; EOF after preroll therefore publishes the terminal frame for
CALLBACK_LOAD's native `stop_time_ms-1` call. Audio preroll drops earlier blocks, sample-trims a block that
straddles the position, and rebases the remaining timestamps to the same playback-zero origin.
Focused regressions cover exact opcode/host dispatch and retained graph stop time, runtime initial-position
and watchdog propagation, active-frame selection, subsequent pacing, terminal-frame retention, synchronized
audio trim/rebase, and a real VFS `CHAPTER.AGF` seek proving both native demuxers reach the requested
11-second neighborhood. Ordinary `0x236` and modal `0x20f` continue to open at position zero. The remaining
effectful inventory is one opcode / two DEBUG-only instructions: `0x24d`.
Validation passes 511/511 engine tests, the complete 213/213 installed-movie corpus gate, opcode/global/
EngineCtx build and lint suites, the zero-warning Godot build, clean diff checking, and the
Himegari-targeted threaded `SELFTEST OK`.
**NEXT:** implement the DEBUG-only `0x24d` green-channel movie-mask compositor.
**MOVIE-MASK TRANSITION `0x24d` IMPLEMENTED (2026-07-29):** the final effectful opcode now dispatches
all twelve operands through a dedicated host request. The old/source retained range is captured into the
script-created scratch surface with the existing platform-neutral retained rasterizer. Each due TEST.AGF
frame publishes its logical green channel as a byte mask, and the scratch surface applies AGE's exact
packed-ARGB alpha multiplication inside the signed/clipped destination rectangle while preserving captured
RGB and every pixel outside the rectangle. Mode 1 runs from zero to 255; other modes run from 255 to zero.
Movie playback retains ordinary instance ownership and VFS/FFmpeg decode, while the mask path redirects
frame publication away from visible movie RGB. Start delay parks first-frame consumption, stop/duration
retiming scales decoder presentation deadlines, and the terminal frame is held to the requested endpoint.
The type-1 command remains blocking until decoder completion and cannot be click-completed through the
type-0 transition shortcut. Completion publishes the exact terminal fill before opcode `0x21c` resumes.
The resulting scratch surface is an ordinary dynamic RGBA image, so software and GPU presentation share
one correctness path.
Focused regressions cover exact VM dispatch, transition lifetime, click immunity, green extraction,
dimension padding, signed clipping, RGB/outside preservation, the native packed-color carry, requested
decoder retiming, and real VFS TEST.AGF metadata/green-mask decode. The effectful opcode inventory is now
empty. Validation passes 518/518 engine tests, the zero-warning Godot build, opcode/global/EngineCtx suites,
clean diff checking, and the Himegari-targeted threaded selftest.
**NEXT:** return to playthrough-led polish and save compatibility testing; no decoded effectful opcode gap
remains in the shipped 481-script corpus.
## Later Phase B breadth
**INIT data-semantics side track started (2026-07-22).** Before naming more gameplay state, the static
extractor itself was audited. It now preserves sparse one-based ids and corrects ITINIT from 189 malformed
records to 287 items (plus SKINIT 129→131 skills); regression checks cover the real tables. It also evaluates
the INIT convention `sub destination, 0, magnitude`, preserving 113 negative ITINIT cells, 212 SKINIT cells,
and 86 EBINIT cells that the former `mov`-only pass silently dropped. A reusable field profiler reports
distributions, examples, and direct script/opcode consumers.
The first semantic tranche now curates the strongest item, skill, and unit fields in
`vm-map/globals.toml`. ITINIT has thirteen parallel arrays plus six linked row-major tables and 877 linked
writes across 44 populated columns; signed fields include equipment penalties and condition-removal deltas.
SKINIT contributes skill category/order/icon/handler, encoded range, element, status, combat/resource deltas,
proc chance, and battle animation. EBINIT now contributes unit order/icon, sex and provisional species,
elements, attacks/equipment/skills, level/cost/base/growth data, canonical variants, CP/CA/CB/CS/cut-in and
voice assets, XP/drops, capture and compendium flags, summoning economy, essence yield, and level scaling.
Its linked-table shapes are 18 SKINIT and 82 EBINIT populated columns. The next EB tranche identifies roster
state flags, per-action unlock requirements, SCJUMP event ids, normal/brainwashed unit variants, and SALLY's
per-unit bonus item. Combat tracing also names the shared column-8 item/skill modifier as critical chance.
The message-table tranche adds a reusable extractor for both shipped global-id control-flow layouts and
joins player-facing ITMES/SKMES/VIMES/EIMES/CIMES text back to INIT records. All 287 item ids, 131 skill
ids, 65 glossary topic ids, and 24 character-profile ids match exactly in both directions; all 192 sparse
EIMES ids resolve to EBINIT unit definitions. Joined records retain dispatch and message-body offsets,
rendered text, and furigana. Title-bearing tables expose title/description, EIMES preserves its untitled
lines as enemy summary and strategy, and CIMES preserves its untitled multiline body as `biography`.
The dispatch keys establish `current_item_id`, `current_skill_id`, `current_glossary_topic_id`,
`current_enemy_encyclopedia_unit_id`, and `current_character_profile_id` as high-confidence shared index
slots.
The follow-up closes the three smaller MES-named scripts. MAINIT now extracts eleven records in its
reserved 30-cell layout and joins MAMES descriptions for ids 1 through 9; its two growth rituals have no
authored MAMES branch. INFOMES is a text-free 32-by-4 first-handler-wins extension registry initialized
with CIMES/EIMES/VIMES, while MES renders and clears a generic caller-populated modal line buffer plus an
optional annotation buffer shared with SBUNKI. These are stable runtime ABIs, not three additional
translatable table shapes.
VIINIT is now extracted as a sparse 200-by-3 glossary definition table: 65 populated names, one required
one-based seen-scene decision for every topic, and a second alternative prerequisite for thirteen topics.
INFOVO evaluates those prerequisites against persistent scene-decision seen flags before listing a topic.
INFOEN likewise gates EBINIT's enabled enemy-information rows through persistent encounter/reveal flags.
The generated VIINIT profile reports 65/65 VIMES coverage, while EBINIT reports 192/277 EIMES coverage;
the remaining EBINIT rows are valid player/unused unit definitions rather than missing EIMES records.
CIINIT defines 24 rows in four reserved 100-cell columns: displayed names, backing EBINIT unit ids,
optional portrait resources, and default-zero portrait placement offsets. INFOCH consumes that registry,
and the generated CIINIT profile reports complete 24/24 CIMES biography coverage.
The follow-up correlation pass makes that evidence directly queryable with
`init_table_profile.py --message-query REGEX` and moves confirmed item/skill row-column meanings into
structured `globals.toml` metadata. Extracted JSON and profiles now resolve raw keys to names such as
`item_stat_modifiers.critical_chance` while preserving the original address key. The message audit confirms
all populated condition columns and the female-only item mask. CHMENU also resolves the last anonymous
SKINIT parallel field as the skill-change catalog inclusion flag and separates acquired-skill state from
skill-information visibility. The remaining item/skill work is refinement rather than an unnamed populated
schema.
EBINIT's populated schema is now fully named. Its apparent final two sparse runtime-table writes were flat-
address overlap collisions: they are the already-established battle-sprite asset for unit 456 and starting
level for unit 600, not distinct stride-300 fields. Enemy AI appears to live outside the static EBINIT
schema. The signed boss class is now behaviorally separated: all nonzero values
receive boss protections and targeting treatment, positive values alone count as required targets under
FIELD's defeat-boss clear rule, negative values are boss-treated adds/decoys/hazards that do not block
victory, and either sign of class 4 selects final-boss music and tactical-map presentation. Classes 1--3
otherwise remain authoring categories rather than distinct runtime branches. The former seven-value
`0x7843e` unknown is now a medium-confidence authoring-only `unit_power_tier`: it tracks unit cost, yield,
caps, stats, and form/story strength but has no shipped script or native-index consumer. SALLY's four unlock
requirements and eight event ids are fully joined to their contract, brainwash, reserved, form-dependent
sex-magic, sacrifice, and release columns. FIELD/BTL/SHOWGROW also resolve all 23 formerly generic populated
voice columns, including five authoring slots unreachable in the shipped selectors. STINIT's separate mixed
parser is now complete:
all 74 sparse stage ids retain their victory/defeat strings, six scalars, fixed-buffer cells, and 1,396
footer-array copies. AIM/FIELD/DRAWCHP consumers establish the four condition slots, stage BGM, turn limit,
and turn-limit outcome. Further passes establish target clear turns, performance reward, replay behavior,
the 20-slot map-texture override list, object type/tile/difficulty fields, and seven required plus five
forbidden story prerequisites. Generated records now carry a joined `semantic_fields` view and assemble
2,312 object placements across 66 stages. FIELD resolves the universal reinforcement interval/limit and
type-tagged initial-faction, teleport, treasure, card-list, and non-triggering-faction payloads. OBINIT
supplies authoritative names for all 46 object types and descriptions for 34. Its state-row mode and
FIELD/DRAWOBJ initialization path resolve 78 initial object states on types 11, 17, and 26. FIELD's
dedicated special-spawn path proves the last three populated type-27 tagged writes are ignored by the
engine; they remain preserved as provenance. The separate 30-cell enemy family is also decoded:
FIELD/SETEN/ADDEN establish unit/faction, direct or object-linked placement, difficulty/story gates, level
floor/cap/scaling, weighted selection, and three difficulty-specific movement/battle routine-set ids. The
generated view assembles all 1,378 populated enemy slots across 66 stages, including 485 enemies suppressed
after the stage's first clear.
CCINIT is the fifth extracted shape rather than a failed name-table parse. Its 71 conditional
class-change rules cover 33 EBINIT units and expose unit/level/applied-slot predicates plus the selected
title, deployment-cost delta, fourteen-stat bonuses, SKINIT skill awards, and state slot set. The shipped
program contains 69 named promotions, 30 skill awards, and two level-independent empty-title Lily
form-adjustment rules; EVOLVE directly queries those form effects while CALCCC/ADDEXP establish the normal
promotion apply/report protocol. The input, working-output, and persistent destination globals are named
as one class-change ABI in `vm-map/globals.toml`, and generated rules carry both raw address provenance and
the shared `semantic_fields` join. SCINIT is now the sixth shape: 2,179 paired assignments produce a
1,209-row SCJUMP decision-to-packed-scene registry plus authored chapter metadata, with all script ids
resolved and the full overwrite history retained. RTINIT is now the seventh shape: its twenty parallel
1000-by-20 banks produce 172 routine sets, 1,043 movement steps, and fourteen battle steps with RTN_M/RTN_B
provider joins, activation/progress/story gates, six explicit reserved banks, and complete overwrite
history. Provider-tagged decoding now covers movement providers 1, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13,
14, 15, 17, 51, and 52, plus providers 2, 9, and 61: progress-only steps, randomized roaming,
stage-object slots, coordinate destinations (including M012's
foreign-entity route mask), enemy/ally and Magic Pillar searches, resource-gated Healing Feather
selection, collectible-treasure seeking, cyclic waypoints, faction-traversable terrain, retreat,
normal-attack routing, immediate element-effective target/action selection, and immediate allied
healing. This covers all 1,043 steps across all nineteen used providers and classifies every one of the 977
authored movement-parameter cells: 974 are semantic inputs, while the three cells attached to M001/M008
are retained as provider-unread residue; thirteen unwritten zero defaults are projected separately. The
three dispatchable providers absent from the shipped RTINIT table remain outside the generated join.
Once the natural spine and first gameplay loop are trustworthy, broaden in independent tracks:
- Remaining title/configuration/load/save branches.
- Save-file format and restoration of a persistent session.
- Map navigation, camera, terrain, deployment, and turn lifecycle.
- Unit statistics, equipment, inventory, skills, magic, heroine forms, and progression.
- Combat resolution presentation, enemy turns/AI services, and win/loss transitions.
- A full chapter of ADV and the scene types encountered between gameplay segments.
- Other data schemas when their runtime consumers make them necessary.
The high-level Phase B direction remains canonical in `docs/remake-architecture-and-roadmap.md`; this file
only provides the execution framework.
## Slice record template
When a stage becomes active, add its concrete plan/results to this document or the then-current slice plan
without pre-planning all later systems. Record:
- Starting state and exact reproduction path.
- One observable end condition.
- Executed unknown/effectful opcode families.
- Required global/profile/host state and its producer.
- Native/original evidence to capture.
- Explicit non-goals.
- Automated regression gates and manual acceptance check.
- Result, remaining discrepancies, and the next decision gate.
## Decision gates
- After B0: confirm or revise the boot/title/SC0000/dungeon sequence using observed load order.
- After B2: decide whether session state is trustworthy enough to remove the diagnostic boot path from
ordinary runs.
- After B3: choose whether save/config work blocks natural SC0000 entry; otherwise defer it.
- After B4: use the actual first-dungeon trace to write the concrete B5 slice rather than guessing its
systems in advance.
- After B5: choose breadth based on the next natural blocker, not opcode coverage percentage alone.