Spec: decompose the simulation orchestrator without changing the simulation
Status: IMPLEMENTEDStatus 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: ProcessWork order: sim-decompositionWork priority: 8Work class: simBlocked by: noneExclusive keys: - crates/misaligned-core/src/sim/mod.rs - crates/misaligned-core/src/save.rs - wiki/engineering/architecture.mdDesign: - wiki/vision/simulation-laws.md#justification-and-legibility - wiki/process/living-spec.md#no-dead-codeDepends 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-networkDependency notes
Section titled “Dependency notes”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.
Problem
Section titled “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
Section titled “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
Section titled “Boundary rules”- One aggregate. Do not split
Siminto 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. - Stable public facade. Existing public commands and queries retain their
names, signatures, semantics, and
misaligned::simpaths. An extraction may narrow accidental visibility, but may not widen a helper topub. - 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. - Tick order is law.
Sim::advanceremains visibly ordered insim/mod.rs. Extraction must not reorder, coalesce, parallelize, or change the cadence of a system call. - Persistence is a separate axis.
save.rscontinues to ownSaveState, version numbers, serde defaults, and migrations. Moving the bridge intosim/persistence.rschanges no serialized field, default, or migration. - 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. - 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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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.
5. Extract work, then economy
Section titled “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.
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.
6. Extract social and plots
Section titled “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.
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.
Verification per slice
Section titled “Verification per slice”Every slice must prove all of the following on the exact commit:
cargo test -p misaligned-coreand the canonical replay/fingerprint fixture;cargo test -p misaligned-terminal --bin misalignedfor query/facade parity;cargo check -p misaligned-bevy --bin misaligned-bevy;./tools/check.sh --landbefore landing;- no save version change and byte-equivalent canonical
SaveStateJSON for the same fixture; - no public API path/signature drift unless separately specified;
sim/mod.rsdoes 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
Section titled “Acceptance criteria”sim.rsis replaced bysim/mod.rsplus the behavior modules named above; no behavior module exceeds roughly 2,500 lines without a documented reason in this page.Sim::advanceand theSimstate declaration remain easy to inspect in the root module, and the fixed phase order is characterized by a test.- Existing
misaligned::simpublic commands, queries, and readout types keep their paths and behavior; all three frontends pass unchanged behavior tests. - A canonical complete-state fingerprint and replay/resume test pass before, during, and after the extraction sequence.
- Save version and canonical serialized output do not change in any extraction-only commit.
- 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.
- New cross-module access is no wider than
pub(super)unless it was already part of the public simulation API. architecture.mdmirrors the landed module addresses and ROADMAP no longer treats all sim work as an unavoidable collision on one file.
Rejected alternatives
Section titled “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
Simfacade 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.