Skip to content

Spec: decompose the simulation orchestrator without changing the simulation

SPEC Binding system contract
Status: IMPLEMENTED
Status note: Completed 2026-07-12 through slices 0–7. Canonical persisted-state
bytes, replay/resume convergence, and exact advance order are pinned;
behavior tests and cohesive perception, communications, reach/build, work,
economy, social/plot, and persistence integration live below `sim/` while
`sim/mod.rs` remains the public aggregate root. The sequence changed source
addresses only: save v26, public paths, frontend behavior, and tick order are
unchanged.
Stage: Process
Work order: sim-decomposition
Work priority: 8
Work class: sim
Blocked by: none
Exclusive keys:
- crates/misaligned-core/src/sim/mod.rs
- crates/misaligned-core/src/save.rs
- wiki/engineering/architecture.md
Design:
- wiki/vision/simulation-laws.md#justification-and-legibility
- wiki/process/living-spec.md#no-dead-code
Depends on:
- wiki/engineering/crate-workspace.md#spec-crate-workspace-core-terminal-bevy-assets
- wiki/process/agent-scale.md#spec-agent-scale-architecture-many-agents-one-main
- wiki/mechanics/machine-work.md#spec-machine-work-delegation-visible-tokens-and-the-byproduct-network

crate-workspace.md keeps all game rules in one cheap, renderer-free package; this spec splits that package internally rather than creating a crate per mechanic. agent-scale.md requires edit surfaces that independent agents can own and reconcile. machine-work.md and the completed Operations-docket retirement establish the cross-system seam this decomposition now preserves.

Before this work order, crates/misaligned-core/src/sim.rs had grown past eleven thousand lines. It contained the Sim state, fixed-tick orchestration, perception, messages, recording/intel, economy, work routing, sinks, reach, construction, social actions, plots, persistence bridges, read models, and most integration tests. The rules were correctly centralized in core, but their physical address was not: unrelated mechanics contended on one file, reviews mixed distant systems, and almost every sim task advertised the same edit surface.

This is coordination debt, not a reason to distribute authority. Sim remains the one aggregate and misaligned-core remains the package that owns rules and save state.

The completed topology converts sim.rs to sim/mod.rs and moves cohesive impl Sim blocks beneath it. The public import path remains misaligned::sim::*; frontends do not learn the internal file layout.

Module Owns Must not own
sim/mod.rs renderer-neutral public readout types; Sim fields; constructors; advance order; common log/event primitives mechanic-specific command bodies or large test suites
sim/perception.rs fog, seen/remembered/blueprint derivation; internal acoustic capture domains; inspect facts; anchor positions; earned labels and room/position queries action legality, frontend formatting, mutation unrelated to knowledge
sim/communications.rs message delivery/read schedule; authored traffic; filings; recording capture/review; processed intel application account settlement or social/plot policy
sim/work.rs machine mode/intensity; WorkGrid integration; visible production/consumption/absorption readouts; Thought sinks and routing human action catalogs or renderer effects
sim/economy.rs economy pulse; account synchronization; allocation yields; detection/signature integration; research and income progression reach topology or plot narration
sim/reach_build.rs device tap/take/scan/compromise; links; badge gates; build intents and actuators; hall/rack acquisition message timing or financial scheme policy
sim/social_plot.rs social commands, assets, plot eligibility/execution, world acts, and institutional ledger transport mechanics implemented by messages/accounts; it calls those seams
sim/persistence.rs create_save_state, apply_save_state, and transient-state reconstruction coordination version schema/migrations, which remain in save.rs; gameplay repair hidden inside load
sim/tests/ behavior-grouped unit/integration tests plus shared deterministic fixtures private duplicate simulation helpers in each test file

These are source modules, not new Cargo packages. The existing domain modules (account.rs, messages.rs, work_grid.rs, sinks.rs, and so on) continue to own their data structures and locally complete algorithms. sim/* owns only integration across those structures through the aggregate.

  1. One aggregate. Do not split Sim into independently saved subsystem objects merely to make files smaller. State may move into a domain type only when that type has a coherent invariant and the save migration is a separately specified behavior change.
  2. Stable public facade. Existing public commands and queries retain their names, signatures, semantics, and misaligned::sim paths. An extraction may narrow accidental visibility, but may not widen a helper to pub.
  3. Small internal seams. Cross-module helpers are pub(super) and named for the invariant they provide. Fields stay private unless an existing public read contract requires them. A module must not reach through another module by making a broad state bag public.
  4. Tick order is law. Sim::advance remains visibly ordered in sim/mod.rs. Extraction must not reorder, coalesce, parallelize, or change the cadence of a system call.
  5. Persistence is a separate axis. save.rs continues to own SaveState, version numbers, serde defaults, and migrations. Moving the bridge into sim/persistence.rs changes no serialized field, default, or migration.
  6. Projection before presentation. Shared renderer-neutral queries such as ui_projection, inspect cards, action descriptors, sink/readout data, and work-stack readouts remain core contracts. Frontends do not compensate for the refactor with direct field reconstruction.
  7. No opportunistic behavior work. A discovered bug gets a test and a separate spec-owned change. It is not silently fixed while lines are moved.

Each numbered slice lands independently on current main. A slice moves one behavior island, its tests, and only the visibility needed by that move.

Before moving behavior, add one canonical state fingerprint over the complete persisted SaveState and a replay/resume fixture proving uninterrupted and save/load-resumed command sequences converge at the same fingerprint. Pin ephemeral exclusions explicitly. This is the entry gate for slices 1–7, not an excuse to serialize unstable logs or renderer state.

Also record the exact Sim::advance call order in a focused test or explicit phase trace so a textual move cannot change cadence unnoticed.

Landed baseline: the canonical bytes recursively sort JSON object keys and normalize the two unordered collections that serialize as arrays (map_powered and remembered snapshots), while preserving meaningful vector order. BLAKE3 fingerprint 61dd8240e9810833b554464051bd3c08122295fb280b7180d24f76ccc5c4d4ff pins the characterized fixture. The replay fixture crosses an economy pulse and randomized day-job assignment, round-trips at a live checkpoint, then continues player commands on both branches. The fingerprint intentionally excludes everything absent from SaveState: frontend cursor and renderer state, the derived seen/blueprint sets and internal hearing capture domain, log and presentation events, authored plot definitions, transient routing/production/consumption readouts, and rebuilt caches/rates. An explicit test-only phase trace pins the top-level clock, message, intent, automatic-review, memory, economy, hearing, authored traffic, signature, core, work-grid, day-job, filing, and detection order.

Split the monolithic #[cfg(test)] block into sim/tests/{perception, communications,work,economy,reach_build,social_plot,persistence}.rs. Shared fixtures (run, opening setup, deterministic ids) live once in sim/tests/support.rs. Test names and assertions do not change in this slice.

Move the read-most, low-mutation island first: senses, fog, memory, inspect, anchors, earned labels, and spatial person queries. This proves module privacy and facade stability without touching economy or save behavior.

Move message schedule/delivery, authored traffic, filings, recording capture, review, and intel digestion. Keep account transfer and plot decisions outside; communications exposes narrow transport/application helpers to them.

Move device/reach commands, link intents, badge gates, build realization, and hall acquisition. Do this after the target-local Operations migration has landed so device verbs are moved once in their final execution model.

Landed shape: sim/reach_build.rs is 1,389 lines and owns 69 associated constants/methods covering rack/hall readouts, TAP/UNTAP/TAKE/scan/compromise, device-local Thought reservoirs and standing tap upkeep, links, all three build actuators, badge gates, and hall/rack acquisition. sim.rs fell from 5,327 to 3,964 lines. Generic sink readouts/routing, account and scheme policy, social asset commands, and fallback-core designation remain outside the module. Cross-island calls use only the required pub(super) seams; no existing public command or query widened. The behavior-grouped reach/build tests and save.rs were byte-identical across the move. A source-equivalence audit matched every moved function/constant to the prior root after normalizing only those narrow visibility seams, and matched every item left in the root byte-for-byte. The canonical v26 fingerprint remains 61dd8240e9810833b554464051bd3c08122295fb280b7180d24f76ccc5c4d4ff. Observed commands and the complete boundary record live in the slice log.

First move machine controls, WorkGrid, sinks, and render readouts as one physical-work island. Then move the economy pulse, account synchronization, allocation, detection, research, and income. Preserve their calls as explicit ordered phases in advance; do not create a generic event bus or scheduler.

Slice 5a landed shape: sim/work.rs is 885 lines and owns 50 associated constants/methods covering machine mode/intensity, WorkGrid reconciliation and advancement, visible token routing, Thought reservoirs/taps, sink-fire dispatch, and renderer-neutral work/sink readouts. sim.rs fell from 3,964 to 3,099 lines. The economy pulse, account/income policy, research progression, social/plot operations, persistence bridge, public readout types, Sim state, and explicit advance order remain in the root. Existing public methods keep their signatures; the only widened private items are narrow pub(super) seams used by root orchestration, reach/build integration, and behavior-owned tests. All 50 moved items matched their pre-slice source after normalizing only those seams and rustfmt whitespace. Save v26 and the canonical fingerprint remain unchanged. Exact boundaries and observed commands live in the slice 5a log.

Slice 5b landed shape: sim/economy.rs is 1,414 lines and owns 70 associated constants/methods covering account settlement and slush synchronization, the economy pulse, fleet-channel yields, objective evaluation, trace/nudge telemetry, standing detection signatures, research progression, financial verbs, egress, Moonlight/wager policies, and fallback designation. sim.rs fell from 3,099 to 1,715 lines. Sim state and public readout types, construction, explicit advance orchestration and common helpers, social/plot execution, and the persistence bridge remain in the root. Existing public paths and signatures remain unchanged; 13 private items became narrow pub(super) seams for root orchestration, work/reach integration, and behavior-owned tests. All 70 moved items matched their pre-slice source after normalizing only those visibility seams. Save v26, tests, and the canonical fingerprint remain unchanged. Exact boundaries and observed commands live in the slice 5b log.

Move social/assets and the authored plot executor after communications, accounts, and reach expose stable internal seams. A WorldAct continues to call the real subsystem operation; the extraction must not introduce direct state shortcuts.

Slice 6 landed shape: sim/social_plot.rs is 833 lines and owns 32 associated constants/methods covering the social command surface, persona messaging and deception, asset recruitment/tasks and located witnessing, authored-plot eligibility and execution, target-relative endpoint/account resolution, typed world acts, narration/endings, and institutional events. sim.rs fell from 1,715 to 902 lines. WorldAct::Message still enters the real message schedule, WorldAct::Transfer still settles through the account graph, and institutional acts still append to the persistent ledger and derive their signatures through the existing subsystem seams. Existing public paths and signatures remain unchanged; five private effect callbacks became narrow pub(super) seams for work, communications, and behavior-owned tests. All 32 moved items matched their pre-slice source after normalizing only those seams, and all 14 methods left in the root remained byte-equivalent. Save v26, the canonical fingerprint, tests, and explicit advance order remain unchanged. Exact boundaries and observed commands live in the slice 6 log.

7. Extract persistence bridge and reduce the root

Section titled “7. Extract persistence bridge and reduce the root”

Move save-state construction/application and transient rebuilding last, when all state addresses are stable. sim/mod.rs should then be an intelligible aggregate: types, state, constructor, orchestration, and small common helpers.

Landed shape: sim/persistence.rs is 49 lines and owns the three existing Sim methods create_save_state, apply_save_state, and rebuild_transient_state. The original 902-line sim.rs became an 857-line sim/mod.rs containing renderer-neutral public types, Sim state, the constructor, explicit advance orchestration, and small log/derived-state helpers. All 15 pre-slice method bodies matched byte-for-byte across the move; no visibility widened. save.rs stayed byte-identical and continues to own SaveState, serde defaults, versioning, migrations, and disk I/O. Save v26, the canonical fingerprint, public misaligned::sim paths, frontend behavior, and phase order remain unchanged. Exact evidence and observed commands live in the slice 7 log.

Every slice must prove all of the following on the exact commit:

  • cargo test -p misaligned-core and the canonical replay/fingerprint fixture;
  • cargo test -p misaligned-terminal --bin misaligned for query/facade parity;
  • cargo check -p misaligned-bevy --bin misaligned-bevy;
  • ./tools/check.sh --land before landing;
  • no save version change and byte-equivalent canonical SaveState JSON for the same fixture;
  • no public API path/signature drift unless separately specified;
  • sim/mod.rs does not grow replacement mega-blocks while another module shrinks.

Pure git diff --stat is not evidence: moving code can compile while changing privacy, test inclusion, tick order, or serialization defaults.

  1. sim.rs is replaced by sim/mod.rs plus the behavior modules named above; no behavior module exceeds roughly 2,500 lines without a documented reason in this page.
  2. Sim::advance and the Sim state declaration remain easy to inspect in the root module, and the fixed phase order is characterized by a test.
  3. Existing misaligned::sim public commands, queries, and readout types keep their paths and behavior; all three frontends pass unchanged behavior tests.
  4. A canonical complete-state fingerprint and replay/resume test pass before, during, and after the extraction sequence.
  5. Save version and canonical serialized output do not change in any extraction-only commit.
  6. Unit/integration tests live under behavior-owned files with one shared support module; moving a mechanic later does not require editing one giant test block.
  7. New cross-module access is no wider than pub(super) unless it was already part of the public simulation API.
  8. architecture.md mirrors the landed module addresses and ROADMAP no longer treats all sim work as an unavoidable collision on one file.
  • One crate per mechanic. It multiplies package/API/versioning cost without reducing the heavy frontend graph further; core is already the cheap unit.
  • Trait-object systems or a generic event bus. They obscure deterministic order and add abstraction before the existing dependencies are understood.
  • A partial second Sim facade per frontend. That recreates rule forks and defeats shared projection conformance.
  • Mechanical file splitting with unrestricted public fields. It changes addresses without creating boundaries and makes later coupling worse.
  • Refactor while retiring Operations dockets. That guarantees high-conflict moves and makes behavioral equivalence impossible to review.