719 lines
40 KiB
Markdown
719 lines
40 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.
|
|
|
|
## 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
|