rrt/docs/runtime-rehost-plan.md

40 KiB

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.

Current Baseline

The repo is already past pure scaffolding.

Implemented today:

  • rrt-runtime exists with a deterministic calendar model, step commands, runtime summaries, and normalized runtime state validation
  • periodic trigger dispatch exists, including ordered periodic maintenance, dirty rerun 0x0a, and a normalized runtime-effect surface with staged event-record mutation
  • snapshots, state inputs, save-slice projection, and normalized state diffing already exist in the CLI and fixture layers
  • checked-in runtime fixtures already cover deterministic stepping, periodic service, direct trigger service, snapshot-backed inputs, save-slice-backed inputs, overlay-import-backed inputs, normalized state-fragment assertions, and imported packed-event execution
  • overlay imports now preserve selected-company and controller-role context, and the normalized company-target model can execute selected_company, human_companies, and ai_companies symbolic scopes through the ordinary runtime service path while keeping condition-relative company scopes explicitly blocked
  • real 0x4e9a grouped rows now carry checked-in descriptor metadata, semantic family/preview summaries, and three recovered executable company-scoped families: descriptor 2 = Company Cash, descriptor 13 = Deactivate Company, and descriptor 16 = Company Track Pieces Buildable
  • the first grounded condition-side unlock now exists for real packed rows: negative-sentinel raw_condition_id = -1 company scope lowers condition_true_company into normalized company targets during import
  • the first ordinary nonnegative condition-id batch now executes too: numeric-threshold company finance, company track, aggregate territory track, and company-territory track rows can import through overlay-backed runtime context
  • exact named-territory binding now lowers candidate-name ordinary rows onto tracked territory names, a minimal player runtime now carries selected-player and role context, and real descriptor 1 = Player Cash and descriptor 14 = Deactivate Player now import and execute through the ordinary runtime path
  • a first-class chairman-profile runtime now exists too, with overlay-backed selected-chairman context and the first chairman-targeted grouped-effect subset: the same real descriptors 1 = Player Cash and 14 = Deactivate Player now also import and execute through the hidden grouped target-subject lane when it resolves to selected-chairman scope, while broader chairman target scopes remain explicit parity on blocked_chairman_target_scope
  • chairman governance state is broader now too: companies carry explicit chairman links plus book-value-per-share, investor-confidence, management-attitude, and takeover/merger cooldown lanes, and the first grounded chairman/control-transfer ordinary-condition batch now imports and executes through the same path for selected-chairman cash / holdings / net-worth / purchasing-power thresholds plus company book-value-per-share / investor-confidence / management-attitude thresholds; real chairman grouped-effect scope ordinals 0..3 now execute too as condition_true, selected, human, and ai, while wider ordinals remain parity frontier
  • checked-in save-slice documents can now carry explicit company rosters and chairman profile tables too, and runtime projection/import will seed or replace company/chairman context from those save-owned surfaces; that lets the currently supported company-targeted and chairman-targeted descriptor/condition batches execute from standalone save-slice fixtures without overlay snapshots when the checked-in documents include that context, and raw .gms inspection/export now reconstructs company/chairman direct-record entries too; the fixed 0x32c8 world block still contributes selected ids plus the grounded campaign-override, issue-0x37 value/multiplier, and chairman slot / role-gate analysis bytes, while the tagged company and chairman/profile collections now provide save-native roster entries and observed_entry_count; raw company debt from the bond table and raw company track-laying capacity from the record tail are grounded too, and chairman purchasing power now reuses the strongest nonnegative cached qword total from the [profile+0x1e9..] band plus current cash instead of collapsing to plain net worth; the same fixed world payload now exposes the grounded issue-0x37 pair at [world+0x29/+0x2d], one broader fixed-dword finance neighborhood around the absolute-calendar and nearby issue lanes, and the separate six-float economic tuning band [world+0x0be2..+0x0bf6] through save inspection too, but current atlas evidence still keeps that editor-tuning family separate from the company-governance issue lanes; the next shared company-side owning state is rehosted now too, because save-native company direct records now project into a typed runtime company_market_state cache map carrying outstanding shares, support/share-price/cache words, chairman salary lanes, calendar words, and connection latches for each live company; and runtime inspect-save-company-chairman <save.gms> now exposes the remaining raw company/chairman scalar candidates directly from the rehosted parser, including fixed-world chairman slot / role-gate context, company dword candidate windows, richer chairman qword cache views, and derived holdings-at-share-price / cached purchasing-power comparisons; the remaining raw-save boundary is company-finance/governance scalar depth plus controller-kind closure, not roster absence
  • a checked-in EventEffects export now exists too at artifacts/exports/rt3-1.06/event-effects-table.json, and a checked-in semantic closure layer now exists at artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json
  • recovered descriptor rows now classify into explicit semantic frontier buckets such as blocked_shell_owned_descriptor, blocked_evidence_blocked_descriptor, and blocked_variant_or_scope_blocked_descriptor instead of generic unmapped-descriptor buckets
  • the first recovered company-governance descriptor tranche now executes through the generic SetCompanyGovernanceScalar surface: descriptor 56 Credit Rating and descriptor 57 Prime Rate now import through ordinary company target lowering
  • adjacent recovered finance/control-transfer descriptors such as 55 Stock Prices and 58 Merger Premium now land on explicit shell-owned descriptor parity instead of generic unmapped descriptor buckets, with tracked fixtures now pinning finance, scenario-outcome, and control-transfer shell rows explicitly
  • the recovered whole-game scalar economy/performance strip 59..104 now has a bounded runtime landing surface too: representative descriptors import as SetWorldScalarOverride and land in RuntimeState.world_scalar_overrides
  • the runtime-variable strip 39..54 now imports and executes too through bounded event-owned world/company/player/territory variable maps, and the matching ordinary-condition strip now gates runtime records through those same maps too, so those descriptors and conditions no longer depend on generic evidence parity or ad hoc fixture-only state
  • the grounded aggregate cargo-economics descriptors now execute too: descriptor 105 All Cargo Prices and descriptors 177..179 All Cargo Production / All Factory Production / All Farm/Mine Production import through bounded cargo override surfaces, and the grounded named cargo-production strip 180..229 now imports through named cargo production overrides too
  • the named cargo-price strip 106..176 now imports through named cargo price overrides too; the checked-in static selector reconstruction is now explicit because the broader 1.06 CargoTypes corpus has 51 names, the Cargo106 cargoSkin corpus has 70, and the rehosted offline selector builder now closes the 71-row price strip as cargoSkin plus the core Rock carry-over; a checked-in offline cargo-source report at artifacts/exports/rt3-1.06/economy-cargo-sources.json now parses both CargoTypes and the Cargo106.PK4 cargoSkin descriptors through rehosted code, normalizes localized ~####Name tokens into visible names, builds a merged live cargo registry, and now derives exact named cargo-production and named cargo-price selectors from the checked-in bindings; dedicated CLI inspector commands now expose both grounded selectors directly, while the same report still shows the nine excluded CargoTypes-only industrial names that sit outside the 71-row price strip
  • the add-building strip 503..519 is now explicitly classified as recovered shell-owned parity with tracked fixture coverage, not generic unresolved descriptor residue
  • a minimal event-owned train surface and an opaque economic-status lane now exist in runtime state, and real descriptors 8 = Economic Status, 9 = Confiscate All, and 15 = Retire Train now import and execute through the ordinary runtime path when overlay context supplies the required train ownership data
  • descriptor 3 = Territory - Allow All now imports and executes too, reinterpreted as company-to-territory access rights instead of a territory-owned policy bit; shell purchase-flow and selected-profile parity still remain outside the runtime surface
  • the first whole-game ordinary-condition batch now executes too: special-condition thresholds, candidate-availability thresholds, and economic-status-code thresholds now gate runtime records through the same service path, and whole-game parity frontiers now report explicit unmapped world-condition and world-descriptor buckets rather than falling back to the generic ordinary or descriptor counts
  • checked-in whole-game condition metadata now drives that same world-side condition batch too: special-condition thresholds use the real special-condition label ids, economic-status thresholds use the checked-in world label id, and candidate-availability thresholds now decode through the recovered %1 Avail. template plus the packed candidate-name side string instead of fixture-only placeholder ids
  • checked-in whole-game grouped-descriptor metadata now drives the first real world-side effect batch too: real special-condition and candidate-availability setter rows now decode and import through the ordinary runtime path, and the first real world-flag family now executes too: descriptor 110 Disable Stock Buying and Selling lowers through checked-in keyed runtime metadata into world.disable_stock_buying_and_selling
  • that world-side effect batch now covers a broader recovered boolean scenario-rule family too: descriptors 111..138 now lower through checked-in metadata into either keyed world_flags or the bounded world_restore.limited_track_building_amount scalar for finance/trading, construction, and governance/company-formation restrictions
  • the same keyed world-toggle path now also covers the late special-condition band where current static evidence stays equally strong: Use Bio-Accelerator Cars, Disable Cargo Economy, Disable Train Crashes, Disable Train Crashes AND Breakdowns, and AI Ignore Territories At Startup now import and execute as keyed world_flags
  • whole-game ordinary-condition decode now covers checked-in world-flag condition ids too for boolean equality/inequality forms, so real packed rows can gate whole-game effects on existing world_flags through world_flag_equals without fixture-authored placeholder ids
  • the tracked parity save-slice no longer preserves an opaque unsupported_framing record; its remaining captured residue is now structurally decoded real_packed_v1 parity state that lands in existing explicit blocker buckets, with the first leftover now pinned to the unresolved upper locomotives-page availability tail instead of anonymous descriptor residue
  • the next recovered locomotives-page descriptor band is now partially executable too: descriptors 230..240, 241..351, 352..451, 453, 457..474, and 475..502 now carry recovered world-side scalar metadata, while descriptors 454..456 (All Steam/Diesel/Electric Locos Avail.) now execute as keyed world_flags
  • a first-class named locomotive availability runtime surface now exists too: save-slice documents can carry the persisted [world+0x66b6] name table into RuntimeState.named_locomotive_availability, and imported runtime effects can mutate that map through the ordinary event-service path without requiring Trainbuy or live locomotive-pool parity
  • the recovered locomotives-page availability bands can now import as full scalar overrides through RuntimeState.locomotive_catalog into RuntimeState.named_locomotive_availability; raw .smp inspection/export now reconstructs the save-side locomotive row family and derives the catalog directly into save-slice documents, so standalone save-slice imports can execute the full lower availability band 241..351 whenever the save carries enough catalog entries; the checked 29-save .gms + .gmx locomotive-catalog-tail-census.json export now fixes the last save-stable static boundary at ordinal 58 (VL80T), leaving the upper bands 457..474, 475..502, plus descriptor 452 as external-corpus or dynamic blockers instead of active repo-local static work
  • the full lower locomotive-cost band 352..451 now imports too through the same save-native or embedded catalog into the event-owned RuntimeState.named_locomotive_cost map when its scalar payloads are nonnegative; the remaining unresolved tail is the separate descriptor 452 plus the upper cost band 475..502
  • the remaining recovered scalar world families now execute as well: cargo-production 230..240 rows lower into slot-indexed cargo_production_overrides, and territory-access-cost descriptor 453 lowers into world_restore.territory_access_cost
  • cargo slot identity is now save-native as well: the recipe-book probe derives a bounded cargo_catalog into save-slice documents and runtime state, so cargo descriptors and %1 Production conditions can expose stable slot metadata without a live cargo simulation layer
  • world-scalar ordinary-condition coverage now aligns with those runtime surfaces too: checked-in metadata lowers named locomotive availability, named locomotive cost, named cargo-production slot thresholds, aggregate cargo production, factory/farm-mine/other cargo production, limited-track-building-amount, and territory-access-cost rows into explicit runtime condition gates
  • cargo slot classification is now checked in across the full 11-slot recipe-book surface, so the remaining world-side frontier is broader descriptor/condition breadth rather than missing cargo classification or save/import context

That means the next implementation work is still breadth, not bootstrap. The current descriptor frontier is no longer anonymous id recovery; it is the remaining recovered-but-nonexecutable families from the checked-in semantic catalog, especially cargo-price, add-building, and other descriptor clusters that now have explicit shell-owned or evidence-blocked status but not yet a bounded executable landing surface. Raw save reconstruction for company/chairman context is still a later tranche once stronger evidence exists, but the current project rule is explicit: prefer rehosting shared owner state and reader/setter families first, and only guess at one more leaf field when that richer owning-state path is blocked. Richer runtime ownership should still be added where later descriptor, stat-family, or simulation work needs more than the current event-owned roster. The current owned company-side roster now includes not just the market/cache lanes but also the first grounded stat-band root windows at [company+0x0cfb], [company+0x0d7f], and [company+0x1c47], and the first runtime-side 0x2329 stat-family reader seam is now rehosted for slots 0x0d and 0x1d, so later finance readers can target saved owner state and one shared reader family directly. Those stat-band windows now carry 32 dwords per root in the save-slice and runtime-owned company market state, and the matching world-side issue reader seam is now rehosted for the grounded 0x37 lane over save-native world restore state. The selected-company summary surface now also carries the unassigned share pool derived from outstanding shares minus chairman-held shares, so later dividend / stock-capital work can extend a shared owned-state reader instead of guessing another finance leaf. The same owned company market state now also supports a bundled annual-finance reader seam for assigned shares, public float, and rounded cached share price, which is a better base for later dividend / issue-calendar simulation than scattered single-field helpers. The fixed-world finance neighborhood is now widened to 17 dwords rooted at [world+0x0d], and the adjacent raw world issue-byte strip 0x37..0x3a now also flows through save-slice/runtime restore state, so later credit / prime / management readers can build on owned issue state instead of another narrow probe; that same owner surface now also carries the saved world absolute counter as first-class runtime restore state instead of shell-context metadata, plus the packed year word and partial-year progress lane that feed the annual-finance recent-history weighting path. Stepped world time now also refreshes the derived selected-year gap scalar owner lane [world+0x4ca2], so later selected-year periodic-boundary world work can build on runtime state instead of a frozen load-time scalar. That same save-native world restore surface now also carries the grounded locomotive-policy bytes and cached available-locomotive rating from the fixed world block, so the All Steam/Diesel/Electric Locos Avail. descriptor strip now writes through owner state instead of living only as mirrored world flags. The selected-year seam now follows the same owner rule: the checked-in 0x00433bd0 year ladder now drives a derived selected-year bucket scalar in runtime restore state, and the economic-tuning mirror [world+0x0bde] now rebuilds from tuning lane 0 instead of freezing one stale load-time word. That same checked-in owner family now also rebuilds the direct bucket trio [world+0x65/+0x69/+0x6d], the complement trio [world+0x71/+0x75/+0x79], and the scaled companion trio [world+0x7d/+0x81/+0x85] from the bucket scalar instead of preserving stale save-time residue. The same owned company annual-finance state now also drives a shared company market reader seam for stock-capital, salary, bonus, and the full two-word current/prior issue-calendar tuples, which is a better base for shellless finance simulation than summary-only helpers. That same owned annual-finance state now also derives elapsed years since founding, last dividend, and last bankruptcy from the runtime calendar, which lines up directly with the grounded annual finance-policy gates in the atlas. Live bond-slot count now also flows through that same owned company market and annual-finance state, matching the stock-capital branch gate that needs at least two live bonds. The same grounded bond table now also contributes both the largest live bond principal and the chosen highest-coupon live bond principal into owned company market and annual-finance state, and now also exposes the highest live coupon rate, so the stock-capital price-to-book ladder can extend a rehosted owner-state seam instead of guessing another finance leaf. The same fixed-world save block now also carries the raw stock, bond, bankruptcy, and dividend finance-policy bytes, and the earliest annual creditor-pressure bankruptcy branch now runs as a pure runtime reader over owned annual-finance state, support- adjusted share price, and those policy bytes rather than staying in atlas prose only. The later deep-distress bankruptcy fallback now rides the same owner-state seam too, using the save-native cash reader plus the first three trailing net-profit years instead of a parallel raw-offset guess. The annual bond lane now rides it as well, using the simulated post-repayment cash window plus the linked-transit threshold split to stage 500000 principal issue counts without shell ownership, and periodic boundary service now commits the same shellless matured-bond repay/compact/issue path instead of stopping at the staging reader. That same service seam now also carries the retired-versus-issued principal totals needed by the later debt-news tail, plus the issued-share and repurchased-share counts needed by the later equity-offering and buyback news tails. Runtime summaries also expose the grounded retired-versus-issued relation directly, and annual finance service now maps that relation onto the exact debt headline selectors 2882..2886. The same service state now also persists the last emitted annual-finance news events as structured runtime-owned records carrying company id, selector label, action label, and the grounded debt/share payload totals. The annual dividend-adjustment lane now rides that same seam too: the runtime now rehosts the shared year-or-control-transfer metric reader, the board-approved dividend ceiling helper, and the full annual dividend branch over owned cash, public float, current dividend, and building-growth policy instead of treating dividend changes as shell-dialog-only logic.

The workflow rule for this queue is explicit too: docs/rehost-queue.md is the control surface, final responses are stop-only, and active work should continue under commentary updates unless the queue is empty, no further non-hook work can advance it without guessing, or approval is needed. The same save-native company direct-record seam now also carries the full outer periodic-company side-latch trio rooted at 0x0d17/0x0d18/0x0d56, including the preferred-locomotive engine-type chooser byte beside the city-connection and linked-transit finance gates. That same seam now also resolves the base world route-preference byte at [world+0x4c74], the effective electric-only override fed by 0x0d17, and the matching 1.4x versus 1.8x route-quality multiplier as a first-class runtime reader rather than a loose atlas-only bridge. That same seam now also owns the first route-preference mutation lane directly: beginning the electric-only periodic-company override rewrites [world+0x4c74] to the effective company preference for the active service pass, ending the override restores the base world byte, and runtime service state now carries both the active and last applied override instead of leaving the route-preference seam as a pure reader note. Save inspection now also separates the shared 0x5209/0x520a/0x520b save family correctly: the smaller direct 0x1d5 collection is the live train family and now exposes a live-entry directory rooted at metadata dword 16, while the actual region collection is the larger non-direct Marker09 family. The tagged placed-structure header 0x36b1/0x36b2/0x36b3 remains grounded alongside them, so the remaining city-connection / linked-transit blocker is record-body reconstruction rather than missing save-side collection identity. That same seam now also carries the fixed-world building-density growth setting plus the linked chairman personality byte, which is enough to rehost the annual stock-repurchase gate on owned save/runtime state instead of another threshold-only note. The stock-capital issue branch now rides that same seam too, with share-pressure, cooldown, and price-to-book gate state exposed as normal runtime readers. Periodic boundary service now also uses that owner seam as a real chooser: the runtime selects one annual-finance action per active company and already commits the shellless creditor-pressure-bankruptcy, deep-distress-bankruptcy, dividend-adjustment, stock-repurchase, stock-issue, and bond-issue branches directly into owned dividend, company stat-post, outstanding-share, issue-calendar, live bond-slot, and company activity state. Shellless calendar advance now also starts to consume that service seam directly: StepCount and AdvanceTo invoke periodic-boundary service automatically on year rollover, so annual finance state can advance as the runtime clock advances instead of only through an explicit manual service command. The bankruptcy branches now follow the grounded owner semantics too: they stamp the bankruptcy year and halve live bond principals in place instead of collapsing into a liquidation-only path. The same owned live bond-slot surface now also carries maturity years through save import, runtime state, and annual bond summaries, which is the right next base for shellless repayment and bond-service simulation. The process rule for the remaining runtime work is explicit too: prefer rehosting owning state and real reader/setter families over guessing leaf fields, and use docs/rehost-queue.md as the checked-in control surface for the work loop. After each commit, check that queue and continue unless the queue is empty, a real blocker remains that cannot be advanced by any further non-hook work without guessing, or approval is needed. The same control surface now also has matching runtime probes: runtime inspect-periodic-company-service-trace <save.gms>, runtime inspect-region-service-trace <save.gms>, and runtime inspect-infrastructure-asset-trace <save.gms>. Use those first when the next blocked frontier is “which higher-layer owner seam is still missing?” rather than “which raw save collection exists?”. That same owner seam now also derives live coupon burden totals directly from saved bond slots, which gives later finance service work a bounded runtime reader instead of another synthetic finance leaf.

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 (complete)

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 (complete)

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 (partially complete)

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

Current status:

  • periodic trigger ordering is implemented
  • normalized trigger-side effects already exist for world flags, company cash/debt, candidate availability, and special conditions
  • one-shot handling, dirty reruns, and staged append/activate/deactivate/remove behavior are already covered by synthetic fixtures
  • the remaining breadth is richer trigger-family behavior and target resolution, not first-pass event-graph mutation

Milestone 3: Persistence Boundary (partially complete)

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

Current status:

  • runtime snapshots and state inputs are implemented
  • .smp save inspection and partial save-slice projection already feed normalized runtime state
  • the packed event-collection bridge now carries per-record summaries into loaded save slices, projected runtime snapshots, normalized diffs, and fixtures
  • the first decoded packed-event subset can now import into executable runtime records when the decoded actions fit the current normalized runtime-effect model
  • tracked save-slice documents now provide a repo-friendly captured-runtime path without checking in raw .smp binaries
  • overlay-backed captured-runtime inputs now provide enough runtime company context for symbolic selected-company and controller-role scopes without inventing company state from save bytes alone
  • aggregate territory context and company-territory track counters now flow through tracked overlay snapshots, named-territory binding now executes on exact matches, and a minimal player runtime is now present, so the remaining gap is broader ordinary condition-id coverage beyond numeric thresholds plus wider real grouped-descriptor and territory-policy semantic coverage, not first-pass captured-runtime plumbing

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
  • optional expected normalized state fragment when only part of the final state matters

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
  • packed event-collection structural summaries when present

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.

Implemented Baseline

The currently implemented normalized runtime surface is:

  • CalendarPoint, RuntimeState, StepCommand, StepResult, and RuntimeSummary
  • fixture loading from inline state, snapshots, and state inputs
  • runtime validate-fixture, runtime summarize-fixture, runtime export-fixture-state, runtime summarize-state, runtime snapshot-state, and runtime diff-state
  • deterministic stepping, periodic trigger dispatch, one-shot event handling, dirty reruns, and a normalized runtime-effect vocabulary with staged event-record mutation
  • save-side inspection and partial state projection for .smp inputs, including per-record packed event summaries and selective executable import
  • closure policy on the remaining frontier: prefer rehosting the owning source state and the real reader/setter family over guessing one more derived leaf metric from nearby save offsets
  • tracked save-slice documents plus save-slice-backed fixture loading for captured-runtime coverage

Checked-in fixture families already include:

  • deterministic minimal-world stepping
  • periodic boundary service
  • direct trigger-service mutation
  • staged event-record lifecycle coverage
  • snapshot-backed fixture execution

Next Slice

The recommended next implementation slice is broader ordinary-condition and grouped-descriptor breadth on top of the now-stable numeric-threshold, whole-game-state, overlay-context, named-territory, player, world/train, and company-territory-access batches.

Target behavior:

  • preserve the current proof set for real ordinary-condition execution: company finance, company track, aggregate territory track, and company-territory track numeric thresholds all pass through parse, semantic summary, overlay-backed import, and ordinary trigger execution
  • extend ordinary condition coverage beyond the current numeric-threshold and whole-game-state families only when comparator semantics, runtime ownership, and binding rules are grounded enough to lower honestly into the normalized runtime path
  • keep named-territory ordinary rows on exact case-sensitive binding until captured evidence justifies alias tables or fuzzier matching
  • keep player-owned condition scope within the minimal event runtime model until later slices need richer player metrics or profile/chairman ownership
  • continue widening real grouped-descriptor execution only when both descriptor identity and runtime effect semantics are grounded enough to map into the normalized runtime path honestly
  • keep descriptor 3 on the now-executable company-territory-access interpretation; do not drift back into territory-owned policy wording without new contrary evidence

Public-model expectations for that slice:

  • additional checked-in ordinary-condition metadata entries beyond the current numeric-threshold allowlist
  • richer ordinary-condition metadata and later runtime ownership only where new condition domains still remain blocked after the current named-territory and player-runtime unlocks
  • more selective real-row decoded_conditions and decoded_actions only where the condition/effect-to-runtime mapping is supported end to end

Fixture work for that slice:

  • preserve the new ordinary-condition tracked overlays for executable company finance, company track, aggregate territory track, and company-territory track thresholds
  • preserve the new whole-game tracked save-slice fixtures for executable special-condition and candidate-availability/economic-status batches, plus the parity-only world-condition and world-descriptor frontier sample
  • preserve the named-territory no-match tracked overlay as the explicit binding blocker frontier
  • preserve the territory-access tracked overlays and parity samples so descriptor 3 access-rights execution does not regress while other grouped families widen
  • keep the older negative-sentinel, mixed real-row, and company-scoped descriptor fixtures green so ordinary-condition breadth does not regress descriptor-side execution
  • keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens

Current local constraint:

  • the local checked-in and on-disk .gms corpus still does not provide a richer captured packed event save set, so wider ordinary-condition and descriptor recovery still needs to rely on the grounded static tables and tracked JSON artifacts until new captures exist

Do not mix this slice with:

  • shell queue/modal behavior
  • shell territory-access purchase or selected-profile parity
  • speculative condition execution without grounded runtime ownership
  • speculative executable import for real rows whose descriptor meaning is still weak