354 lines
23 KiB
Markdown
354 lines
23 KiB
Markdown
# 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.
|
||
|
||
## 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 entered INITCONFIG,
|
||
INIT2 and all 23 of its data-initializer children, TUNE, INIT, and TITLE (28 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 9/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.
|
||
|
||
**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 B1–B3; 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 new packed-raw 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.
|
||
|
||
**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 raw catalog movies `0x335f`/`LOGO.AGF` and `0x3364`/`OP.AGF`; existing `0x236` is the distinct
|
||
non-modal, scene-local movie-to-surface path. The VM now models the initial-root flag and clears it at the
|
||
op-`0x9` whole-stack root-reload boundary. Godot resolves a typed raw 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 remains explicitly deferred
|
||
until the decoder abstraction has an engine-owned synchronized audio/volume contract.
|
||
|
||
## 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 currently exposed post-`0x1` developer-menu
|
||
fallthrough as evidence; native `0x1` is non-returning. 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 1–4 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.
|
||
|
||
## 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.
|
||
|
||
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.
|
||
- Load/save implementation and save-format reversal.
|
||
- 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.
|
||
|
||
## Later Phase B breadth
|
||
|
||
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.
|
||
- `STINIT` and 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.
|