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

96 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CONTRACTS — how the lanes fit together (ORCHESTRATOR-OWNED)
The binding type definitions live in [`src/contracts.ts`](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.