rrt/docs/runtime-rehost-plan.md

512 lines
12 KiB
Markdown

# 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.