A video game where you play as a misaligned AI, deceiving and building power. An experiment in spec-driven development.
misaligned wiki engineering architecture.md
6.1 kB

Architecture #

Type: knowledge

As-built: a Cargo workspace with four packages. Game rules live only in misaligned-core; frontends are thin packages that depend on it.

Why this shape exists (binding): crate-workspace.md — package jobs, dependency edges, naming, rejected alternatives, and standing criteria. This knowledge page is the path/layout mirror; it does not replace that page's reasons. Multi-agent gates: agent-scale.md.

Workspace layout #

Cargo.toml                      — workspace root (members, shared deps, profiles)
crates/
  misaligned-core/              — sim library (lib name: misaligned); no Bevy/crossterm
    src/sim/mod.rs              — Sim aggregate root (types, state, advance, facade)
    src/sim/perception.rs       — senses, fog, inspect, anchors, labels, spatial queries
    src/sim/communications.rs   — messages, filings, recording/intel, hearing capture
    src/sim/reach_build.rs      — reach/device verbs, links, builds, badges, hall/racks
    src/sim/work.rs             — machine controls, WorkGrid, Thought sinks and readouts
    src/sim/economy.rs          — accounts, allocation, detection, research, income
    src/sim/social_plot.rs      — social/assets, plots, world acts, institutional ledger
    src/sim/persistence.rs      — Sim/SaveState bridge and transient reconstruction
    src/sim/tests/              — behavior-grouped unit/integration tests + support
    src/*.rs                    — map, save, domain systems (account, reach, …)
    tests/act_one.rs            — Act One integration test
  misaligned-terminal/          — binary `misaligned` (crossterm + agent protocol)
  misaligned-bevy/              — binary `misaligned-bevy` (Bevy 0.18 frontend)
  misaligned-assets/            — shared art lib + `misaligned-assets` mesh tester
                                  + `misaligned-effects` liquid/dust lab

Public imports stay misaligned::sim::*; the sim/ file split is internal.

Package contracts #

Package May contain Must not contain
misaligned-core Sim, map, systems, save format, rule tests Bevy, crossterm, wall-clock-driven game rules
misaligned-terminal UI, input, agent line protocol, wall-clock tick mapping Game-rule forks of Sim
misaligned-bevy 3D/2.5D view, shot harness, wall-clock tick mapping Game-rule forks of Sim
misaligned-assets Shared mesh/effect render code and focused art harnesses Save/load of full runs; sim progression

Frontends import the lib as misaligned::… (crate name misaligned-core, lib name misaligned). That split is deliberate: package name states the job (core); lib name states the product so imports stay stable — see crate-workspace naming.

misaligned-bevy depends on misaligned-assets for shared rack/mesh/effect rendering so the game binary and focused art harnesses do not fork visual code; assets still does not depend on core (no sim progression in either tester).

The sim/frontend contract #

These invariants are load-bearing (they keep replays, debugging, and the future async-multiplayer option open — see wiki/gameplay/horizon.md guardrails):

  • Game rules live only in core. If a frontend needs an if about game behavior, that logic belongs in Sim. The Bevy frontend once forked the rules and diverged badly; that cost a full rewrite (see wiki/log/2026-07-05-bevy-sim-port.md).
  • Sim never reads the wall clock and does no I/O (save path helpers that use dirs for the JSON save location are infrastructure on the save format, not sim progression). Frontends own the wall-clock-to-tick mapping (tick_ms, pause, bounded catch-up of max 5 ticks per frame). Instant::now() appears only in frontend code.
  • Frontends talk to the sim through command/query methods (ui_projection, inspect, core_position, salvage_nearest_to, buy_rack_at, add_fallback_at, create_save_state/apply_save_state, and each system's commands as its spec lands) and read state directly for rendering. The cursor is frontend state only; there is no move_player or sim-owned avatar. Messages flow back via drain_log() — call it once per frame/turn, not per command batch.
  • After build actions (place/demolish, done by frontends via BuildMode mutating sim.map/sim.player directly), call sim.recompute_derived(). This is the one place frontends mutate sim internals; a future cleanup could move BuildMode application into a sim command.

Save format #

crates/misaligned-core/src/save.rs, serde JSON. SaveState derives Serialize/Deserialize directly. Written to dirs::data_dir()/misaligned/misaligned_save.txt (macOS: ~/Library/Application Support/misaligned/).

  • The save is a single JSON object with a version field for future migration. Full B1 subsystem state: compute, core, detection, day job, people, reach, intel, accounts, research, objective, building intents, badge, WorkGrid, remembered fog snapshots, map and RNG. The cursor position is deliberately absent.
  • Version migrations are additive and explicit in save.rs. Ownership of the format stays in core — frontends must not implement migrations. The field bridge and transient reconstruction live in sim/persistence.rs; save.rs retains the schema, serde defaults, versions, migrations, and I/O.

Build commands (workspace) #

cargo test -p misaligned-core
cargo run -p misaligned-terminal --release          # or: cargo run --release
cargo run -p misaligned-bevy --release
cargo run -p misaligned-assets
./tools/check.sh --lib       # core + terminal + cheap bevy check
./tools/check.sh --frontend  # bevy + assets
./tools/check.sh --full      # workspace

Known architectural debts #

  • BuildMode lives in core but is really frontend-shared UI state.
  • Scale-debt items (compute grouping, recursive layouts) remain governed by wiki/vision/scale.md; no aggregate machinery until the stage needs it.