{
  "$comment": "The machine-readable index of the kit. Agents read this before reading source. Every package MUST keep its `exports` and `invariants` current; `npm run lint` fails the build if a package exports a symbol that is not listed here.",
  "name": "lattice",
  "tagline": "The grid underneath.",
  "version": "0.1.1",
  "license": "MIT",
  "repository": "https://github.com/C-Aniruddh/lattice",
  "homepage": "https://lattice.aniruddh.tech",
  "scope": "@latticekit",
  "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."
    },
    "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"
      ]
    },
    "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."
      }
    },
    "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"
      ]
    },
    "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"
      ]
    },
    "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"
      ]
    },
    "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"
      ]
    },
    "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"
      ]
    },
    "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": []
    }
  },
  "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/"
    }
  ]
}
