# Spec: decompose the simulation orchestrator without changing the simulation ``` Type: spec 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 ``` ## Dependency notes [crate-workspace.md](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](../process/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. ## Problem 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. ## Standing topology 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. ## Boundary rules 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. ## Extraction sequence Each numbered slice lands independently on current main. A slice moves one behavior island, its tests, and only the visibility needed by that move. ### 0. Characterize the aggregate 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. ### 1. Move tests out first 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. ### 2. Extract perception 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. ### 3. Extract communications 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. ### 4. Extract reach and construction 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](../log/2026-07-11-sim-decomposition-reach-build.md). ### 5. Extract work, then economy 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](../log/2026-07-11-sim-decomposition-work.md). 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](../log/2026-07-11-sim-decomposition-economy.md). ### 6. Extract social and plots 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](../log/2026-07-12-sim-decomposition-social-plots.md). ### 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](../log/2026-07-12-sim-decomposition-persistence.md). ## Verification per slice 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. ## Acceptance criteria 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. ## Rejected alternatives - **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.