34 KiB
P9.1 Runtime Architecture Specification
Status: expert input for PMO reconciliation Baseline:
dec9574405043624acee0f4f81963d829e8d40e9Preserved product source: P9.0R10 at41204acafa911a505f5b24a493aa032983e706d1Scope: behavior-equivalent extraction boundaries and P9.1 parallel write contract
1. Decision Summary
P9.1 shall keep runtime/main.tscn and its root Control as the only scene
entry while reducing runtime/main.gd to a compatibility facade and
composition root. Deterministic first-session rules move into plain
RefCounted domain objects before new gameplay is added. Rendering and
real-time playback consume immutable snapshots and domain-emitted facts; they
do not calculate outcomes.
The required dependency direction is:
main.gd facade / composition root
-> ui/p9_1/* presenter and Control builders
-> presentation/* clock, animation, feedback and audio adapters
-> domain/p9_1/* session state, commands, battle fixtures and copy facts
tests -> public facade during extraction
tests -> domain/p9_1 APIs for P9.1 matrix and transaction tests
domain/p9_1 -> no Node, Control, SceneTree, Input, AudioServer or frame clock
There is no event bus singleton and no autoload in this milestone. The root
constructs dependencies explicitly. Domain commands return typed-by-contract
Dictionary results because this repository currently uses GDScript without a
schema library; every payload has a stable kind, required keys and value
types documented below. Introducing a dependency, plugin or autoload would add
global ownership and is not justified by this bounded slice.
This document does not authorize runtime edits. The reconciled implementation contract must preserve P9.0R10 behavior and the Owner-accepted player arc.
2. Baseline Inventory
2.1 Current responsibilities and concrete symbols
runtime/main.gd is 3,704 lines. It currently owns all of the following:
| Responsibility | Mutable state or concrete symbols | Current coupling |
|---|---|---|
| Boot and navigation | AppPage, page, run_boot_verification, enter_game, start_new_game, go_home, confirm_restart |
Page mutation immediately calls render() and audio cues. |
| Session presentation settings | locale, text_scale, reduced_motion, muted, toggle_* |
Localization and accessibility branches are read throughout rendering and animation. |
| Recruit selection and formation | selected_recruits, front_choice, rear_choice, formation_commit_count, toggle_recruit, assign_automatic_formation, commit_formation |
Rules, copy, sound and rerender are in the same methods. |
| Run transactions | coin, adaptation_*, guild_*, retained_item, added_rear, route_*, training_*, reward flags |
Selection validation, commit-once guards, spend/reward and page transition are interleaved. |
| Battle fixture state | battle_phase, clocks, HP, event counters, phase_impact_committed, five elapsed/result times |
The render frame calls state mutation through _process, commit_phase_impact and advance_battle_phase. |
| Battle outcomes | commit_phase_impact, commit_second_phase_impact, commit_third_phase_impact, commit_fourth_phase_impact, commit_fifth_phase_impact |
Outcome tables are page branches and call play_cue() plus render(). |
| Causal explanations | victory_explanation, second_result_explanation, route/training/build helpers |
Product facts, localized prose and presentation colors share the root script. |
| Rendering | render, 20-plus render_* methods, button/style/actor helpers |
Full DisplayRoot tree is destroyed and rebuilt on every render. Stable node names are test API. |
| Playback | ANIMATION_SPECS, animation-state functions, _process, sprite positions, projectile |
Domain phase, wall-clock time, animation frames and Control coordinates are coupled. |
| Feedback and audio | active_battle_feedback, history, emit_battle_feedback, update_battle_feedback_visual, play_cue |
Exactly-once outcome facts are stored in UI-shaped dictionaries and trigger platform services directly. |
| Cleanup | clear_experience, _exit_tree |
One long reset method owns all transient domain and presentation state. |
The independent runtime/domain/run_state.gd and current runtime/tests/p9_1_*
belong to an older opening-market lineage. They expose game.run and
AppPage.RUN, neither of which exists in the P9.0R10 accepted runtime. They are
not a second authority for this extraction and must not be merged into the new
P9.1 first-session model by name alone. PMO must explicitly classify them as
legacy/quarantined or separately restore that lineage before any runner claims
they are part of the P9.0R1-R10 regression suite.
2.2 Current event flow
The accepted runtime uses this loop:
Control signal callback
-> root method validates current page and mutable fields
-> method mutates values and page
-> play_cue()
-> render() rebuilds DisplayRoot
_process(delta)
-> advances wall clocks and sprite positions
-> crossing impact time calls commit_phase_impact()
-> impact function mutates HP, appends feedback and plays audio
-> crossing phase duration calls advance_battle_phase()
-> phase completion mutates reward/page and rerenders
The problem is not that a render loop exists. The problem is that a skipped or duplicated render frame can currently enter the outcome mutation path. P9.1 must make the deterministic command/impact transaction callable without a scene tree and make the real-time driver only request named impacts once.
2.3 Existing regression seams
The preserved P9.0 tests instantiate res://main.tscn and directly read or
write root fields. Across the suite they rely on:
game.AppPagenumeric names andgame.page;- public state such as
front_choice,rear_choice, HP, Coin, choices, commit flags, phase clocks/counters and feedback history; - direct calls such as
advance_battle_phase,commit_phase_impact, transaction methods, copy helpers,renderandclear_experience; ANIMATION_SPECS, duration helpers, animation lookup and sprite references;- stable Control names searched recursively and real
Input.parse_input_eventtouch delivery; - exact marker output, strict diagnostic cleanup and fixed result values.
This is an intentionally broad compatibility surface during extraction. Tests shall not all be rewritten to new APIs in the foundation step because doing so would remove the evidence that behavior was preserved. The facade keeps that surface until equivalent domain and UI contract tests pass at the same source revision; compatibility removal is a later integration action with explicit criteria in section 10.
3. State Ownership
Exactly one object owns each mutable value. Snapshots are deep copies and consumers must treat them as immutable.
| Owner | Authoritative mutable state | Explicitly does not own |
|---|---|---|
RuntimeShell (main.gd) |
Dependency instances, boot verification fields, render_pending, compatibility property forwarding |
Run outcomes, choice validity, HP/Coin calculations, UI-derived recommendations |
PresentationSettings |
locale, text_scale, reduced_motion, muted, audio_enabled |
Gameplay state or translated gameplay facts |
FirstSessionState |
Current domain screen, selected members/order, all selection/commit flags, Coin, HP, rewards, route/growth state, current encounter progress, committed impact IDs and semantic fact history | Nodes, textures, positions, elapsed frame delta, audio |
BattleFixture |
Immutable encounter phase definitions, durations, impact IDs and deterministic transition functions | Mutable progress/history, sprite references, animation frames, pixel coordinates, localized labels |
BattleClock |
Playback elapsed seconds for current phase/encounter, result display time, active feedback display age | HP/Coin or whether an impact is legal |
SessionPresenter |
Node references it created, ephemeral hover/focus and layout data | Any decision or outcome duplicated from domain state |
AnimationPlayerAdapter |
Loaded textures, animation frame selection, actor base positions, projectile visual state | Domain phase transitions or impact commits |
FeedbackPresenter |
Current rendered number/VFX node and display age | Feedback history or damage/heal calculation |
AudioAdapter |
AudioStreamPlayer and generator playback lifecycle |
Outcome choice or transaction state |
The domain screen is not a Godot Control. Use stable domain names rather than
numeric UI enum values:
boot, title, main_menu, game_intro, settings, first_mission,
recruit_selection, formation_review, yard,
battle_1, result_1, adaptation, battle_2, result_2,
guild_response, battle_3, result_3,
route_board, route_review, battle_4, result_4,
growth, growth_review, battle_5, session_result
The compatibility facade maps these names to the existing AppPage constants.
Historical FRONT_SELECTION and REAR_SELECTION remain facade-only enum
values and are never legal domain screens.
4. Module and File Contract
4.1 Foundation-owned shared extraction
The foundation change creates and wires these stable boundaries before domain, UI and test workers start:
| File | API and responsibility |
|---|---|
runtime/main.gd |
RuntimeShell; scene lifecycle, boot, dependency construction, facade properties/methods, render scheduling. No new P9.1 rule branches. |
runtime/presentation/presentation_settings.gd |
Session-local locale, text scale, reduced-motion, mute, audio availability and low-power values; emits no gameplay event. |
runtime/domain/p9_1/session_state.gd |
FirstSessionState; data container, reset_transient(), snapshot(), invariant checks. Initial implementation mirrors accepted fields exactly. |
runtime/domain/p9_1/session_service.gd |
FirstSessionService; all selection and commit commands, screen transitions, rewards and encounter start/finish. |
runtime/domain/p9_1/battle_fixture.gd |
Deterministic encounter definitions, duration_for_phase, impact_time, commit_impact, complete_phase; emits semantic facts without Node calls. |
runtime/domain/p9_1/contracts.gd |
Stable screen, command, encounter, event and rejection string constants; snapshot schema version. |
runtime/presentation/battle_clock.gd |
Converts delta to impact_due / phase_due requests; freezes during confirmation; owns elapsed display timing only. |
runtime/presentation/animation_adapter.gd |
Asset library and animation frame lookup extracted without changing frames/timing. |
runtime/presentation/audio_adapter.gd |
Existing generated SFX setup, cue playback, mute and cleanup. |
runtime/ui/session_presenter.gd |
Existing render dispatch and render helpers moved behavior-equivalently; consumes snapshot and callbacks. |
runtime/ui/widgets.gd |
Existing button, label, actor, bar and style builders; no domain reads. |
runtime/compat/p9_0_root_facade.gd |
Mapping helpers used by main.gd to preserve test-visible properties, calls and AppPage. No independent mutable state. |
If GDScript property forwarding makes runtime/compat/p9_0_root_facade.gd
impractical as composition, keep the thin forwarding code in main.gd; do not
use inheritance or duplicate fields merely to satisfy this proposed filename.
The contract is one authoritative state object plus a compatible root surface.
4.2 Domain APIs
FirstSessionService exposes commands. Every command returns:
{
"ok": bool,
"code": StringName, # stable success or rejection code
"events": Array[Dictionary],
"snapshot": Dictionary, # deep copy after the attempt
}
Required commands for the preserved and P9.1 path are:
reset_transient
open_new_session
toggle_recruit(profession_id)
review_formation
revise_formation
commit_formation
choose_adaptation(item_id)
commit_adaptation
choose_guild_response(response_id)
commit_guild_response
choose_route(route_id)
review_route
cancel_route_review
commit_route
choose_growth(member_id)
review_growth
cancel_growth_review
commit_growth
commit_impact(encounter_id, phase, impact_id)
complete_phase(encounter_id, phase)
Selection commands may change only selection state. Commit commands revalidate
the entire precondition and either apply all effects once or apply nothing.
Failures use stable codes such as wrong_screen, invalid_id,
selection_required, insufficient_coin, already_committed and
stale_encounter; UI copy is not stored in these codes.
Direct field mutation is not part of the new domain API. Fixtures may use a
test-only builder in runtime/tests/support/p9_1_session_fixture.gd to create a
validated starting snapshot. Production UI never calls that builder.
4.3 Snapshot schema
FirstSessionState.snapshot() returns the domain fields in this minimum
shape. RuntimeShell.render_snapshot() adds the settings section from
PresentationSettings to a deep copy; it never writes settings into domain
state.
{
"schema": 1,
"revision": int, # increases after successful commands only
"screen": StringName,
"settings": { # render snapshot only; composed by shell
"locale": StringName,
"text_scale": float,
"reduced_motion": bool,
"muted": bool,
"low_power": bool,
},
"party": {
"selected_ids": Array[StringName],
"ordered_ids": Array[StringName],
"lead_id": StringName,
"second_id": StringName,
"added_id": StringName,
"formation_commits": int,
},
"resources": {"coin": int},
"decisions": {
"adaptation": StringName,
"guild_response": StringName,
"route": StringName,
"growth_member": StringName,
},
"locks": {
"adaptation": bool,
"guild_response": bool,
"route": bool,
"growth": bool,
},
"encounter": {
"id": StringName,
"phase": int,
"playing": bool,
"front_hp": int,
"rear_hp": int,
"enemy_hp": int,
"committed_impacts": Array[StringName],
"elapsed": float,
},
"rewards": {
"battle_1": bool,
"route": bool,
"battle_5": bool,
},
"facts": Array[Dictionary],
"available_actions": Array[StringName],
}
The pure domain snapshot omits settings; every other key above is domain
owned. available_actions is domain-derived and allows buttons to enable without
reimplementing validation. It is not a recommendation and cannot preselect.
Settings are included only in the complete render input and cannot affect a
command outcome or the domain revision.
Every party member ID is stable and carries profession as member data. P9.1
must not use UI positions such as front, rear or added as the durable
identity for growth. The compatibility facade may map the current two-member
fields while old tests remain.
4.4 Semantic events and facts
Domain output is an append-in-result event list, not a signal emitted from a Node. Events use these contracts:
kind |
Required payload | Consumer |
|---|---|---|
screen_changed |
from, to, cause |
Shell/presenter rerender and focus policy |
selection_changed |
decision, selected_id |
Presenter selection visuals |
transaction_committed |
transaction, decision_id, revision, resource deltas |
Result copy facts and tests |
impact_committed |
encounter_id, phase, impact_id, target_member_id or enemy, delta, qualifier, tier |
Feedback, audio and deterministic tests |
encounter_finished |
encounter_id, final HP, elapsed fixture time |
Presenter/result navigation |
reward_committed |
reward_id, coin_delta, coin_after |
Result facts and tests |
run_cleared |
reason |
Shell clears playback/presentation state |
qualifier is a semantic key such as block, evade_reduction,
flank_counter, ranged_hit, heal, combo or finisher, never localized
prose. UI translation maps keys to Chinese/English. The event's exact delta
is created inside the successful mutation, preserving the P9.0R3 rule that
feedback is never inferred from animation or page entry.
The facts array in a snapshot contains durable causal facts needed for the
current diagnosis/result. Each fact has fact_id, actor_id, action,
target_id, exact delta where applicable, source_decision_id and
encounter_id. Result copy is formatted from these facts; it does not rerun
party classification or outcome calculations.
4.5 Presenter API
SessionPresenter has one public render entry:
render(snapshot: Dictionary, actions: Dictionary[StringName, Callable]) -> void
The presenter may request domain actions only through named callbacks. A callback calls the service, passes returned events to presentation adapters, then schedules a render using the returned snapshot. The UI worker may add P9.1 screens and copy formatting under its owned path but cannot read service internals or calculate HP, Coin, ordering, coverage, choice value or results.
The presenter continues to create the stable P9.0 Control names until legacy tests are retired. New P9.1 controls receive stable semantic names in the reconciled UX contract. Node names are input/test handles; labels are localized and must never be used as identifiers.
4.6 Battle clock handshake
The frame adapter receives the current encounter snapshot and returns requests:
advance(delta, encounter_snapshot, reduced_motion, paused) -> {
"impact_requests": Array[{"encounter_id", "phase", "impact_id"}],
"phase_complete": bool,
"phase_progress": float,
}
The shell submits requests to the domain service. The service rejects duplicate
impact_id, wrong phase or stale encounter. Only accepted
impact_committed events reach feedback/audio. phase_progress drives visual
interpolation and animation only. Reduced motion and low power may change
sampling and translation but never impact order, fixture durations, final
values or command availability.
Headless tests bypass BattleClock and invoke named impacts/phase completion
directly, proving domain results do not depend on FPS. One integration test
must still drive large deltas across impact thresholds to prove the adapter
requests each impact exactly once.
5. Compatibility Facade
During extraction, runtime/main.gd remains the API expected by P9.0 tests.
Every facade value is computed from an owner; it is not separately stored.
Required mappings include:
- existing
AppPagenames andpagegetter/setter for fixture/capture tests; - selection, party, Coin, choice/commit, HP, phase, reward and feedback getters;
- existing transaction method names, translating successful service events to
play_cueandqueue_render; phase_duration,phase_impact_time,advance_battle_phaseandcommit_phase_impactdelegating to fixture/clock/domain boundaries;- animation constants/helpers and sprite references delegating to presentation;
renderand stable Control names delegating toSessionPresenter;clear_experiencecalling domain reset plus clock/feedback cleanup exactly once.
Compatibility setters are allowed only for fields that existing capture/tests set directly. They shall call a clearly named test/compat patch function which revalidates the state before rendering. Production callbacks never use these setters. Any field that cannot be forwarded without two sources of truth blocks that extraction step; the foundation worker must keep it in the root until its owner can be singular.
The facade shall contain no P9.1-only pair branching. New behavior enters the domain and presenter APIs and is wired by the later integration owner.
6. Extraction Sequence and Regression Gates
Each numbered step is a separate commit candidate. A step advances only when its focused checks and the serial accepted regression set pass with strict diagnostics clean. On failure, revert that step's commit or repair within the same owned files; never mask the failure by weakening or rewriting preserved assertions.
Step 0: Freeze evidence and classify the suite
- Record exact baseline source, Godot version and the ordered P9.0R1-R10 test command/marker manifest in a runner-owned evidence file.
- Confirm the accepted suite count from the R10 closure evidence rather than
globbing every file named
p9_1_*. - Record the orphan opening-market lineage separately; do not delete it in foundation extraction.
Gate: clean baseline reproduces all 22 accepted R1-R10 tests, R10 matrix/touch, strict marker scan and cleanup. If it does not, terminal is redesign required at baseline reproducibility; extraction must not begin.
Step 1: Introduce state owner behind a no-op facade
Create contracts.gd, session_state.gd and the compatibility forwarding
surface. Initialize the state from the same defaults and delegate reset. Do not
move transactions or change rendering.
Gate: reset/default/navigation tests, Home/Restart cleanup, all direct root field assertions and full serial regression. Rollback: remove the state object and forwarding commit; no data migration exists.
Step 2: Extract selection and run transactions
Move automatic formation, adaptation, guild response, route, growth, rewards
and screen transition validation to FirstSessionService, in historical order.
Keep root method names as wrappers. Every operation gains a focused atomicity
test: invalid/insufficient/duplicate attempts leave snapshot and revision
unchanged.
Gate after each transaction family: its original focused test plus snapshot diff against baseline fixtures, then the serial suite. Rollback is per family; do not move all transactions in one unreviewable commit.
Step 3: Extract deterministic battle fixtures
Move durations, impact timing, HP mutation and semantic event creation one encounter at a time from battle 1 through battle 5. Root methods delegate. First preserve exact P9.0R10 values and event order; no arbitrary-party post-battle-two behavior is added in this step.
Gate per encounter: exact event sequence, deltas, HP/time, once-only impacts, reward, first-battle feedback and associated R1-R7 tests. R10 remains the final gate. Rollback only the most recent encounter extraction.
Step 4: Extract clock, animation, feedback and audio adapters
Move time accumulation and visual playback after outcomes are already domain owned. Separate semantic event history from the single active presentation payload. Extract animation lookup/assets and audio lifecycle without changing timing, frames or accepted cues.
Gate: R2 animation terminal bounds/dynamic slice, R3 exact feedback and reduced motion, R3 audio lifecycle, large-delta impact-once integration, Restart pause, Home cleanup and serial regression. Rollback adapters independently; domain tests must remain green even if presentation extraction is reverted.
Step 5: Extract presenter and widgets
Move page dispatch, screen builders and shared Control/style helpers. Preserve node names, minimum touch size, layout, text scaling, locale behavior and root sprite references through the facade.
Gate per screen family: domain/navigation, real parsed touch with direct and
fallback zero, layout/capture at existing viewports, English 130%, reduced
motion, mute, low-power representative state, portrait notice and serial
regression. Use normal rendering, not only direct render() fixtures.
Step 6: Foundation closure
The foundation source is ready for parallel work only when:
main.gdis a composition root/facade and has no authoritative outcome calculation or P9.1-only rule branch;- all mutable values have one owner and snapshot/event schema tests pass;
- domain tests run without instantiating
main.tscn; - all accepted P9.0R1-R10 behavior remains green at one clean revision;
- the exact parallel write paths in section 7 exist and compile;
- PMO reconciles this document with game-design and UX/visual outputs.
Rollback at closure is the last green step revision, not a mixed partial extraction. No parallel worker begins from a failing foundation branch.
7. Non-Overlapping Parallel Writes
The following is the implementation ownership contract after foundation is integrated. Directory ownership includes new files only unless an exact file is listed. Moving or renaming another role's file is a write to that file and is forbidden.
| Role | Exact writes | Reads/consumes | Forbidden writes |
|---|---|---|---|
| Foundation | runtime/main.gd, runtime/domain/p9_1/{contracts,session_state,session_service,battle_fixture}.gd, runtime/presentation/{presentation_settings,battle_clock,animation_adapter,audio_adapter,feedback_presenter}.gd, runtime/ui/{session_presenter,widgets}.gd, runtime/compat/p9_0_root_facade.gd |
Accepted source/contracts/tests | New P9.1 gameplay, test expectations, product behavior |
| Domain | New files only under runtime/domain/p9_1/content/ and runtime/domain/p9_1/rules/ |
Foundation domain APIs plus reconciled gameplay contract | main.gd; foundation API files; any Control, UI, tests or runners |
| UI | New files only under runtime/ui/p9_1/ |
Snapshot/event schema plus reconciled UX contract | Domain/presentation logic; main.gd; shared presenter/widgets; tests/runners |
| Tests | New files under runtime/tests/p9_1/ and runtime/tests/support/p9_1/ |
Public domain/presenter contracts and frozen matrix | Existing P9.0 tests; production runtime; shared runner/entry files |
| Integration | runtime/main.gd, exact foundation shared API files when reconciliation requires, shared P9.1 runner/manifest files, runtime/project.godot only if the frozen integration contract explicitly requires it |
Domain, UI and test commits | Rewriting accepted P9.0 assertions; product expansion |
The integration role is the only role allowed to wire new domain/UI modules into the shared facade, resolve schema mismatches, or edit shared runners. Integration must not silently absorb one role's duplicated calculation. A schema mismatch returns to the earliest producing role or receives an explicit reconciled contract amendment before integration.
No parallel role owns runtime/main.tscn; the accepted one-root scene needs no
edit for this architecture. If implementation proves otherwise, PMO must add
that exact file to integration ownership before work, never let UI and
integration both edit it.
8. Test Architecture After Extraction
8.1 Test layers
- Pure domain tests instantiate service/state/fixtures without a scene tree. Cover the six unordered pairs, both adaptations, every reconciled downstream choice, invalid transactions, deterministic events and reset.
- Facade regressions keep the accepted P9.0 tests unchanged while the facade exists. They prove property/method/node compatibility.
- Presenter contract tests feed fixed snapshots/events and assert stable controls, available/disabled actions, localized fact rendering and absence of duplicated outcome logic.
- Clock integration tests drive normal, low-FPS/large-delta, reduced-motion and paused paths and compare the final domain snapshot.
- End-to-end parsed input uses
Input.parse_input_eventScreenTouch and ScreenDrag through the complete session with fallback/direct zero. - Normal-render evidence covers queue/action feedback, causal decisions and result deltas at small/reference/large landscape, Chinese/English, 130%, reduced motion, mute and low power.
- Strict diagnostics scan script/parse/resource/import/assertion/ObjectDB and cleanup failures; the terminal marker must be unique.
8.2 Contract assertions
Add schema tests that reject:
- mutable arrays/dictionaries leaking from snapshots;
- UI code importing
battle_fixture.gdor rule content directly; - domain files extending
Node/Controlor referencingSceneTree,Input,AudioServer, textures, node paths or viewport size; - a successful command with no revision increment, or rejected command with a state/revision change;
- duplicate
impact_id, reward or commit events; - presenter calculations of Coin/HP/coverage/result facts;
- source files outside the owning role's write allowlist.
Dependency-direction and write-scope checks may be small repository scripts;
they must use parsed GDScript metadata where practical, with rg guards as a
bounded additional check rather than claiming a full language parser.
8.3 Baseline runner requirement
The test worker shall create one serial runner manifest listing exact scripts, expected unique markers, timeout, required viewport/environment and strict log policy. It must not select tests by filename glob. The orphan opening-market files are separately named and have no effect on the R1-R10 pass claim until a future frozen goal reconciles them.
9. Invariants
These are mandatory at every green migration step and at P9.1 integration:
- A successful command is atomic and increases
revisionexactly once. - A rejected command changes no snapshot field, event history, resource, reward, clock or page.
- Formation order is derived from selected member identities, never click order, and commits once.
- Choice selection is revisable and free; its commit locks once and is the only spend/state-change boundary.
- An impact is keyed by encounter + phase + impact ID and commits at most once.
- Feedback is the exact semantic event created inside that impact mutation.
- Rewards commit only after their declared victory boundary and at most once.
reset_transientclears all run and playback state while preserving session-local presentation settings.- Domain output is identical across render FPS, reduced motion, mute, low power and viewport size.
- UI enables from
available_actions, displays domain facts and never predicts or recalculates outcomes. - Chinese and English are formatting over the same semantic facts; locale changes no domain snapshot except the shell-composed settings section.
- No path after battle two may use the R10
later_preview_supports_pairlimitation once P9.1 integration claims all six complete sessions.
10. Legacy Adapters and Deletion Criteria
| Legacy path | Why retained | Delete only when |
|---|---|---|
Root AppPage and page facade |
P9.0 tests and captures set/read it | Replacements cover every accepted transition and fixture, preserved tests are migrated in a dedicated reviewed commit, and no source uses it. |
| Root public state properties | P9.0 tests directly assert/mutate them | Domain snapshot/builder tests plus facade-independent integration tests cover the same facts, and a reference search is empty. |
| Root transaction wrappers | Existing touch/domain tests call them | All callbacks and tests call the service action map, parsed touch stays green, and reference search is empty. |
| Root render/animation helpers and sprite refs | Layout/capture/animation tests inspect them | Presenter/animation contract tests and normal-render evidence replace each assertion, with no root references. |
Historical FRONT_SELECTION / REAR_SELECTION |
Enum compatibility | No preserved evidence references them and automatic selection regressions pass after removal. |
later_preview_supports_pair() |
R10 honestly stops exceptional pairs after battle two | All six pairs complete five battles with P9.1 matrix and end-to-end evidence; the method and UI boundary copy are then removed together. |
Orphan domain/run_state.gd and old tests/p9_1_* |
Separate prior lineage, deletion not owned here | A frozen Goal classifies their product intent and either restores them as a separate module or removes them with independent evidence. |
Deletion is always integration-owned, source-searched, tested and committed separately from feature addition. Compatibility aliases do not become a permanent alternative API.
11. Integration Risks and Responses
| Risk | Earliest detectable boundary | Required response |
|---|---|---|
| Two mutable sources of truth in root and state object | Step 1 snapshot/facade assertions | Stop; keep field in one owner or redesign forwarding. Never synchronize copies per frame. |
| Rewritten tests pass while behavior regresses | Any extraction step | Preserve existing P9.0 assertions; compare exact markers, values, node names and normal rendering before test migration. |
| Frame timing changes outcomes | Step 3/4 large-delta and headless tests | Outcome must move to named domain impacts; clock only requests them. |
| Dictionary schema drifts between parallel workers | Parallel compile/schema tests | Reconcile constants/schema before wiring; no UI fallback keys or silent defaults for required fields. |
| UI duplicates pair/coverage/result logic | Presenter contract/static dependency check | Return to UI role; consume facts and available actions instead. |
main.gd becomes a second integration battlefield |
Parallel write audit | Only integration edits it after foundation; domain/UI/tests remain in exact owned paths. |
| Legacy opening-market files are included in the wrong pass claim | Step 0 manifest | Quarantine in an explicit runner group and report separately. |
Full DisplayRoot rebuild loses focus/input or stale references |
Step 5 touch/layout/cleanup | Preserve facade refs during extraction; later incremental rendering requires its own frozen behavior-equivalent step. |
| Snapshot deep copies create low-power cost | Pure benchmark after correctness | Measure first. Optimize snapshot sections/revision caching without giving UI mutable references. |
| Godot import/class cache makes isolated files appear valid | Foundation closure clean import | Run from clean import/cache evidence and compile every new script through the actual project. |
| Compatibility layer never disappears | Integration closure audit | Apply deletion criteria and record remaining adapters with owner and follow-up; do not add new facade-only feature APIs. |
12. Foundation Handoff Checklist
Before the foundation developer is dispatched, the reconciled contract must answer each item with a stable reference:
- final P9.1 screen names and whether
session_resultreplaces or extends the historicalTHIRD_CYCLE_RESULTlabel; - stable member IDs and decision IDs supplied by game design;
- UX-owned new Control names and result fact ordering;
- exact
factsandavailable_actionsrequired on each P9.1 screen; - low-power semantics that may affect presentation sampling only;
- the exact accepted P9.0R1-R10 serial runner manifest and orphan-test policy;
- final shared foundation paths and the three parallel write allowlists;
- the source revision from which all parallel worktrees branch.
If any item is unresolved, the earliest failed boundary is specification reconciliation; runtime extraction must remain frozen.