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