glytch/fktry/CONTRACTS.md
type-two 0f0bfb039b [orchestrator] round 7 review: ratify the airborne ruling, expose the dev sim handle
CONTRACTS v9 notes: 'sealed' jam reason documented; SIM's firewalls-catch-only-
what-walks departure RATIFIED (UI measured the alternative as rung 3 not
existing); unitCleared queued as round 8's first contract item.

main.ts DEV block now exposes window.__fktrySim so testkit verbs can drive an
in-browser review the way lane tests drive headless ones - used today to verify
the full Phase 1 loop live: band 4, three simultaneous notices, gunship tether +
real mirrored theft (sky placard + ledger + ticker + toast), auditor SECTOR LOCK
with hash countdown, THE SCREEN held at HOLD with zero feed-time cost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 13:21:58 +10:00

4.8 KiB
Raw Blame History

CONTRACTS — how the lanes fit together (ORCHESTRATOR-OWNED)

The binding type definitions live in src/contracts.ts — that file is the law. This doc explains the shape of the machine in prose.

Data flow

data/*.json ──▶ main.ts loads GameData
                    │
        ┌───────────┼─────────────────────────┐
        ▼           ▼                         ▼
   sim.init()  renderer.init()   ui.init() / screen.init()
        │
   30 tps fixed timestep:  sim.tick()
        │
        ├─▶ sim.snapshot()  ──▶ renderer.render(snap, alpha)   (every rAF)
        │                   ──▶ ui.update(snap, events)
        └─▶ sim.drainEvents() ─▶ screen.onEvents(events)

   user input:  UI builds Commands ─▶ bus.dispatch ─▶ sim.enqueue
                renderer supplies pickTile + ghost preview

Ownership map

Path Owner Everyone else
src/contracts.ts, src/main.ts, root config, this doc, MASTERPLAN.md orchestrator read-only
src/sim/** LANE-SIM read-only
src/render/**, public/assets/** (consumption) LANE-RENDER read-only
src/ui/** LANE-UI read-only
src/screen/** LANE-SCREEN read-only
data/** LANE-DATA read-only
lanes/LANE-X.md NOTES section lane X orchestrator writes ORDERS section
../docs/FKTRY_LORE.md canon read-only for all lanes

Key invariants

  • Snapshot is read-only. Renderer/UI/Screen never mutate it. Sim may reuse internal buffers between snapshots; consumers must not hold references across frames.
  • All mutation goes through Commands. No lane reaches into sim internals.
  • Events are drained once per frame by main.ts and fanned out; consumers get the same array — treat it as immutable.
  • IDs are data-driven. Renderer/UI/Screen never hard-code item/machine ids; they key off GameData and the asset field. New content should light up with zero code.
  • alpha in renderer.render(snap, alpha) is the 0..1 fraction between the last sim tick and the next — use it to interpolate belt item positions for smooth motion.
  • Belt visual speed derives from MachineDef.beltSpeed and BeltItem.t; sim owns the truth, renderer owns the smoothness.
  • Coordinates: tile grid, origin at world center, +x east, +y south. Dir 0..3 = N,E,S,W clockwise. Machine pos = origin corner (min x, min y of footprint).

Contract-change requests

Written in your lane NOTES as CONTRACT REQUEST: <what> — <why>. The orchestrator batches them between rounds. Until granted, work around locally inside your lane.

v4 notes

  • snapshot.commissionQueue may alias a live sim array — read-only, never retain across frames (same rule as the snapshot itself, stated here because it looks like a plain array).
  • Selection protocol: UI writes bus.setSelection(...); main.ts derives ghost mode and dispatches place/remove on click. UI must NOT dispatch remove itself. The '__remove' def string is a deprecated bridge, deleted in v5.
  • Heat: heatPerTick is GROSS; net climb = heatPerTick coolPerTick. A machine whose heatPerTick ≤ its coolPerTick can never overheat — the validator enforces it.
  • Research gating: ids appearing in any TechDef.unlocks are locked until researched; ids referenced by no tech are always available.

v6 notes

  • THE SPEAKER: AudioFX (src/audio/**, LANE-SCREEN) — all sound is Web Audio synthesis, no asset files. main.ts wires init/unlock/onEvents/frame. Pre-discovery only the diegetic minimum plays; the band unlocks on relicFound.
  • Sanctioned narrow imports (read-only, documented at the export site): src/sim/constants.ts (tuning truth) and src/audio's radio control (UI's tuner dock). Everything else stays contract-mediated.
  • Relics are secrets. snapshot.relics is always present, but nothing may signpost an unfound relic: RENDER draws the subtle shimmer, UI shows nothing until found. Discoverability budget: wandering + curiosity. That's the design.
  • New jam reason string: 'no seam' (extractor off-seam).

v9 notes

  • Jam reason 'sealed' joins 'no seam' in the sim's vocabulary: a warden-sealed machine stalls with it until the pry command clears the seal. UI translates it as a document, not a fault.
  • RULING (round 7, ratifying SIM's departure): firewalls catch only what WALKS. Gunships are airborne and immune — UI measured rung 3 being deleted entirely otherwise. Pinned by SIM's regression test (a gunship must reach tether and emit mirrored in the shipped demo).
  • Round-8 backlog, first item: a unitCleared event — the firewall's victory currently fires nine times in the demo and is silent to SCREEN/audio/RENDER.