{
  "$comment": "The landing page as data. Generated from .lattice/kit.json and site/data/*.json at build time; do not edit. The prose equivalent is /llms.txt.",
  "generated": "2026-08-19",
  "commit": "cbd48bd",
  "name": "lattice",
  "tagline": "The grid underneath.",
  "version": "0.1.1",
  "license": "MIT",
  "repository": "https://github.com/C-Aniruddh/lattice",
  "install": {
    "plugin": {
      "Claude Code": [
        "/plugin marketplace add C-Aniruddh/lattice",
        "/plugin install lattice@lattice"
      ],
      "Codex": [
        "codex plugin marketplace add C-Aniruddh/lattice",
        "codex plugin add lattice@lattice"
      ],
      "Grok": [
        "grok plugin install C-Aniruddh/lattice"
      ]
    },
    "libraries": "npm i @latticekit/core @latticekit/iso @latticekit/draw @latticekit/loop @latticekit/input",
    "published": true,
    "publishedVersion": "0.1.1",
    "note": "All nine packages are on the public npm registry at 0.1.1. Both commands work as written."
  },
  "measured": {
    "packages": {
      "value": 9,
      "unit": "",
      "source": ".lattice/kit.json — packages"
    },
    "dependencies": {
      "value": 0,
      "unit": "",
      "source": "every package.json has no `dependencies` outside @latticekit; core has none at all"
    },
    "assetFiles": {
      "value": 0,
      "unit": "",
      "source": "find packages -type f -not -path '*/node_modules/*' -not -path '*/dist/*' -not -path '*/coverage/*' — 207 .ts, 19 .json, 9 .md, nothing else"
    },
    "tests": {
      "value": 2648,
      "unit": "",
      "source": "npx vitest run — 2648 passed (2648)"
    },
    "testFiles": {
      "value": 100,
      "unit": "",
      "source": "npx vitest run — 100 passed (100)"
    },
    "publicSymbols": {
      "value": 527,
      "unit": "",
      "source": "unique names across .lattice/kit.json packages.*.exports (raw sum 544; VERSION appears in eight)"
    },
    "gzipTotal": {
      "value": 83.02,
      "unit": "kB",
      "source": "npm run size — total 83.02 kB, exclusive backends charged at the heaviest, never summed"
    },
    "frameBudget": {
      "value": 8,
      "unit": "ms",
      "source": ".lattice/kit.json — budgets.maxFrameBudgetMs"
    },
    "spriteDraw": {
      "value": "2.14 ms",
      "unit": "",
      "source": "docs/PERFORMANCE.md:318 — 400 sprites x 42 ops, dpr 3: 2.14 ms mean, 2.24 ms p99, 27% of the budget"
    },
    "coverageFloor": {
      "value": 90,
      "unit": "%",
      "source": ".lattice/kit.json — budgets.coverageStatements 0.9, coverageCore 1.0"
    },
    "exhibits": {
      "value": 18,
      "unit": "",
      "source": "site/data/exhibits.json — live[], one row per exhibit, plus the hero at hero{}, which is nineteen worlds in all. Each is a directory under examples/, built into /x/<name>/ by site/tools/build.mjs and served as a page of its own; open any of them and it is running, not a picture."
    },
    "exampleLines": {
      "value": 10,
      "unit": "",
      "source": "grep -cvE '^[[:space:]]*($|//|/*|*)' site/example/hello.ts — docs/GALLERY.md's own line rule, applied to the whole program printed in Getting started and running beside it. site/tools/build-page.mjs re-counts the file at build time and fails the build if this number has drifted from it."
    },
    "skills": {
      "value": 12,
      "unit": "",
      "source": "find skills -name SKILL.md | wc -l — twelve directories under skills/, each carrying one SKILL.md: the parent that owns /lattice, plus eleven specialists it loads (starting, art, world, economy, input, sound, saving, hud, determinism, performance, traps). The parent is counted because it is a skill and not a wrapper — it is the one that fires on a from-scratch build."
    },
    "traps": {
      "value": 34,
      "unit": "",
      "source": "grep -c '^### ' skills/traps/SKILL.md — one heading per named failure that compiles, runs, and produces a plausible-looking broken game. Each carries the wrong version as well as the right one, because recognizing the wrong one is the job."
    },
    "pageFrame": {
      "value": "live",
      "unit": "",
      "source": "read off this page's own @latticekit/loop while you look at it: worstGapMs, the longest wall gap between two painted frames in the last ten seconds, with every world above it running on the same main thread. The only figure in this strip that was not measured on one machine on one day — it is measuring yours. It reads 'warming' for the first three seconds of a loop and an em dash in a background tab, because 0.0 ms means requestAnimationFrame stopped rather than that anything got faster."
    }
  },
  "sizes": [
    {
      "package": "core",
      "gzipKb": 7.4,
      "budgetKb": 12
    },
    {
      "package": "iso",
      "gzipKb": 11.47,
      "budgetKb": 12
    },
    {
      "package": "draw",
      "gzipKb": 12.33,
      "budgetKb": 12.5,
      "note": "exclusive backends, charged at the heaviest: canvas2d 12.33 charged, record 11.31, shared 10.19"
    },
    {
      "package": "loop",
      "gzipKb": 6.57,
      "budgetKb": 12
    },
    {
      "package": "input",
      "gzipKb": 15.39,
      "budgetKb": 16,
      "note": "override 16 kB, argued in .lattice/kit.json — budgets.overrides.input"
    },
    {
      "package": "audio",
      "gzipKb": 7.76,
      "budgetKb": 12
    },
    {
      "package": "persist",
      "gzipKb": 5.79,
      "budgetKb": 12
    },
    {
      "package": "sim",
      "gzipKb": 7.58,
      "budgetKb": 12
    },
    {
      "package": "ui",
      "gzipKb": 8.72,
      "budgetKb": 12
    }
  ],
  "budgets": {
    "coverageStatements": 0.9,
    "coverageCore": 1,
    "maxGzipKbPerPackage": 12,
    "maxFrameBudgetMs": 8,
    "overrides": {
      "input": {
        "maxGzipKb": 16,
        "reason": "15.39 kB against a 15 kB override, and this raise was pre-refused by the text it replaces — \"the revisit does not start by proposing 16\". It did not start there. Both alternatives that text named were measured first, and both were refused on merit rather than on effort.\n\nThe covered-by-overlay message saves 0.248 kB, priced accurately, and is the one worth selling: declaredChrome, the cached rect and the capture listener all survive it, so a caller still gets the code and the element that ate their press, and README.md § When a HUD covers the world teaches it at greater length. But 0.248 < 0.39. Selling it buys a still-red gate and a worse diagnostic, which is a loss twice.\n\nThe known-code table saves 0.650 kB — it has grown since it was priced at 0.43 — and is not sellable at any price. Deleting it does not make a message worse; it deletes the only message there is. key:'key:space' would then compile, bind a code no keyboard reports, and never fire. Worse, CompiledActions.bindings exists so a shortcut sheet renders FROM the map, so the dead binding gets printed to the player as a working shortcut. That is a silent wrong answer reaching the screen — the same class K44's terrain seam just closed, so trading one for the other is not a saving. Four tests pin those exact messages.\n\nThe split was measured too and does not solve it: headless half 12.895 kB, browser adapter 2.832 kB. It converts a 0.39 kB overrun on one package into a 0.90 kB overrun on the half a headless replay does pay. It fixes the framing and not the number, and size.mjs's backends mechanism cannot help — that needs two mutually exclusive backends and input has one adapter over a shared core.\n\nSo the number moves, for the reason the first override said would justify a different conversation: something new landed rather than the surface drifting. What landed is the terrain seam, which stopped every ActionEvent on sloped ground being silently wrong by 281 px and fourteen tiles. 0.39 kB is what that correctness cost.\n\nRevisit at 16, and start where this revisit ended rather than where it began: the two cuts above are priced and one of them is genuinely available. If 0.248 kB is ever enough on its own, take it. If it is not, the answer is the split plus its own budget, not 17 — and whoever funds the split should know it starts 0.90 kB over the default rather than under it."
      },
      "draw": {
        "maxGzipKb": 12.5,
        "reason": "12.08 kB against a 12 kB default, measured as the heaviest single bundle rather than the sum of two that never ship together. The previous 13.5 was granted against a fictional 12.93 — 1.0 kB of that was measurement error and is gone now that size.mjs charges exclusive backends at the maximum. What remains is a real 0.08 kB overrun plus headroom, and this is the number kit.json already predicted for this bundle. Retiring it outright needs 0.08 kB out of the browser bundle, which is a cheap and separate conversation."
      }
    }
  },
  "layers": [
    {
      "layer": 0,
      "packages": [
        "core"
      ]
    },
    {
      "layer": 1,
      "packages": [
        "iso",
        "loop",
        "sim",
        "persist",
        "audio"
      ]
    },
    {
      "layer": 2,
      "packages": [
        "draw",
        "input"
      ]
    },
    {
      "layer": 3,
      "packages": [
        "ui"
      ]
    }
  ],
  "packages": {
    "core": {
      "name": "@latticekit/core",
      "purpose": "Deterministic primitives. Seeded randomness, noise, math, easing, typed events, pools, formatting.",
      "environment": "isomorphic",
      "dependsOn": [],
      "modules": [
        "rng",
        "hash",
        "noise",
        "math",
        "easing",
        "vec2",
        "events",
        "pool",
        "format",
        "guard",
        "time",
        "dispose"
      ],
      "exports": [
        "COMPACT_SUFFIXES",
        "Disposer",
        "DurationStyle",
        "EASINGS",
        "EPSILON",
        "Easing",
        "EasingName",
        "Emitter",
        "EpochMillis",
        "MonotonicMillis",
        "MonotonicNow",
        "Now",
        "Pool",
        "PoolOptions",
        "ReadonlyVec2",
        "Rng",
        "RngSnapshot",
        "Scope",
        "TAU",
        "Vec2",
        "approx",
        "asEpochMillis",
        "asMonotonicMillis",
        "backIn",
        "backOut",
        "bounceOut",
        "clamp",
        "clamp01",
        "createRng",
        "createScope",
        "cubicIn",
        "cubicInOut",
        "cubicOut",
        "damp",
        "expectFinite",
        "expectIndex",
        "expectInt",
        "expectNonEmpty",
        "expectObject",
        "expectRange",
        "expectRecordOfFinite",
        "expectSafeInteger",
        "expectSerializable",
        "fbm2",
        "fbm3",
        "fmtCompact",
        "fmtDuration",
        "fmtInteger",
        "fmtPercent",
        "fmtRate",
        "fmtSigned",
        "hash2",
        "hash3",
        "hashBytes",
        "hashNumber",
        "hashParts",
        "hashStep",
        "hashString",
        "inOut",
        "inverseLerp",
        "isSerializable",
        "lerp",
        "linear",
        "mix32",
        "mod",
        "moveTowards",
        "noise2",
        "noise3",
        "quadIn",
        "quadInOut",
        "quadOut",
        "quartOut",
        "remap",
        "reverse",
        "smooth",
        "smoother",
        "smoothstep",
        "toUnit",
        "unreachable",
        "v2",
        "v2Add",
        "v2AddScaled",
        "v2Angle",
        "v2Approx",
        "v2Copy",
        "v2Cross",
        "v2Dist",
        "v2DistSq",
        "v2Dot",
        "v2FromAngle",
        "v2Len",
        "v2LenSq",
        "v2Lerp",
        "v2Normalize",
        "v2Perp",
        "v2Rotate",
        "v2Scale",
        "v2Set",
        "v2Sub",
        "wrap"
      ],
      "invariants": [
        "Zero dependencies and zero DOM references.",
        "Two tiers of determinism. Tier A uses only arithmetic ECMA-262 specifies exactly (+ - * /, Math.sqrt, Math.imul, bitwise) and is bit-identical everywhere. Tier B uses sin/cos/pow/exp/log, which the spec does not require to be correctly rounded, and is presentation-only: never hashed, never persisted, never replayed. Every Tier B site declares itself with `@tier-b`.",
        "No module-level mutable state. There is no global Rng, deliberately, and no id counter.",
        "Sub-streams fork from a stream's identity, not its cursor, so a draw made out of order elsewhere cannot reshuffle this one.",
        "Validators return their argument rather than taking a boolean — a boolean has already discarded the value that was wrong, so it cannot name it in the error."
      ],
      "entryPoints": [
        "createRng",
        "hash2",
        "v2",
        "createScope"
      ],
      "$entryPointsNote": "The three or four symbols a reader calls FIRST. `exports` answers 'which package'; a flat list of 484 names cannot answer 'which symbol'. Every package carries this; an empty list means the package is not yet built.",
      "layer": 0,
      "gzipKb": 7.4,
      "budgetKb": 12,
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/packages/core",
      "readme": "https://github.com/C-Aniruddh/lattice/blob/main/packages/core/README.md",
      "reference": "/reference/core/"
    },
    "audio": {
      "name": "@latticekit/audio",
      "purpose": "Sound without assets: WebAudio synthesis from declarative sound definitions, with voice limiting, buses and a music sequencer.",
      "environment": "browser",
      "dependsOn": [
        "core"
      ],
      "modules": [
        "engine",
        "voice",
        "sounds",
        "bus",
        "bed",
        "music"
      ],
      "exports": [
        "ATTACK_SEC",
        "Audio",
        "AudioOptions",
        "BUS_NAMES",
        "Bed",
        "BedLayer",
        "BedOptions",
        "BusId",
        "BusName",
        "LOOKAHEAD_SEC",
        "Layer",
        "MAX_VOICES",
        "Mixer",
        "MixerState",
        "MusicDeck",
        "Note",
        "PUMP_INTERVAL_MS",
        "PlayOptions",
        "RAMP_SEC",
        "SEMITONE",
        "Song",
        "SongProblem",
        "SoundDef",
        "SoundProblem",
        "Track",
        "TrackVoice",
        "VERSION",
        "VoicePlan",
        "Wave",
        "createAudio",
        "createBed",
        "createDeck",
        "effectiveGain",
        "validateSong",
        "validateSounds"
      ],
      "invariants": [
        "No AudioContext exists until a user gesture unlocks it.",
        "Silent, not throwing, where there is no WebAudio. play() in a headless run produces no sound and still reports acceptance — the policy above is pure and testable, the rendering below is not.",
        "A hard voice ceiling. Summed gains above 1 clip into a click, and twenty voices never sound twenty times better.",
        "A layer is one fixed chain of ten numbers, not author-defined routing. The moment routing is author-defined the clipping ceiling can no longer be validated statically.",
        "This package stores nothing. The mixer returns a versioned snapshot and the game hands it to persist — there is no edge between two layer-1 packages."
      ],
      "entryPoints": [
        "createAudio",
        "createBed",
        "createDeck"
      ],
      "layer": 1,
      "gzipKb": 7.76,
      "budgetKb": 12,
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/packages/audio",
      "readme": "https://github.com/C-Aniruddh/lattice/blob/main/packages/audio/README.md",
      "reference": "/reference/audio/"
    },
    "iso": {
      "name": "@latticekit/iso",
      "purpose": "Isometric space: the projection, the camera, depth sorting, tile maps, footprints, hit-testing, and grid pathfinding.",
      "environment": "isomorphic",
      "dependsOn": [
        "core"
      ],
      "modules": [
        "projection",
        "footprint",
        "camera",
        "depth",
        "tilemap",
        "height",
        "hittest",
        "anchor",
        "path"
      ],
      "exports": [
        "Anchor",
        "Camera",
        "CameraOptions",
        "DIR_DX",
        "DIR_DY",
        "DepthSorter",
        "FlowField",
        "Footprint",
        "GridPoint",
        "HALF_H",
        "HALF_W",
        "HeightField",
        "MutableTileSource",
        "Path",
        "PathFinder",
        "PathOptions",
        "Rect",
        "STEP_DIAG",
        "STEP_ORTHO",
        "TILE_H",
        "TILE_W",
        "Tile",
        "TileCost",
        "TileGrid",
        "TileGridOptions",
        "TileRange",
        "TileSource",
        "VERSION",
        "Volume",
        "anchorPan",
        "anchorToScreen",
        "anchorVisible",
        "boxSilhouette",
        "createCamera",
        "depthOf",
        "footprintAnchor",
        "footprintBase",
        "footprintBounds",
        "footprintContains",
        "footprintFlatness",
        "footprintOverlaps",
        "forEachFootprintTile",
        "gridToScreen",
        "gridToWorld",
        "gridToWorldX",
        "gridToWorldY",
        "heightAt",
        "isEdgeOn",
        "pathDirAt",
        "pathProject",
        "pathSample",
        "pathSimplify",
        "pickSorted",
        "pointInPolygon",
        "pointInTile",
        "pxToUnits",
        "rectCenterX",
        "rectCenterY",
        "rectContains",
        "rectExpand",
        "rectFromSize",
        "rectHeight",
        "rectIntersects",
        "rectIsEmpty",
        "rectMakeEmpty",
        "rectSet",
        "rectUnion",
        "rectWidth",
        "screenToTile",
        "screenToTileOnHeights",
        "slopeAt",
        "tileBounds",
        "tileDiamond",
        "tileSourceOf",
        "unitsToPx",
        "worldToGrid",
        "worldToGridX",
        "worldToGridY",
        "worldToTile",
        "worldToTileOnHeights"
      ],
      "invariants": [
        "Three coordinate spaces — grid, world, screen — never conflated. Every conversion is a named function taking an output parameter.",
        "Tile lookup floors, never rounds. Rounding snaps to the nearest lattice vertex and picks the wrong tile for three quarters of every diamond.",
        "Hit-testing is computed from state and camera, never cached during a draw pass. pick() is a method on the sorted Scene, so 'reverse of paint order' is structural rather than remembered.",
        "Tile size is fixed at 64x32. Any other uniform size is exactly a camera zoom, and parameterizing it would infect draw, input and ui signatures permanently.",
        "Elevation lives on grid vertices, not tile centres. Once z exists the projection is no longer invertible, so picking must be terrain-aware.",
        "A path is a curve to be sampled by arc length, not a list of nodes to be stepped. Grid-unit parameterization would make a walker 58% faster on one diagonal than the other."
      ],
      "entryPoints": [
        "createCamera",
        "DepthSorter",
        "screenToTile",
        "pathSample"
      ],
      "layer": 1,
      "gzipKb": 11.47,
      "budgetKb": 12,
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/packages/iso",
      "readme": "https://github.com/C-Aniruddh/lattice/blob/main/packages/iso/README.md",
      "reference": "/reference/iso/"
    },
    "loop": {
      "name": "@latticekit/loop",
      "purpose": "Time. A wall-clock game loop with fixed-step simulation and interpolated rendering, plus scheduling, tweens and frame statistics.",
      "environment": "isomorphic (host clock is injected)",
      "dependsOn": [
        "core"
      ],
      "modules": [
        "clock",
        "frames",
        "loop",
        "scheduler",
        "tween",
        "stats",
        "replay"
      ],
      "exports": [
        "BrowserFramesOptions",
        "Clock",
        "DEFAULT_ABSENCE_MS",
        "DEFAULT_BUDGET_MS",
        "DEFAULT_HZ",
        "DEFAULT_IDLE_PUMP_MS",
        "DEFAULT_MAX_CATCH_UP_MS",
        "DEFAULT_WARMUP_FRAMES",
        "DEFAULT_WINDOW_MS",
        "Disposer",
        "FrameHost",
        "FrameSource",
        "FrameStats",
        "Job",
        "Loop",
        "LoopOptions",
        "LoopPhase",
        "ManualClock",
        "ManualFrames",
        "Pump",
        "PumpKind",
        "ReplayOptions",
        "ReplayResult",
        "ReplaySource",
        "Scheduler",
        "Timeline",
        "TimerId",
        "TweenId",
        "TweenOptions",
        "Tweens",
        "VERSION",
        "browserFrames",
        "createLoop",
        "createTimeline",
        "createTweens",
        "manualClock",
        "manualFrames",
        "replay"
      ],
      "invariants": [
        "Simulation advances on the wall clock, never on frame deltas — rAF is 0 Hz in a hidden tab.",
        "Catch-up is clamped at 250ms per pump and the excess is dropped, not deferred. The loop advances callbacks; sim advances value.",
        "loop.time deliberately drifts below real time while hidden. Anything that must be truthful about the player's wall clock is a timestamp in state, never a duration accumulated on the fixed step.",
        "The clock and the frame source are both injected, so every test runs at whatever speed it likes with no timers.",
        "This package has no epoch and stamps nothing. The calendar is one game-owned function, injected."
      ],
      "entryPoints": [
        "createLoop",
        "browserFrames",
        "createTweens",
        "replay"
      ],
      "layer": 1,
      "gzipKb": 6.57,
      "budgetKb": 12,
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/packages/loop",
      "readme": "https://github.com/C-Aniruddh/lattice/blob/main/packages/loop/README.md",
      "reference": "/reference/loop/"
    },
    "persist": {
      "name": "@latticekit/persist",
      "purpose": "Saves that survive: versioned state, an explicit migration chain, pluggable storage, debounced writes and integrity checks.",
      "environment": "isomorphic (storage adapter is injected)",
      "dependsOn": [
        "core"
      ],
      "modules": [
        "store",
        "migrate",
        "adapters",
        "integrity",
        "replay",
        "browser"
      ],
      "exports": [
        "Autosave",
        "AutosaveOptions",
        "Cancel",
        "ChainBuilder",
        "Checkpoint",
        "Checksum",
        "Digest",
        "Divergence",
        "Envelope",
        "FailureReason",
        "FlushTargets",
        "Increment",
        "ListenerTarget",
        "MigrationChain",
        "MigrationStep",
        "OpenResult",
        "ReadFailure",
        "Recognize",
        "Recorder",
        "RecorderOptions",
        "Refusal",
        "Rejected",
        "ReplayCompat",
        "ReplayLog",
        "ReplayVerdict",
        "ReplayVerifier",
        "Schedule",
        "SecondsTimeline",
        "StorageAdapter",
        "StorageLike",
        "Store",
        "StoreOptions",
        "StoreStatus",
        "VERSION",
        "WriteFailure",
        "WriteResult",
        "WriteSkip",
        "browserStorage",
        "createRecorder",
        "createStore",
        "createVerifier",
        "defaultChecksum",
        "elapsedSince",
        "inspect",
        "installFlushTriggers",
        "memoryStorage",
        "migrations",
        "scheduleFrom",
        "webStorage"
      ],
      "invariants": [
        "The chain IS the version. createStore reads the head off the migration chain, so declaring version 7 and shipping a chain that ends at 6 is inexpressible.",
        "Every migration steps exactly one rung, and every rung carries a recognizer. A save can never be orphaned.",
        "Writes flush on visibilitychange, not beforeunload — mobile Safari does not reliably deliver the latter. reset() closes handles BEFORE removing the key, or the autosave writes the live state back over the clear.",
        "A corrupt save degrades to a fresh one with one of seven closed reasons, returned as a value. Never a thrown exception on boot.",
        "A save from the future makes the store read-only. A stale deploy must not eat a good save.",
        "A replay log is evidence, not progress: it is never migrated. A version, stepMs or profile mismatch is refused by name, because a migrated recording would produce a confident wrong answer."
      ],
      "entryPoints": [
        "createStore",
        "migrations",
        "createRecorder"
      ],
      "layer": 1,
      "gzipKb": 5.79,
      "budgetKb": 12,
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/packages/persist",
      "readme": "https://github.com/C-Aniruddh/lattice/blob/main/packages/persist/README.md",
      "reference": "/reference/persist/"
    },
    "sim": {
      "name": "@latticekit/sim",
      "purpose": "Idle-economy mathematics in closed form: cost curves, the flow integrator, offline accrual, and capacity gating.",
      "environment": "isomorphic",
      "dependsOn": [
        "core"
      ],
      "modules": [
        "graph",
        "flow",
        "ledger",
        "offline",
        "schedule",
        "crossing",
        "capacity",
        "cost",
        "ids"
      ],
      "exports": [
        "CapacityCurve",
        "CatchUp",
        "CostCurve",
        "Crossing",
        "Economy",
        "EconomySpec",
        "Edge",
        "EdgeScale",
        "EdgeSpec",
        "EntityId",
        "Flow",
        "GateRatios",
        "IdSource",
        "Ledger",
        "Milestones",
        "NO_GATES",
        "OfflineCurve",
        "Phase",
        "StockVec",
        "Stocks",
        "VERSION",
        "advance",
        "advanceOver",
        "asEntityId",
        "buildFlow",
        "bulkCost",
        "capacityLoad",
        "capacityShare",
        "capacityWall",
        "costOfNext",
        "createFlow",
        "createIdSource",
        "defineEconomy",
        "degreeOf",
        "elapsedSeconds",
        "expectFiniteStocks",
        "integrate",
        "maxBuyable",
        "maxOfflineCredit",
        "milestoneMultiplier",
        "mintId",
        "offlineCredit",
        "offlineCreditRate",
        "offlineElapsed",
        "project",
        "ratesOf",
        "reanchor",
        "solveCrossing",
        "solveCrossingOver",
        "zeroStocks"
      ],
      "invariants": [
        "Closed form, never a loop. maxBuyable is O(1) and 12x faster than a 400-step buy loop; the loop is legitimate only as a test oracle.",
        "The economy has no tick. State is (stocks, rates, lastTimestamp) and is integrated on read. sim reads no clock and accepts no delta — every call that moves the anchor takes a required epoch timestamp.",
        "The topological order is computed by Kahn and therefore proven. Declared storage order stays separate from evaluation order, so a v4 node cannot move a v1 save's fields.",
        "Cycles and self-loops are refused at construction, naming the cycle. A numerical fallback would be a second implementation of the economy that diverges silently on exactly the saves that matter.",
        "Offline progress warps time, never yield, and a plan is never re-based. Credit for a resumed absence is W(span) - W(from), which telescopes; restarting the warp at each discovered crossing would pay for K absences instead of one, and each restart is cheaper.",
        "The upper clamp on an offline gap is the softcap's flat branch. A device clock a year fast credits eleven hours."
      ],
      "entryPoints": [
        "defineEconomy",
        "advance",
        "advanceOver",
        "maxBuyable"
      ],
      "layer": 1,
      "gzipKb": 7.58,
      "budgetKb": 12,
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/packages/sim",
      "readme": "https://github.com/C-Aniruddh/lattice/blob/main/packages/sim/README.md",
      "reference": "/reference/sim/"
    },
    "draw": {
      "name": "@latticekit/draw",
      "purpose": "The rendering layer: a Surface interface with a Canvas2D backend, color derivation, and the isometric solid kit that makes procedural art read as designed.",
      "environment": "browser (Canvas2D) with an offscreen/headless backend for tests",
      "dependsOn": [
        "core",
        "iso"
      ],
      "modules": [
        "surface",
        "canvas2d",
        "record",
        "color",
        "palette",
        "solids",
        "sprite",
        "shadow",
        "light",
        "text",
        "layers",
        "cache"
      ],
      "exports": [
        "Animator",
        "BASE_SLOTS",
        "Bitmap",
        "BlitMode",
        "BoxOpts",
        "Canvas2dOpts",
        "DAY",
        "DEFAULT_TEXT",
        "DUSK",
        "ESTIMATED_ADVANCE_RATIO",
        "Emitter",
        "FACE_LEFT",
        "FACE_RIGHT",
        "FACE_TOP",
        "FLAG_BUILDING",
        "FLAG_GHOST",
        "FLAG_POWERED",
        "FLAG_SELECTED",
        "FrameOpts",
        "GHOST_LIFT",
        "GROUND_LIFT",
        "Ink",
        "LEVEL_H",
        "LIGHT_TINT",
        "Layer",
        "LightField",
        "LightFieldOpts",
        "MIN_WALL_TEXT_PX",
        "Massing",
        "NIGHT",
        "OffscreenOpts",
        "OffscreenSurface",
        "Op",
        "OpName",
        "PALETTE_STEPS",
        "PASS_NAMES",
        "Palette",
        "Passes",
        "Pen",
        "RecordingSurface",
        "RecordingTarget",
        "RenderTarget",
        "Rgba",
        "SELECT_LIFT",
        "SHADE_TINT",
        "SolidWriter",
        "SpriteDef",
        "Stops",
        "Surface",
        "SurfaceKind",
        "TargetMode",
        "TextStyle",
        "VARIANT_ZERO",
        "VERSION",
        "Variant",
        "Vars",
        "beginFrame",
        "contactShadow",
        "createCanvas2dSurface",
        "createLightField",
        "createOffscreenSurface",
        "createPalette",
        "createRecordingSurface",
        "cssOf",
        "defineSprite",
        "drawFootprint",
        "drawGhost",
        "drawSprite",
        "endFrame",
        "extendStops",
        "glowDot",
        "hex",
        "hexOf",
        "hsl",
        "hueToHex",
        "isoBox",
        "isoCylinder",
        "isoPatch",
        "isoPost",
        "isoRoof",
        "isoTerrain",
        "isoTile",
        "isoWall",
        "lerpPalette",
        "levelsToPx",
        "mix",
        "outlineOf",
        "paletteVars",
        "pxToLevels",
        "renderFrame",
        "rgba",
        "screenText",
        "shade",
        "spriteBounds",
        "spriteHeightPx",
        "spriteVolume",
        "subPen",
        "wallText",
        "wash",
        "withAlpha"
      ],
      "invariants": [
        "Draw calls go through Surface. No package reaches for CanvasRenderingContext2D directly.",
        "A solid is described by one color; its faces are derived. Shadows cool, highlights warm.",
        "Everything is drawable into an offscreen surface, which is what makes UI thumbnails and golden tests possible."
      ],
      "entryPoints": [
        "createCanvas2dSurface",
        "renderFrame",
        "createPalette",
        "createLightField"
      ],
      "backends": {
        "modules": [
          "canvas2d",
          "record"
        ],
        "reason": "Two implementations of Surface, and no consumer builds both. A browser game creates a canvas2d surface; a headless replay or golden test creates a recording surface. Summing them measures a bundle nobody ships."
      },
      "layer": 2,
      "gzipKb": 12.33,
      "budgetKb": 12.5,
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/packages/draw",
      "readme": "https://github.com/C-Aniruddh/lattice/blob/main/packages/draw/README.md",
      "reference": "/reference/draw/"
    },
    "input": {
      "name": "@latticekit/input",
      "purpose": "Normalized input: pointer, touch and keyboard into one per-tick sample stream, plus gestures, an action map, a camera controller, and the recorded log a replay is driven from.",
      "environment": "browser",
      "dependsOn": [
        "core",
        "iso"
      ],
      "modules": [
        "profile",
        "sample",
        "step",
        "recognize",
        "actions",
        "terrain",
        "events",
        "cameracontrol",
        "scope",
        "system",
        "record",
        "dom"
      ],
      "exports": [
        "ActionBinding",
        "ActionEvent",
        "ActionMap",
        "CameraController",
        "DEFAULT_PROFILE",
        "Diagnostic",
        "DiagnosticCode",
        "DiagnosticSink",
        "Disposer",
        "DomInputSystem",
        "DragGesture",
        "FixedStep",
        "GestureBase",
        "GestureMap",
        "GestureName",
        "GestureProfile",
        "GridPoint",
        "HeadlessInputOptions",
        "HeightField",
        "InputLog",
        "InputOptions",
        "InputRecording",
        "InputScope",
        "InputSystem",
        "LOG_VERSION",
        "PointerKind",
        "ProfileOverrides",
        "ProfileScalar",
        "RawSample",
        "ReplayCursor",
        "TapGesture",
        "Terrain",
        "TerrainOption",
        "VERSION",
        "Vec2",
        "ZoomGesture",
        "ZoomSource",
        "createHeadlessInput",
        "createInput",
        "createLog",
        "fixedStep",
        "record",
        "replay",
        "replayCursor"
      ],
      "invariants": [
        "Input never learns what is in the world. No option, method or callback takes state, a rect, a pickable flag or a hit — so there is nowhere to cache a hit box and nothing that would read one.",
        "Gestures are delivered on simulation ticks, never on frames, and never from a clock this package read itself.",
        "Every event carries the tile it resolved to, through the camera as it stood when the tick opened and the ground as the caller declared it. A declared height field is marched; \"flat\" is the plane, stated; and an omitted declaration is still the plane but says so once, the first time a coordinate is read, because a picked tile that is silently wrong on a slope is the failure this seam exists to prevent.",
        "Off the map the coordinates are NaN and onGround is false, never a sea-level fallback. A number that is wrong and plausible is worse than one that is unmistakably absent, and a throw would crash the ordinary gesture of dragging the sky.",
        "Zoom is anchored to the pointer, and zoom is not publicly assignable — zoomAt is the only mutator, so skipping the anchoring is unavailable rather than discouraged.",
        "There is no free-function binder. Listeners come only from a scope, so an unowned listener cannot be constructed.",
        "A tick sees a bucket closed before it started. Mid-tick arrivals go to the next bucket; overflow collapses moves and never drops a down, up, cancel, key or wheel."
      ],
      "entryPoints": [
        "createInput",
        "createHeadlessInput",
        "createLog"
      ],
      "layer": 2,
      "gzipKb": 15.39,
      "budgetKb": 16,
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/packages/input",
      "readme": "https://github.com/C-Aniruddh/lattice/blob/main/packages/input/README.md",
      "reference": "/reference/input/"
    },
    "ui": {
      "name": "@latticekit/ui",
      "purpose": "DOM overlay primitives — a declarative element builder, panels, toasts, number rolls, and thumbnails rendered from the draw kit. Deliberately not a framework.",
      "environment": "browser",
      "dependsOn": [
        "core",
        "draw"
      ],
      "modules": [
        "overlay",
        "el",
        "panel",
        "toast",
        "roll",
        "thumb",
        "theme"
      ],
      "exports": [
        "AcknowledgeOptions",
        "Attrs",
        "BrandOptions",
        "CadenceFn",
        "Child",
        "Dispose",
        "Disposer",
        "Driven",
        "FloatHost",
        "FloatKind",
        "FloatOptions",
        "LayerName",
        "MountOptions",
        "Overlay",
        "OverlayOptions",
        "Palette",
        "PaletteOptions",
        "Panel",
        "PanelOptions",
        "Roll",
        "RollOptions",
        "ScreenPoint",
        "ThumbCache",
        "ThumbSpec",
        "ToastHost",
        "ToastKind",
        "ToastOptions",
        "VERSION",
        "acknowledge",
        "applyPalette",
        "auditOverlay",
        "clear",
        "createOverlay",
        "drive",
        "el",
        "floats",
        "hide",
        "interactive",
        "panel",
        "passthrough",
        "pulse",
        "roll",
        "setBrand",
        "setText",
        "setTokens",
        "show",
        "thumbnails",
        "toasts"
      ],
      "invariants": [
        "No virtual DOM. The whole overlay is a few dozen nodes that change a few times a second.",
        "The package ships no stylesheet at all. The root is inline pointer-events:none and interactivity is granted per node, so the specificity fight that swallows taps on the world has no rule to lose to.",
        "State updates on the interval cadence, never inside the render callback. If render never runs, every number on screen is still right.",
        "Anything that is not painting must survive a hidden tab. The loop's update callback IS the interval — a second setInterval here recreates the poll-races-settle data-loss bug."
      ],
      "entryPoints": [],
      "layer": 3,
      "gzipKb": 8.72,
      "budgetKb": 12,
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/packages/ui",
      "readme": "https://github.com/C-Aniruddh/lattice/blob/main/packages/ui/README.md",
      "reference": "/reference/ui/"
    }
  },
  "contracts": [
    {
      "claim": "draw must not reorder after iso's sort()",
      "packages": [
        "iso",
        "draw"
      ],
      "breaksAs": "the tap opens the building behind the one under the finger",
      "test": "test/contracts/"
    },
    {
      "claim": "draw's stroke traces boxSilhouette's six points in order",
      "packages": [
        "iso",
        "draw"
      ],
      "breaksAs": "hit-testing and pixels diverge with no test in either package noticing",
      "test": "test/contracts/"
    },
    {
      "claim": "the tick index starts at 0, increments by one, never skips or repeats",
      "packages": [
        "loop",
        "input",
        "persist"
      ],
      "breaksAs": "a replay that reports a confident wrong answer",
      "test": "test/contracts/"
    },
    {
      "claim": "stepMs is a compatibility constant appearing in recorded sessions",
      "packages": [
        "loop",
        "persist"
      ],
      "breaksAs": "a log recorded at 60Hz replayed at 50Hz diverges for reasons no stack trace shows",
      "test": "test/contracts/"
    }
  ],
  "readiness": {
    "version": "0.1.1",
    "publishedToNpm": false,
    "stable": [
      "the exported names — `npm run lint` fails the build if a package exports a name .lattice/kit.json does not list",
      "their behavior — 2,648 tests, 90% statements per package and 100% in core, enforced",
      "the layering and the determinism rule, both lint-enforced",
      "the per-package gzip budgets"
    ],
    "notStable": [
      "function signatures: nothing has shipped to a registry, so nothing outside this repository uses them yet",
      "the /lattice plugin: specified in docs/SKILLS.md, not built"
    ],
    "versioning": {
      "scheme": "semver",
      "preOneRule": "a minor bump may break source compatibility; a patch never does",
      "lockstep": "the nine packages version and publish together, one number for the whole kit",
      "breakageKinds": {
        "source": "a renamed symbol or a changed signature. Loud; the compiler finds every one.",
        "artifact": "a change that invalidates something already written down — a save, a replay log, a shared seed. Silent. Ships with a migration or does not ship. See docs/SEAMS.md."
      }
    }
  },
  "browsers": {
    "renderer": "Canvas2D",
    "requires": [
      "Canvas2D",
      "requestAnimationFrame",
      "ResizeObserver",
      "PointerEvent",
      "setPointerCapture"
    ],
    "optional": {
      "@latticekit/persist": "localStorage, behind a swappable storage adapter",
      "@latticekit/audio": "AudioContext; needs a user gesture before it makes a sound"
    },
    "doesNotUse": [
      "WebGL",
      "WebGPU",
      "WebAssembly",
      "Web Workers",
      "OffscreenCanvas"
    ],
    "output": "ES2022 ES modules, unminified",
    "floor": {
      "chrome": 92,
      "edge": 92,
      "firefox": 90,
      "safari": 15.4,
      "derivedFrom": "the compiler target plus the newest syntax actually present in the built output — private class fields and Array.prototype.at. NOT a browser test matrix: CI runs the suite in Node on 20.19, 22 and 24."
    }
  },
  "alternatives": {
    "three": "a 3D renderer. An isometric game is a 2D projection with a sorting rule; a scene graph and a camera stack are a large dependency for a coordinate transform.",
    "pixi": "a fast 2D renderer and nothing else, so the projection, depth sort, pathfinding, seeded noise, save migrations and sound are still yours to write — which is most of what these nine packages are.",
    "phaser": "the closest and fairest comparison: a complete engine with scenes, physics, input, audio and a loader, and a decade of documentation. If you want a game engine, use Phaser.",
    "whatIsDifferentHere": [
      "deterministic by rule, not by discipline: the clock and the random source are lint errors inside a package, which is what makes a replay land on the same pixel",
      "no asset pipeline at all: art is derived from one color per solid, sound is synthesized from a declaration",
      "written to be handed to an agent: manifest, invariants, contracts and known traps machine-readable here"
    ]
  },
  "gallery": {
    "hero": {
      "dir": "demo",
      "name": "Lamp Road",
      "seed": "lamp-road",
      "caption": "A valley at dusk, and the proof that the seams fit.",
      "packages": [
        "core",
        "iso",
        "draw",
        "loop",
        "input",
        "audio",
        "sim",
        "ui"
      ],
      "tag": "THE WHOLE KIT",
      "prompt": "a valley at dusk with a road up to a shrine, and pilgrims who will not walk into the dark",
      "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/demo",
      "live": "/x/demo/"
    },
    "exhibits": [
      {
        "dir": "crowd",
        "name": "Crowd",
        "seed": "piazza",
        "tag": "CLOSED-FORM CROWDS",
        "prompt": "a busy piazza with hundreds of people crossing it, all walking somewhere",
        "caption": "Nine hundred walkers, one expression, no per-walker state.",
        "idea": "A walker's position is pathSample(route, ((phi*i + t*v) mod 1) * arcLength). There is no walker struct.",
        "fact": "0 bytes of state per walker",
        "factFrom": "examples/crowd/src/hud.ts:74",
        "leans": [
          "iso.path"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/crowd",
        "live": "/x/crowd/"
      },
      {
        "dir": "island",
        "name": "Island",
        "seed": "atoll",
        "tag": "DAY CYCLE",
        "prompt": "a small island where I can watch a whole day go by in a minute",
        "caption": "One day, in ninety seconds.",
        "idea": "Terrain, a shoreline, trees, and a full day/night cycle — the palette lerp doing the whole job.",
        "fact": "a 90-second day",
        "factFrom": "examples/island/src/main.ts:45",
        "leans": [
          "draw",
          "iso"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/island",
        "live": "/x/island/"
      },
      {
        "dir": "clay",
        "name": "Clay",
        "seed": "riverbed",
        "tag": "LIVE TERRAIN",
        "prompt": "a valley where I can push the ground up with my cursor and everything on it resettles",
        "caption": "The ground is material. Drag it, and watch everything else resettle.",
        "idea": "Raise a ridge and the water re-routes, the walkers re-plan, and the trees ride it up and slide off.",
        "fact": "2,200 props over a live height field",
        "factFrom": "examples/clay/README.md:181",
        "leans": [
          "iso.height",
          "iso.path"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/clay",
        "live": "/x/clay/"
      },
      {
        "dir": "harbor",
        "name": "Harbor",
        "seed": "harbor",
        "by": "Codex",
        "tag": "DEPTH SORTING",
        "prompt": "a harbor at dusk with tall masts and cranes, and a long jetty out over the water",
        "caption": "Tall thin objects, one depth order.",
        "idea": "A mast is a twentieth of a tile wide and twelve storeys high, and only its base says where it belongs. One DepthSorter holds every hull, crane and the ninety-two-tile jetty.",
        "fact": "197 objects, one depth order",
        "factFrom": "examples/harbor/README.md:92",
        "leans": [
          "iso.depth"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/harbor",
        "live": "/x/harbor/"
      },
      {
        "dir": "wayfinding",
        "name": "Wayfinding",
        "seed": "concourse",
        "by": "Codex",
        "tag": "FLOW FIELD",
        "prompt": "a crowded hall where I can close a gate and watch everyone find another way round",
        "caption": "One field, six hundred and forty readers, one rebuild.",
        "idea": "There is no per-walker path. Closing a crossing rebuilds one field, and every walker's next read returns a different answer — so a map change costs the same whatever the crowd is.",
        "fact": "640 walkers, one rebuild",
        "factFrom": "examples/wayfinding/src/main.ts:207",
        "leans": [
          "iso.path"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/wayfinding",
        "live": "/x/wayfinding/"
      },
      {
        "dir": "builder",
        "name": "Builder",
        "seed": "731",
        "by": "Codex",
        "tag": "PLACEMENT",
        "prompt": "a yard where I drop a workshop down and it tells me when it will not fit",
        "caption": "Footprints, a ghost, validity, and the tap-to-tile seam.",
        "idea": "One predicate between the pixel a finger landed on and the six tiles it would occupy. The ghost, the color under it and the refusal are all that same boolean.",
        "fact": "1,444 yard tiles, 66 obstacles",
        "factFrom": "examples/builder/README.md:84",
        "leans": [
          "iso",
          "input"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/builder",
        "live": "/x/builder/"
      },
      {
        "dir": "caverns",
        "name": "Caverns",
        "seed": "lampblack",
        "tag": "LIGHT POOLS",
        "prompt": "a cave lit only by scattered lanterns, with the pools of light overlapping",
        "caption": "Pools that meet without a seam.",
        "idea": "The light field costs its buffer, not its light count — which is why the price is flat from 104 pools to 704.",
        "fact": "704 light pools",
        "factFrom": "examples/caverns/README.md:98",
        "leans": [
          "draw.light"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "lightScale=0.3",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/caverns",
        "live": "/x/caverns/"
      },
      {
        "dir": "orbit",
        "name": "Orbit",
        "seed": "polaris-7",
        "by": "Codex",
        "tag": "NO GROUND",
        "prompt": "stations turning in space with stars behind them and no ground anywhere",
        "caption": "No ground at all — platforms, stars, a cold palette.",
        "idea": "No TileGrid, no terrain pass and no height field. Every platform's angle is a closed-form function of t, and three star bands carry the depth the ground usually would.",
        "fact": "214 sorted objects, no ground",
        "factFrom": "examples/orbit/README.md:94",
        "leans": [
          "draw.palette"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/orbit",
        "live": "/x/orbit/"
      },
      {
        "dir": "replay",
        "name": "Replay",
        "seed": "20250818",
        "by": "Claude",
        "tag": "SEEDED REPLAY",
        "prompt": "a marsh I can record, wind back, and prove came out the same",
        "caption": "Tap the marsh to seed a bloom. Then scrub the tape: every frame is recomputed from the seed and the log, never remembered.",
        "idea": "The bar re-runs from tick zero on every pointer move, checks each checkpoint as it passes, and can come back red — a check that cannot fail proves nothing.",
        "fact": "1,081 ticks re-run in 1.5 ms",
        "factFrom": "examples/replay/README.md:179",
        "leans": [
          "loop",
          "persist",
          "input"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "persist",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/replay",
        "live": "/x/replay/"
      },
      {
        "dir": "terraces",
        "name": "Terraces",
        "seed": "contour",
        "tag": "ELEVATION PICKING",
        "prompt": "stepped terraces on a hillside where I can click on any level and hit the one I meant",
        "caption": "Elevation, and why a tap needs the terrain.",
        "idea": "On ground with height the obvious screen-to-tile conversion is wrong, and it is wrong by more the higher you climb.",
        "fact": "the naive pick misses by 1,400 px at the ridge",
        "factFrom": "examples/terraces/README.md:41",
        "leans": [
          "iso.height"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/terraces",
        "live": "/x/terraces/"
      },
      {
        "dir": "instrument",
        "name": "Instrument",
        "seed": "overtone",
        "by": "Grok",
        "tag": "SYNTHESIS, DRAWN",
        "prompt": "a hall of pipes I can play, where I can see the sound being made",
        "caption": "Sound with no files.",
        "idea": "The ribbon in the air is Audio.onScheduled — what the synthesizer was asked to make, drawn before it is audible. An analyser would draw the speaker instead.",
        "fact": "60 recipes, 0 bytes of audio",
        "factFrom": "examples/instrument/README.md:37",
        "leans": [
          "audio"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "audio",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/instrument",
        "live": "/x/instrument/"
      },
      {
        "dir": "endless",
        "name": "Endless",
        "seed": "endless",
        "tag": "ENDLESS WORLD",
        "prompt": "a world I can keep panning across forever that never loads anything",
        "caption": "Pan forever. Nothing is loaded and nothing is kept.",
        "idea": "Chunks are minted from the seed as you reach them and evicted behind you. Come back and they are identical.",
        "fact": "256 chunks resident, a flat 128 KiB",
        "factFrom": "examples/endless/README.md:79",
        "leans": [
          "core.noise",
          "iso"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/endless",
        "live": "/x/endless/"
      },
      {
        "dir": "canyon",
        "name": "Canyon",
        "seed": "colorado",
        "tag": "EROSION",
        "prompt": "a desert canyon cut by a river, with a slider that rewinds a million years",
        "caption": "A million years of a river. The bar re-runs the model.",
        "idea": "Erosion accumulates, so the scrub bar is a deterministic re-run from the nearest checkpoint, never a lookup.",
        "fact": "one erosion step, 112x112 grid, 0.30 ms",
        "factFrom": "examples/canyon/README.md:112",
        "leans": [
          "iso.height",
          "core.noise"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/canyon",
        "live": "/x/canyon/"
      },
      {
        "dir": "idle",
        "name": "Idle",
        "seed": "foundry",
        "by": "Grok",
        "tag": "IDLE ECONOMY",
        "prompt": "a little brick works that keeps earning while I am away",
        "caption": "Fourteen hours, one step.",
        "idea": "The price of the next kiln is b·r^k evaluated rather than accumulated, so buy-max is arithmetic; a fourteen-hour absence is one advanceOver between two frames.",
        "fact": "28,285 s credited in one step",
        "factFrom": "examples/idle/README.md:98",
        "leans": [
          "sim"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "sim",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/idle",
        "live": "/x/idle/"
      },
      {
        "dir": "city",
        "name": "City block",
        "seed": "city-block",
        "tag": "DERIVED SOLIDS",
        "prompt": "a city block at night from above, with traffic in the streets and lit windows",
        "caption": "Setback massing and a window rhythm. Every value here is in the URL.",
        "idea": "The technique that carries the whole look: one color per solid, three faces derived, a rhythm of warm windows.",
        "fact": "36 buildings, 120 cars",
        "factFrom": "examples/city/src/traffic.ts:30",
        "leans": [
          "draw"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "ui"
        ],
        "tileParams": "lightScale=0.35",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/city",
        "live": "/x/city/"
      },
      {
        "dir": "migration",
        "name": "Migration",
        "seed": "archive",
        "by": "Claude",
        "tag": "SAVE MIGRATION",
        "prompt": "an old save file opened by five newer versions of the same game",
        "caption": "A v1 save opened by a v5 build, one rung at a time.",
        "idea": "Four migrations declared once, and five sealed builds fall out of the same chain. A crate that a build refuses topples back over the rung it failed, named.",
        "fact": "750 saves on the ladder",
        "factFrom": "examples/migration/README.md:114",
        "leans": [
          "persist"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "persist",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/migration",
        "live": "/x/migration/"
      },
      {
        "dir": "resonance",
        "name": "Resonance",
        "seed": "hollow",
        "tag": "SYNTHESIZED SOUND",
        "prompt": "a puzzle in a ruin where every gate hums a chord and I answer it by ear",
        "caption": "Answer the chord.",
        "idea": "A puzzle you solve by ear. Every gate hums a chord; you carry six tuned strings and no audio files exist.",
        "fact": "6 ms attack on a struck string",
        "factFrom": "examples/resonance/README.md:29",
        "leans": [
          "audio",
          "draw.light"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "audio",
          "ui"
        ],
        "tileParams": "lightScale=0.3",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/resonance",
        "live": "/x/resonance/"
      },
      {
        "dir": "errand",
        "name": "Errand",
        "seed": "meadowmill",
        "tag": "SAVE AND LOAD",
        "prompt": "a small village game where I walk around, talk to people, take things and save",
        "caption": "Walk, talk, take, use, save. The whole genre, small enough to read.",
        "idea": "Five verbs and deliberately no sixth. The save envelope is about sixty bytes and the HUD prints its size.",
        "fact": "a ~60-byte save envelope",
        "factFrom": "examples/errand/src/main.ts:35",
        "leans": [
          "iso.path",
          "ui",
          "persist"
        ],
        "packages": [
          "core",
          "iso",
          "draw",
          "loop",
          "input",
          "persist",
          "ui"
        ],
        "tileParams": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/examples/errand",
        "live": "/x/errand/"
      }
    ],
    "specifiedNotBuilt": [],
    "fanOut": {
      "claim": "8 of the 18 exhibits were built by 3 vendors' agents from docs/GALLERY.md alone",
      "method": "each agent was given only its own row of the exhibits table, the whole standard, and the tools, and was not allowed to read an existing exhibit's source — the test was whether the written spec is followable, not whether an agent can pattern-match",
      "harness": "7 of 8 passed every row of the looking harness unaided; the exception was Replay, on legibility, for a text node too small for the pass to measure",
      "builtBy": {
        "Codex": [
          "harbor",
          "wayfinding",
          "builder",
          "orbit"
        ],
        "Claude": [
          "replay",
          "migration"
        ],
        "Grok": [
          "instrument",
          "idle"
        ]
      },
      "whatTheyFound": "every one of the eight carries its author's own spec-gap notes verbatim in its README, and all eight hit the same wall: examples/_shared — the bootstrap and the control panel these documents assume a reader has — lives in this repository and is not shipped. Collected in docs/GALLERY.md § What eight strangers found in this document",
      "builtHere": "the other 10 exhibits and the hero were built in this repository with a person in the loop"
    }
  },
  "fromOneSentence": {
    "claim": "one sentence in, a playable game out, in an empty directory with no access to this repository",
    "method": "each game was built by a different vendor's agent from the one sentence below, with @latticekit/* installed from the public npm registry. The source is unedited; the only change made to any of them is the --port in its dev script",
    "record": "https://github.com/C-Aniruddh/lattice/blob/main/from-one-sentence/README.md",
    "games": [
      {
        "dir": "before-the-bell",
        "name": "Before the Bell",
        "agent": "Grok",
        "lead": true,
        "port": 5212,
        "sentence": "a game where you place stalls and open gates to pull the crowd to your bakery before the market closes",
        "size": "1,482 lines over 9 modules",
        "packages": [
          "audio",
          "core",
          "draw",
          "input",
          "iso",
          "loop",
          "ui"
        ],
        "defect": "",
        "params": "demo=1",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/from-one-sentence/before-the-bell",
        "live": "/g/before-the-bell/"
      },
      {
        "dir": "chime-path",
        "name": "Chime Path",
        "agent": "Claude",
        "lead": false,
        "port": 5210,
        "sentence": "a game where you hang chimes along a mountain path and tune each one, so the wind plays them in order as walkers pass",
        "size": "1,341 lines over 6 modules",
        "packages": [
          "audio",
          "core",
          "draw",
          "input",
          "iso",
          "loop",
          "persist",
          "ui"
        ],
        "defect": "five HUD nodes ship under the contrast floor, and the agent that drove a browser still missed them",
        "params": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/from-one-sentence/chime-path",
        "live": "/g/chime-path/"
      },
      {
        "dir": "evenfall-orchard",
        "name": "Evenfall Orchard",
        "agent": "Codex",
        "lead": false,
        "port": 5211,
        "sentence": "a game where you plant an orchard and each evening choose to harvest or let it grow, and it keeps growing while the tab is closed",
        "size": "73 lines in one module",
        "packages": [
          "core",
          "draw",
          "input",
          "iso",
          "loop",
          "persist",
          "sim",
          "ui"
        ],
        "defect": "one phase of its day is 84% one near-black color, which its author never saw",
        "params": "",
        "source": "https://github.com/C-Aniruddh/lattice/tree/main/from-one-sentence/evenfall-orchard",
        "live": "/g/evenfall-orchard/"
      }
    ]
  },
  "example": {
    "path": "site/example/hello.ts",
    "source": "https://github.com/C-Aniruddh/lattice/blob/main/site/example/hello.ts",
    "code": "/**\n * The whole of a Lattice program, and the one the landing page prints and runs side by side.\n *\n * It is a real file rather than a snippet, and it is the same file in both places: `site/tools/\n * build.mjs` typechecks it against the built packages before the page is generated, and\n * `site/example/index.html` is a real page that boots it, which the Getting started section\n * mounts in an iframe. So the listing cannot become a lie in either direction — a signature that\n * moves in the kit breaks the build, and a program that no longer draws anything is visible on\n * the page beside its own source.\n *\n * **Ten code lines, and that is the specification rather than a coincidence.** The forty-line\n * version this replaces was a seeded town on rolling ground with a depth sorter, a `Passes`\n * object and pointer input, and it was read by somebody who does not write TypeScript as proof\n * that a Lattice program is long. What a stranger has to be able to see in four seconds is: a\n * surface, a camera, a palette, a loop, and a world drawn inside it. Everything past that is in\n * the gallery, standing over the thing it does.\n *\n * The one thing the listing leaves to its host is the canvas's size, because\n * `createCanvas2dSurface` reads `clientWidth`/`clientHeight` and a bare `<canvas>` is 300x150.\n * `index.html` beside this file gives it the viewport in two lines of CSS.\n */\nimport { createCamera } from '@latticekit/iso';\nimport { BASE_SLOTS, beginFrame, createCanvas2dSurface, createPalette, endFrame, isoBox } from '@latticekit/draw';\nimport { browserFrames, createLoop } from '@latticekit/loop';\n\nconst surface = createCanvas2dSurface(document.body.appendChild(document.createElement('canvas')));\nconst camera = createCamera(innerWidth, innerHeight, { zoom: 0.62 }), palette = createPalette(BASE_SLOTS);\n\ncreateLoop({ clock: { now: () => performance.now() }, frames: browserFrames(), render: (_alpha, t) => {\n  const pen = beginFrame({ surface, camera, palette, t, clear: 'sky' });   // erase, then paint the sky\n  // Back to front is just the loop order in a 2:1 projection, so this city needs no depth sort.\n  for (let gy = -7; gy < 7; gy++) for (let gx = -7; gx < 7; gx++) isoBox(pen, gx, gy, 1, 1, { color: 'metal', h: 2 + 5 * Math.sin(t + (gx + gy) * 0.4) ** 2 });\n  endFrame(pen);\n} }).start();\n"
  }
}
