Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
434656e593 |
@@ -0,0 +1,630 @@
|
|||||||
|
# P9.1 Runtime Architecture Specification
|
||||||
|
|
||||||
|
> Status: expert input for PMO reconciliation
|
||||||
|
> Baseline: `dec9574405043624acee0f4f81963d829e8d40e9`
|
||||||
|
> Preserved product source: P9.0R10 at `41204acafa911a505f5b24a493aa032983e706d1`
|
||||||
|
> Scope: 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:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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.AppPage` numeric names and `game.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, `render` and `clear_experience`;
|
||||||
|
- `ANIMATION_SPECS`, duration helpers, animation lookup and sprite references;
|
||||||
|
- stable Control names searched recursively and real
|
||||||
|
`Input.parse_input_event` touch 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:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
{
|
||||||
|
"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:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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.
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
{
|
||||||
|
"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:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
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:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
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 `AppPage` names and `page` getter/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_cue` and `queue_render`;
|
||||||
|
- `phase_duration`, `phase_impact_time`, `advance_battle_phase` and
|
||||||
|
`commit_phase_impact` delegating to fixture/clock/domain boundaries;
|
||||||
|
- animation constants/helpers and sprite references delegating to presentation;
|
||||||
|
- `render` and stable Control names delegating to `SessionPresenter`;
|
||||||
|
- `clear_experience` calling 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
|
||||||
|
|
||||||
|
1. Record exact baseline source, Godot version and the ordered P9.0R1-R10 test
|
||||||
|
command/marker manifest in a runner-owned evidence file.
|
||||||
|
2. Confirm the accepted suite count from the R10 closure evidence rather than
|
||||||
|
globbing every file named `p9_1_*`.
|
||||||
|
3. 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.gd` is 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
|
||||||
|
|
||||||
|
1. **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.
|
||||||
|
2. **Facade regressions** keep the accepted P9.0 tests unchanged while the
|
||||||
|
facade exists. They prove property/method/node compatibility.
|
||||||
|
3. **Presenter contract tests** feed fixed snapshots/events and assert stable
|
||||||
|
controls, available/disabled actions, localized fact rendering and absence
|
||||||
|
of duplicated outcome logic.
|
||||||
|
4. **Clock integration tests** drive normal, low-FPS/large-delta, reduced-motion
|
||||||
|
and paused paths and compare the final domain snapshot.
|
||||||
|
5. **End-to-end parsed input** uses `Input.parse_input_event` ScreenTouch and
|
||||||
|
ScreenDrag through the complete session with fallback/direct zero.
|
||||||
|
6. **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.
|
||||||
|
7. **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.gd` or rule content directly;
|
||||||
|
- domain files extending `Node`/`Control` or referencing `SceneTree`, `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:
|
||||||
|
|
||||||
|
1. A successful command is atomic and increases `revision` exactly once.
|
||||||
|
2. A rejected command changes no snapshot field, event history, resource,
|
||||||
|
reward, clock or page.
|
||||||
|
3. Formation order is derived from selected member identities, never click
|
||||||
|
order, and commits once.
|
||||||
|
4. Choice selection is revisable and free; its commit locks once and is the
|
||||||
|
only spend/state-change boundary.
|
||||||
|
5. An impact is keyed by encounter + phase + impact ID and commits at most once.
|
||||||
|
6. Feedback is the exact semantic event created inside that impact mutation.
|
||||||
|
7. Rewards commit only after their declared victory boundary and at most once.
|
||||||
|
8. `reset_transient` clears all run and playback state while preserving
|
||||||
|
session-local presentation settings.
|
||||||
|
9. Domain output is identical across render FPS, reduced motion, mute, low
|
||||||
|
power and viewport size.
|
||||||
|
10. UI enables from `available_actions`, displays domain facts and never
|
||||||
|
predicts or recalculates outcomes.
|
||||||
|
11. Chinese and English are formatting over the same semantic facts; locale
|
||||||
|
changes no domain snapshot except the shell-composed settings section.
|
||||||
|
12. No path after battle two may use the R10 `later_preview_supports_pair`
|
||||||
|
limitation 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_result` replaces or extends the
|
||||||
|
historical `THIRD_CYCLE_RESULT` label;
|
||||||
|
- stable member IDs and decision IDs supplied by game design;
|
||||||
|
- UX-owned new Control names and result fact ordering;
|
||||||
|
- exact `facts` and `available_actions` required 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.
|
||||||
Reference in New Issue
Block a user