Add headless runtime tooling and Campaign.win analysis
This commit is contained in:
parent
57bf0666e0
commit
27172e3786
37 changed files with 11867 additions and 302 deletions
512
docs/runtime-rehost-plan.md
Normal file
512
docs/runtime-rehost-plan.md
Normal file
|
|
@ -0,0 +1,512 @@
|
|||
# Runtime Rehost Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Replace the shell-dependent execution path with a bottom-up runtime that can be tested headlessly,
|
||||
grown incrementally, and later support one or more replacement frontends.
|
||||
|
||||
This plan assumes the current shell and presentation path remain unreliable for the near term. We
|
||||
therefore treat shell recovery as a later adapter problem rather than as the primary execution
|
||||
milestone.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
Current static analysis points to one important constraint: the existing gameplay cadence is still
|
||||
nested under shell-owned frame and controller ownership rather than under one clean detached
|
||||
gameplay loop.
|
||||
|
||||
- `simulation_frame_accumulate_and_step_world` is still called from the shell-owned cadence and
|
||||
still performs shell-window and presentation-adjacent servicing.
|
||||
- `shell_service_frame_cycle` owns frame refresh, deferred work, cursor updates, and one-time
|
||||
window visibility work.
|
||||
- the world-view input path and ordinary controller input path still flow through shell-owned
|
||||
objects and globals.
|
||||
|
||||
That makes shell-first stabilization a poor rewrite target. The better target is the lower runtime:
|
||||
calendar stepping, periodic world maintenance, scenario event service, runtime persistence, and the
|
||||
stateful collections beneath them.
|
||||
|
||||
## Architecture
|
||||
|
||||
The runtime rehost should be split into four layers.
|
||||
|
||||
### 1. `rrt-runtime`
|
||||
|
||||
Purpose:
|
||||
|
||||
- pure runtime state
|
||||
- deterministic stepping
|
||||
- scenario and company maintenance
|
||||
- persistence and normalization boundaries
|
||||
|
||||
Constraints:
|
||||
|
||||
- no controller-window ownership
|
||||
- no presentation refresh
|
||||
- no shell globals in the public model
|
||||
- no dependency on message pumps or platform input
|
||||
|
||||
### 2. `rrt-fixtures`
|
||||
|
||||
Purpose:
|
||||
|
||||
- fixture schemas
|
||||
- captured-state loading
|
||||
- golden summaries and diff helpers
|
||||
- normalization helpers for comparing original-runtime outputs against rehosted outputs
|
||||
|
||||
### 3. `rrt-cli`
|
||||
|
||||
Purpose:
|
||||
|
||||
- headless runtime driver
|
||||
- fixture execution commands
|
||||
- state diff and round-trip tools
|
||||
|
||||
This should become the first practical execution surface for the new runtime.
|
||||
|
||||
### 4. Future adapter layers
|
||||
|
||||
Possible later crates:
|
||||
|
||||
- `rrt-shell-adapter`
|
||||
- `rrt-ui`
|
||||
- `rrt-hook` capture bridges
|
||||
|
||||
These should adapt to `rrt-runtime`, not own the core simulation model.
|
||||
|
||||
## Rewrite Principles
|
||||
|
||||
1. Prefer state-in, state-out functions over shell-owned coordinators.
|
||||
2. Choose narrow vertical slices that can be verified with fixtures.
|
||||
3. Treat persistence boundaries as assets, because they give us reproducible test inputs.
|
||||
4. Normalize state aggressively before diffing so comparisons stay stable.
|
||||
5. Do not rehost shell or input code until the lower runtime already has a trustworthy step API.
|
||||
|
||||
## Candidate Boundaries
|
||||
|
||||
Good early targets:
|
||||
|
||||
- calendar-point arithmetic and step quantization helpers
|
||||
- `simulation_advance_to_target_calendar_point`
|
||||
- `simulation_service_periodic_boundary_work`
|
||||
- `scenario_event_collection_service_runtime_effect_records_for_trigger_kind`
|
||||
- `world_load_saved_runtime_state_bundle`
|
||||
- `world_runtime_serialize_smp_bundle`
|
||||
- company and placed-structure refresh helpers that look like collection or state transforms
|
||||
|
||||
Poor early targets:
|
||||
|
||||
- `simulation_frame_accumulate_and_step_world`
|
||||
- `world_entry_transition_and_runtime_bringup`
|
||||
- `shell_controller_window_message_dispatch`
|
||||
- world-view camera and cursor service helpers
|
||||
- shell window constructors or message handlers
|
||||
|
||||
## Milestones
|
||||
|
||||
### Milestone 0: Scaffolding
|
||||
|
||||
Goal:
|
||||
|
||||
- create the workspace shape for bottom-up runtime work
|
||||
- define fixture formats and CLI entrypoints
|
||||
- make the first headless command runnable even before the runtime is featureful
|
||||
|
||||
Deliverables:
|
||||
|
||||
- new crate `crates/rrt-runtime`
|
||||
- new crate `crates/rrt-fixtures`
|
||||
- `rrt-cli` subcommands for runtime and fixture work
|
||||
- initial fixture file format checked into the repo
|
||||
- baseline docs for state normalization and comparison policy
|
||||
|
||||
Exit criteria:
|
||||
|
||||
- `cargo run -p rrt-cli -- runtime validate-fixture <path>` works
|
||||
- one sample fixture parses and normalizes successfully
|
||||
- the new crates build in the workspace
|
||||
|
||||
### Milestone 1: Deterministic Step Kernel
|
||||
|
||||
Goal:
|
||||
|
||||
- stand up a minimal runtime state and one deterministic stepping API
|
||||
- prove that a world can advance without any shell or presentation owner
|
||||
|
||||
Deliverables:
|
||||
|
||||
- calendar-point representation in Rust
|
||||
- reduced `RuntimeState` model sufficient for stepping
|
||||
- one `advance_to_target_calendar_point` execution path
|
||||
- deterministic smoke fixtures
|
||||
- human-readable state diff output
|
||||
|
||||
Exit criteria:
|
||||
|
||||
- one minimal fixture can advance to a target point and produce stable repeated output
|
||||
- the same fixture can run for N steps with identical results across repeated runs
|
||||
- state summaries cover the calendar tuple and a small set of world counters
|
||||
|
||||
### Milestone 2: Periodic Service Kernel
|
||||
|
||||
Goal:
|
||||
|
||||
- add recurring maintenance and trigger dispatch on top of the first step kernel
|
||||
|
||||
Deliverables:
|
||||
|
||||
- periodic boundary service modes
|
||||
- trigger-kind dispatch scaffolding
|
||||
- the first runtime-effect service path behind a stable API
|
||||
- fixture coverage for one or two trigger kinds
|
||||
|
||||
Exit criteria:
|
||||
|
||||
- one fixture can execute periodic maintenance without shell state
|
||||
- trigger-kind-specific effects can be observed in a normalized diff
|
||||
|
||||
### Milestone 3: Persistence Boundary
|
||||
|
||||
Goal:
|
||||
|
||||
- load and save enough runtime state to support realistic fixtures
|
||||
|
||||
Deliverables:
|
||||
|
||||
- serializer and loader support for a narrow `.smp` subset or an equivalent normalized fixture view
|
||||
- round-trip tests
|
||||
- versioned normalization rules
|
||||
|
||||
Exit criteria:
|
||||
|
||||
- one captured runtime fixture can be round-tripped with stable normalized output
|
||||
|
||||
### Milestone 4: Domain Expansion
|
||||
|
||||
Goal:
|
||||
|
||||
- add the minimum missing subsystems needed by failing fixtures
|
||||
|
||||
Likely order:
|
||||
|
||||
- company maintenance
|
||||
- scenario event service breadth
|
||||
- placed-structure local-runtime refresh
|
||||
- candidate and cargo-service tables
|
||||
- locomotive availability refresh
|
||||
|
||||
Exit criteria:
|
||||
|
||||
- representative fixtures from multiple subsystems can step and diff without shell ownership
|
||||
|
||||
### Milestone 5: Adapter and Frontend Re-entry
|
||||
|
||||
Goal:
|
||||
|
||||
- connect the runtime core to external control surfaces
|
||||
|
||||
Possible outputs:
|
||||
|
||||
- shell-compatibility adapter
|
||||
- replacement CLI workflows
|
||||
- replacement UI or tool surfaces
|
||||
|
||||
Exit criteria:
|
||||
|
||||
- external commands operate by calling runtime APIs rather than by reaching into shell-owned state
|
||||
|
||||
## Fixture Strategy
|
||||
|
||||
We should maintain three fixture classes from the start.
|
||||
|
||||
### `minimal-world`
|
||||
|
||||
Small synthetic fixtures for deterministic kernel testing.
|
||||
|
||||
Use for:
|
||||
|
||||
- calendar stepping
|
||||
- periodic maintenance
|
||||
- invariants and smoke tests
|
||||
|
||||
### `captured-runtime`
|
||||
|
||||
Fixtures captured from the original process or from serialized runtime state.
|
||||
|
||||
Use for:
|
||||
|
||||
- parity checks
|
||||
- subsystem-specific debugging
|
||||
- rehosted function validation
|
||||
|
||||
### `roundtrip-save`
|
||||
|
||||
Persistence-oriented fixtures built around real save data or normalized equivalents.
|
||||
|
||||
Use for:
|
||||
|
||||
- serializer validation
|
||||
- normalization rules
|
||||
- regression tests
|
||||
|
||||
Each fixture should contain:
|
||||
|
||||
- metadata
|
||||
- format version
|
||||
- source provenance
|
||||
- input state
|
||||
- command list
|
||||
- expected summary
|
||||
- optional expected normalized full state
|
||||
|
||||
## Normalization Policy
|
||||
|
||||
Runtime diffs will be noisy unless we define a normalization layer early.
|
||||
|
||||
Normalize away:
|
||||
|
||||
- pointer addresses
|
||||
- allocation order
|
||||
- container iteration order when semantically unordered
|
||||
- shell-only dirty flags or presentation counters
|
||||
- timestamps that are not semantically relevant to the tested behavior
|
||||
|
||||
Keep:
|
||||
|
||||
- calendar tuple
|
||||
- company and world ids
|
||||
- cash, debt, and game-speed-related runtime fields when semantically relevant
|
||||
- collection contents and semantic counts
|
||||
- trigger-side effects
|
||||
|
||||
## Risks
|
||||
|
||||
### Hidden shell coupling
|
||||
|
||||
Some lower functions still touch shell globals indirectly. We should isolate those reads quickly and
|
||||
replace them with explicit runtime inputs where possible.
|
||||
|
||||
### Fixture incompleteness
|
||||
|
||||
Captured state that omits one manager table can make deterministic functions appear unstable. The
|
||||
fixture format should make missing dependencies obvious.
|
||||
|
||||
### Over-scoped early rewrites
|
||||
|
||||
The first two milestones should remain deliberately small. Do not pull in company UI, world-view
|
||||
camera work, or shell windows just because their names are nearby in the call graph.
|
||||
|
||||
## Milestone 0 Specifics
|
||||
|
||||
This milestone is mostly repo shaping and interface definition.
|
||||
|
||||
### Proposed crate layout
|
||||
|
||||
`crates/rrt-runtime`
|
||||
|
||||
- `src/lib.rs`
|
||||
- `src/calendar.rs`
|
||||
- `src/runtime.rs`
|
||||
- `src/step.rs`
|
||||
- `src/summary.rs`
|
||||
|
||||
`crates/rrt-fixtures`
|
||||
|
||||
- `src/lib.rs`
|
||||
- `src/schema.rs`
|
||||
- `src/load.rs`
|
||||
- `src/normalize.rs`
|
||||
- `src/diff.rs`
|
||||
|
||||
### Proposed first public types
|
||||
|
||||
In `rrt-runtime`:
|
||||
|
||||
- `CalendarPoint`
|
||||
- `RuntimeState`
|
||||
- `StepCommand`
|
||||
- `StepResult`
|
||||
- `RuntimeSummary`
|
||||
|
||||
In `rrt-fixtures`:
|
||||
|
||||
- `FixtureDocument`
|
||||
- `FixtureCommand`
|
||||
- `ExpectedSummary`
|
||||
- `NormalizedState`
|
||||
|
||||
### Proposed CLI surface
|
||||
|
||||
Add a `runtime` command family to `rrt-cli`.
|
||||
|
||||
Initial commands:
|
||||
|
||||
- `rrt-cli runtime validate-fixture <fixture.json>`
|
||||
- `rrt-cli runtime summarize-fixture <fixture.json>`
|
||||
- `rrt-cli runtime diff-state <left.json> <right.json>`
|
||||
|
||||
These commands do not require a complete runtime implementation yet. They only need to parse,
|
||||
normalize, and summarize.
|
||||
|
||||
### Proposed fixture shape
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"format_version": 1,
|
||||
"fixture_id": "minimal-world-step-smoke",
|
||||
"source": {
|
||||
"kind": "synthetic"
|
||||
},
|
||||
"state": {
|
||||
"calendar": {
|
||||
"year": 1830,
|
||||
"month_slot": 0,
|
||||
"phase_slot": 0,
|
||||
"tick_slot": 0
|
||||
},
|
||||
"world_flags": {},
|
||||
"companies": [],
|
||||
"event_runtime_records": []
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"kind": "advance_to",
|
||||
"calendar": {
|
||||
"year": 1830,
|
||||
"month_slot": 0,
|
||||
"phase_slot": 1,
|
||||
"tick_slot": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"expected_summary": {
|
||||
"calendar_year": 1830
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Milestone 0 task list
|
||||
|
||||
1. Add the two new workspace crates.
|
||||
2. Add serde-backed fixture schema types.
|
||||
3. Add a small summary model for runtime fixtures.
|
||||
4. Extend `rrt-cli` parsing with the `runtime` command family.
|
||||
5. Check in one synthetic fixture under a new tracked fixtures directory.
|
||||
6. Add tests for fixture parsing and normalization.
|
||||
|
||||
## Milestone 1 Specifics
|
||||
|
||||
This milestone is the first real execution step.
|
||||
|
||||
### Scope
|
||||
|
||||
Implement only enough runtime to support:
|
||||
|
||||
- calendar-point representation
|
||||
- comparison and ordering
|
||||
- `advance_to` over a reduced world state
|
||||
- summary and diff output
|
||||
|
||||
Do not yet model:
|
||||
|
||||
- shell-facing frame accumulators
|
||||
- input
|
||||
- camera or cursor state
|
||||
- shell windows
|
||||
- full company, site, or cargo behavior
|
||||
|
||||
### Proposed runtime model
|
||||
|
||||
`CalendarPoint`
|
||||
|
||||
- year
|
||||
- month_slot
|
||||
- phase_slot
|
||||
- tick_slot
|
||||
|
||||
Methods:
|
||||
|
||||
- ordering and comparison
|
||||
- step forward one minimal unit
|
||||
- convert to and from normalized serialized form
|
||||
|
||||
`RuntimeState`
|
||||
|
||||
- current calendar point
|
||||
- selected year or absolute calendar scalar if needed
|
||||
- minimal world counters
|
||||
- optional minimal event-service scratch state
|
||||
|
||||
`StepCommand`
|
||||
|
||||
- `AdvanceTo(CalendarPoint)`
|
||||
- `StepCount(u32)`
|
||||
|
||||
`StepResult`
|
||||
|
||||
- start summary
|
||||
- end summary
|
||||
- steps_executed
|
||||
- boundary_events
|
||||
|
||||
### Milestone 1 execution API
|
||||
|
||||
Suggested Rust signature:
|
||||
|
||||
```rust
|
||||
pub fn execute_step_command(
|
||||
state: &mut RuntimeState,
|
||||
command: StepCommand,
|
||||
) -> StepResult
|
||||
```
|
||||
|
||||
Internally this should call a narrow helper shaped like:
|
||||
|
||||
```rust
|
||||
pub fn advance_to_target_calendar_point(
|
||||
state: &mut RuntimeState,
|
||||
target: CalendarPoint,
|
||||
) -> StepResult
|
||||
```
|
||||
|
||||
### Milestone 1 fixture set
|
||||
|
||||
Add at least these fixtures:
|
||||
|
||||
1. `minimal-world-advance-one-phase`
|
||||
2. `minimal-world-advance-multiple-phases`
|
||||
3. `minimal-world-step-count`
|
||||
4. `minimal-world-repeatability`
|
||||
|
||||
### Milestone 1 verification
|
||||
|
||||
Required checks:
|
||||
|
||||
- repeated runs produce byte-identical normalized summaries
|
||||
- target calendar point is reached exactly
|
||||
- step count matches expected traversal
|
||||
- backward targets fail cleanly or no-op according to chosen policy
|
||||
|
||||
### Milestone 1 task list
|
||||
|
||||
1. Implement `CalendarPoint`.
|
||||
2. Implement reduced `RuntimeState`.
|
||||
3. Implement `advance_to_target_calendar_point`.
|
||||
4. Implement CLI execution for one fixture command list.
|
||||
5. Emit normalized summaries after execution.
|
||||
6. Add deterministic regression tests for the initial fixtures.
|
||||
|
||||
## Immediate Next Actions
|
||||
|
||||
After this document lands, the recommended first implementation sequence is:
|
||||
|
||||
1. add `rrt-runtime` and `rrt-fixtures`
|
||||
2. extend `rrt-cli` with `runtime validate-fixture`
|
||||
3. add one synthetic fixture
|
||||
4. implement `CalendarPoint`
|
||||
5. implement one narrow `advance_to` path
|
||||
|
||||
That sequence gives the project a headless execution backbone without needing shell recovery first.
|
||||
Loading…
Add table
Add a link
Reference in a new issue