@flatkit/compiler 0.35.3 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/manifest.ts","../src/languageCard.ts","../src/drawingCard.ts","../src/index.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// manifest.ts — \"map of the scene\" for an LLM (and tooling introspection).\n//\n// The language fits in < 2k tokens (see languageCard); the real context cost for an LLM\n// is knowing WHAT IT CAN NAME in THIS scene (objects, assets, variables, functions).\n// `docToManifest` derives this compact block from a Doc — the exact counterpart of references\n// by name (sceneRefs): the model then references only REAL names, and the linter catches the rest.\n//\n// Pure, derived (never stored). ~a few hundred tokens for a real scene.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Doc, Group, Image, Instance, ItemEvent, Layer, Text } from '@flatkit/types'\nimport { EXPR_CHANNELS, OFFSET_CHANNELS } from '@flatkit/engine/timeline'\nimport { getSymbol, isGroup, isImage, isInstance, isPoseable, isText } from '@flatkit/engine/layers'\nimport { analyzeExpr } from '@flatkit/engine/expr'\nimport { forEachAction, forEachActionExpression, forEachItemExpression } from './docWalk'\nimport { languageCard } from './languageCard'\nimport { drawingCard } from './drawingCard'\n\n/** Type label of a poseable (hence named) item. */\nfunction kindLabel(doc: Doc, it: Group | Instance | Text | Image): string {\n if (isInstance(it)) { const s = getSymbol(doc, it.symbolId); return s ? `Instance:${s.name}` : 'Instance' }\n if (isText(it)) return 'Text'\n if (isImage(it)) return 'Image'\n return 'Symbol' // Group (\"symbol\" in the UI)\n}\n\n/**\n * A named scene object and the CONTRACT the logic places on it — what a host must honour to reskin the\n * scene without touching its behavior. Deliberately free of coordinates: the names and the roles are the\n * contract, the composition stays entirely the skin's business. (A contract that carried positions would\n * hand every reskin the same layout, which is exactly the failure it exists to prevent.)\n */\nexport type ManifestObject = {\n name: string\n kind: string\n /** Item events the logic handles on it (`click`, `drop`…) — the skin must keep them reachable. */\n events: ItemEvent[]\n /** A `drag`/`turn` interactor targets it: the pointer writes its position, so the skin must not pin it. */\n dragged: boolean\n /** A placement target: something drops on it, or it carries a `hitbox`. Keep the name and a generous box. */\n zone: boolean\n /** Channels the logic drives (bindings and modifier targets) — a skin that sets these fights the logic. */\n channels: string[]\n /** Document variables it reads: the game state a skin can hang its own visuals on. */\n reads: string[]\n}\n\n/** The program's STATE: every variable a skin could meaningfully bind to. Wider than `doc.variables`,\n * which only holds the `var`-declared ones — a variable first assigned in a handler, or written by a\n * `drag` interactor, is just as real at runtime and is often the most interesting one to bind. */\nfunction stateVariables(doc: Doc): Set<string> {\n const out = new Set(Object.keys(doc.variables ?? {}))\n forEachAction(doc, (a) => {\n if (a.do === 'setVar' || a.do === 'setIndex') out.add(a.name)\n else if (a.do === 'repeatRange') out.add(a.var)\n })\n for (const i of doc.interactors ?? []) for (const v of [i.varX, i.varY, i.varT]) if (v) out.add(v)\n return out\n}\n\n/** State variables referenced by an expression — parsed, not grepped, so `open` never matches `reopen`\n * and a name nested inside a call still counts. An unparseable expression yields nothing (the linter\n * reports it; the manifest stays quiet). Runtime scalars (`time`, `mouse.x`) are not state and never\n * appear: they are not something a host can set. */\nfunction readsOf(expr: string, state: Set<string>, into: Set<string>): void {\n const a = analyzeExpr(expr)\n if (!a.ok) return\n for (const id of a.refs.ids) if (state.has(id)) into.add(id)\n}\n\n/** Named scene objects (groups included, library symbols excluded) with their binding contract; first\n * name wins, and nested groups are walked. */\nexport function manifestObjects(doc: Doc): ManifestObject[] {\n const out: ManifestObject[] = []\n const seen = new Set<string>()\n const state = stateVariables(doc)\n const dropZones = new Set((doc.interactions ?? []).filter((i) => i.event === 'drop' && i.over).map((i) => i.over as string))\n const walk = (layers: Layer[]) => {\n for (const l of layers) for (const it of l.items) {\n if (isPoseable(it) && it.name && !seen.has(it.name)) {\n seen.add(it.name)\n const mine = (doc.interactions ?? []).filter((i) => i.targetId === it.id)\n const channels: string[] = []\n const reads = new Set<string>()\n forEachItemExpression(it, (expr, channel) => { channels.push(channel); readsOf(expr, state, reads) })\n for (const i of mine) forEachActionExpression(i.actions, (expr) => readsOf(expr, state, reads))\n out.push({\n name: it.name,\n kind: kindLabel(doc, it),\n events: [...new Set(mine.map((i) => i.event))],\n dragged: !!doc.interactors?.some((i) => i.targetId === it.id),\n zone: dropZones.has(it.name) || (isGroup(it) && !!it.hitbox),\n channels,\n reads: [...reads],\n })\n }\n if (isGroup(it)) walk(it.layers)\n }\n }\n walk(doc.layers)\n return out\n}\n\n/** The events the program EMITS to its host (`send \"…\"`), in order of first appearance, deduped. The\n * other half of the contract: the moments a host — or a skin's motion pass — can react to. */\nexport function manifestEvents(doc: Doc): string[] {\n const events = new Set<string>()\n forEachAction(doc, (a) => { if (a.do === 'send') events.add(a.event) })\n return [...events]\n}\n\n/** One object's contract, as readable clauses. Empty = pure decor the skin owns outright. */\nfunction contractParts(o: ManifestObject): string[] {\n const parts: string[] = []\n if (o.dragged) parts.push('drag')\n if (o.zone) parts.push('zone')\n if (o.events.length) parts.push(`on ${o.events.join('/')}`)\n if (o.channels.length) parts.push(`driven: ${o.channels.join(' ')}`)\n if (o.reads.length) parts.push(`reads: ${o.reads.join(' ')}`)\n return parts\n}\n\n/** Variables → `name=value` (scalar) or `name[len]` (array). */\nfunction manifestVars(doc: Doc): string[] {\n return Object.entries(doc.variables ?? {}).map(([k, v]) => (Array.isArray(v) ? `${k}[${v.length}]` : `${k}=${v}`))\n}\n\n/**\n * Compact map of the scene (objects/assets/variables/functions/packages) — injectable in a prompt.\n * Only non-empty sections appear. The names are the ones the code can reference.\n */\nexport function docToManifest(doc: Doc): string {\n const objs = manifestObjects(doc)\n const assets = (doc.assets ?? []).map((a) => `${a.kind}:${a.id}`)\n const vars = manifestVars(doc)\n const funcs = (doc.functions ?? []).map((f) => `${f.name}(${f.params.join(', ')})`)\n const lines = ['# SCENE', `size: ${doc.width}x${doc.height}`]\n if (objs.length) lines.push(`objects: ${objs.map((o) => `${o.name}(${o.kind})`).join(', ')}`)\n if (assets.length) lines.push(`assets: ${assets.join(', ')}`)\n if (vars.length) lines.push(`vars: ${vars.join(', ')}`)\n if (funcs.length) lines.push(`funcs: ${funcs.join(', ')}`)\n if (doc.imports?.length) lines.push(`packages: ${doc.imports.join(', ')}`)\n // The binding contract, for the objects that carry one — what a skin must honour. No positions: the\n // composition is the skin's to invent, which is precisely what keeps two scenes from looking alike.\n const contract = objs.map((o) => ({ o, parts: contractParts(o) })).filter(({ parts }) => parts.length)\n if (contract.length) {\n lines.push('contract (honour these; the layout is yours):')\n for (const { o, parts } of contract) lines.push(` ${o.name} - ${parts.join(', ')}`)\n }\n const events = manifestEvents(doc)\n if (events.length) lines.push(`events: ${events.join(', ')}`)\n lines.push(`channels: ${EXPR_CHANNELS.join(' ')} (additive offsets: ${OFFSET_CHANNELS.join(' ')} -> pos = at + d)`)\n return lines.join('\\n')\n}\n\n/**\n * EVERYTHING a model needs to write a whole program: both language references (static) + the scene map\n * derived from the Doc. It is the bundle, not the map — if you only want the names this scene can\n * reference (because you already inject the references yourself), that is **`docToManifest(doc)`**, and\n * calling this instead ships the cards a second time.\n *\n * The DRAWING card is included by default. Handed the behavior card alone, a model asked for decor\n * invents a shapes grammar — and what it invents does not compile. Pass `{ drawing: false }` when the\n * model only edits behavior and the prompt budget is tight.\n */\nexport function llmContext(doc: Doc, opts: { drawing?: boolean } = {}): string {\n const cards = opts.drawing === false ? languageCard() : `${languageCard()}\\n\\n${drawingCard()}`\n return `${cards}\\n\\n${docToManifest(doc)}`\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// languageCard.ts — TERSE reference of FlatInk Script, \"system-prompt\" sized.\n//\n// Single source of truth to steer an LLM (or a human in a hurry): condensed\n// grammar + built-ins, ~1k tokens. The function/constant/channel lists are\n// INTERPOLATED from expr/timeline/stdlib → never out of sync with the real engine.\n// To be paired with `docToManifest` (manifest.ts) for the names specific to a scene.\n// ─────────────────────────────────────────────────────────────────────────────\nimport { STD_CONSTANTS, STD_FUNCTIONS } from '@flatkit/engine/expr'\nimport { EXPR_CHANNELS } from '@flatkit/engine/timeline'\nimport { PACKAGES } from '@flatkit/engine/stdlib'\n\n/** Language reference card (static, kept in sync with the engine via STD_*). */\nexport function languageCard(): string {\n return `# FlatInk Script — reference\nNumbers only (0 = false, anything else = true). No string type (only event/label/asset names in quotes). Comment: // to end of line.\nOne statement per line — a newline ends the statement (write \\`x = 1\\` then \\`y = 2\\` on separate lines, not \\`x = 1 y = 2\\`).\n\n## Objects\nobject \"Name\" { … } attaches behavior BY NAME. The name must be a group / instance / text / image — the only things that carry a pose.\nA SHAPE (\\`rect … as \"N\"\\`) or a LAYER cannot be animated: wrap the shape in \\`group \"N\" { layer \"art\" { … } }\\`. (Naming one is a compile error.)\nOpacities MULTIPLY down the tree — do not set \\`opacity 0\\` on the shape to hide it at rest, or the group's fade-in stays invisible.\n\n## Events (attach to an object or to the scene)\nwhen loaded { } // once, on start\nevery frame { } // every frame\nat frame N { } // when the playhead reaches frame N\nwhen clicked|hovered|unhovered|pressed|dragged|released|held { }\n\n## Actions\nplay · pause · go to frame N [and play|and pause] · go to \"label\"\nname = expr // set a variable\narr[i] = expr // write an array slot\narr = fill(n, v) // replace a WHOLE array (the only array-valued assignment; expressions are scalar)\nif cond { } else { } · repeat N times { } · repeat i from A to B { }\nmyProc() · send \"event\"[, expr | text(\"id\") | { a = expr, b … }] · sound \"assetId\"\n\n## Expressions (drive a channel, or compute in an action)\nchannel = expr channels: ${EXPR_CHANNELS.join(' ')} (expression wins over keyframes)\ndx = expr · dy = expr // ADDITIVE offset: final pos = at + (dx, dy) — oscillate AROUND the anchor (dx = 30*sin(time)); absolute x/y REPLACE at\nrotationDeg = expr // sugar for rotation = rad(expr) — author angles in DEGREES (rotation & sin/cos/atan2 are RADIANS)\noperators: + - * / % < > <= >= == != && || ! cond ? a : b\ncontext: time frame clock value · mouse.x mouse.y mouse.dx mouse.dy · keys.Space keys.ArrowLeft … (keys are 1/0, use directly: keys.Space ? … : …)\n time WRAPS every durationFrames (2.5 s by default) — clock is MONOTONE. Timestamps you capture and compare later MUST use clock\n (\\`when wrong { shown = clock }\\` + \\`opacity = pulse(shown, 4)\\`), or the ramp replays on every loop.\nName.x Name.y Name.rotation Name.scaleX Name.scaleY Name.opacity // any named object (identifier name), live on-screen value (read-only)\nself.x self.y self.rotation self.scaleX self.scaleY self.opacity // the object's own channels, in its bindings (no mirror variable)\n\n## Spaces (local vs world)\nself & channels (x, y, rotation…) = LOCAL (relative to parent — what x = … sets). Name.x, mouse.x = WORLD (the stage).\nAt the scene root, local = world (the common case → nothing to think about). If an object is NESTED in a group and you\nrelate it to a world position, convert: toLocalX(x, y) toLocalY(x, y) (world → your space) · toGlobalX/Y (your space → world).\nconstants: ${STD_CONSTANTS.join(' ')}\nfunctions: ${STD_FUNCTIONS.join(' ')}\n\n## Direct manipulation (interactors)\ndrag x, y // object follows the pointer while held → writes vars x, y (bind them: x = vx, y = vy)\n{ enabled <expr> } gates the GESTURE only — when/pressed/released/clicked STILL fire. Guard the body: when released { if done == 0 { … } }\ndragX vx · dragY vy // single-axis\ndrag x, y { confine to Zone snap 10 } // bound to a named object's box · grid snap\nturn a around cx,cy { snap 15 } // dial/knob → a = pivot→cursor angle in RADIANS (pair: rotation = a)\nturnDeg a around cx,cy { snap 15 } // same in DEGREES (pair: rotationDeg = a) · snap is degrees on both\ntrace p along Route { tolerance 30 step 40 } // follow a named guide → p = 0..1 by ARC LENGTH (pair: draw \"p\"). step = a TRACE, not a cursor: without it one press near the finish reports 1\nreveal c { brush 32 grain 8 erase cells grid } // scratch → c = cleared fraction; brush = finger, grain = resolution; erase = the runtime rubs the target out; grid[row*cols+col] = 1 says WHERE\nwhen dropped on Zone { … } // fires on release when the object's center is inside the named zone\n\n## Declarations\nvar name = 0 · var arr = fill(n, v) · var arr = [a, b, c] // document state: readable EVERYWHERE (bindings included)\nlet name = 0 // the same thing at the top level of a program; inside a scope it stays local\nfn name(a, b) = expr // value function (use in expressions)\nfn name() { … } // procedure (block of actions)\neach \"Symbol\" as i { opacity = data[i] } // bind every instance of a symbol (i = index)\nuse \"package\" packages: ${PACKAGES.join(' ')} // OPTIONAL: calling a package function imports it automatically\n\n## Example\nvar score = 0\nvar lit = 0\nevery frame { score = score + 1 }\nobject \"Ball\" {\n when clicked { lit = 1 }\n opacity = lit == 1 ? 1 : 0.4 // a binding READS the document's state\n rotation = atan2(Target.y - self.y, Target.x - self.x) // aim at another object by name\n}`\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// drawingCard.ts — TERSE reference of the DRAWING half of FlatInk, \"system-prompt\" sized.\n//\n// `languageCard` covers BEHAVIOR (events, channels, expressions) and not one word of the composition:\n// no shapes, no paints, no filters. An integrator handing that card to a model and asking for decor was\n// handing it a reference with nothing about drawing — so the model guessed, and what a model guesses in\n// a DSL does not compile. The two cards are complements: pair them (plus `docToManifest` for the names\n// of a particular scene) whenever a model has to produce a whole `.flatink`.\n//\n// Every ```flatink example below is COMPILED by drawingCard.test.ts. A reference that is copied drifts;\n// one that is compiled cannot. When the grammar moves, that test goes red rather than this card going\n// quietly wrong.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Drawing reference card (composition: shapes, paints, filters, text, clipping). */\nexport function drawingCard(): string {\n return `# FlatInk — drawing (the \\`scene { … }\\` half)\n\nThe unit is the LAYER: \\`layer \"name\" { … }\\`, stacked bottom to top. A layer holds shapes, text, images\nand groups (which nest their own layers). Coordinates are PIXELS in the parent's frame — never percentages.\n\n## Shapes\ncircle <cx> <cy> <r>\nellipse <cx> <cy> <rx> <ry>\nrect <x> <y> <w> <h> [<r>] // r = rounded corners (or <rx> <ry> for distinct)\npath \"M0 0 C40 -20 80 20 120 0 Z\" // raw SVG path data — total freedom\ncircle 100 100 40 as \"Ring\" // name it (right after the geometry) → addressable\n\n## Paint\nfill #rrggbb | #rrggbbaa · nofill\nfill linear(<angle>, 0:#…, 1:#…) // angle: 0 = →, 90 = ↓ ; stops are offset:color\nfill radial(<cx>, <cy>, <r>, 0:#…, 1:#…) // cx/cy/r as 0..1 of the box\nstroke <paint> <width> [cap butt|round|square] [join miter|round|bevel] [miter <n>] [dash a,b]\ndraw <0..1> [from <0..1>] // how much of the OUTLINE is stroked, by ARC LENGTH; \"quoted\" = per-frame expression\nopacity <0..1> // opacities MULTIPLY down the tree\n\n## Filters — on any item, no wrapper group needed\nfilter glow <blur> <color> · filter shadow <dx> <dy> <blur> <color>\nfilter blur <radius> · filter adjust <brightness> <contrast> <saturate> <hue>\n\n## Text\ntext \"…\" at <x>,<y> box <w> <h> font \"sans-serif\" size <n> align center line 1.2 color #… [bold] [italic] [wrap]\ntext \"…\" along \"<shapeId>\" align center start 0.5 // laid along a named shape's curve\nWord-wrap is OPT-IN: without \\`wrap\\`, only an explicit \\\\n breaks a line.\n\n## Images\nimage \"<assetId>\" <w> <h> at <x>,<y> // the asset is declared at the top: asset \"logo\" \"logo.svg\" image\n\n## Clipping\ngroup \"Name\" at x,y clip <x> <y> <w> <h> { … } // rectangular cut, in the group's LOCAL coordinates\nmask layer \"Name\" { <shapes> layer \"c\" { … } } // arbitrary shape: the mask's matter clips its child layers\n\n## The five rules that decide whether it compiles\n1. WORD ORDER IS FIXED: content → \\`as \"…\"\\` → \\`at …\\` → style. So \\`text \"Hi\" at 10,10 box 200 40\\`,\n NEVER \\`text \"Hi\" box 200 40 at 10,10\\`. A shape names itself right after its geometry.\n Stroke options (\\`cap\\`/\\`join\\`/\\`miter\\`/\\`dash\\`) belong to the STROKE and follow it directly:\n \\`nofill stroke #888 2 dash 6,5\\`, not \\`stroke #888 2 nofill dash 6,5\\`.\n2. A COMMENT STARTS WITH \\`//\\`. A \\`#\\` opens a COLOR.\n3. ONE STATEMENT PER LINE.\n4. Anything you intend to ANIMATE must be a \\`group \"Name\" at x,y { layer \"c\" { … } }\\`. An \\`object\\`\n block aimed at a shape or a layer is a compile ERROR — shapes are baked material and carry no pose.\n5. A group's children are positioned RELATIVE to it. Draw them around 0,0 and place the group.\n\n## Examples\n\nA sky and a ground, in two gradients:\n\n\\`\\`\\`flatink\nsize 960 540\nscene {\n layer \"sky\" {\n rect 0 0 960 540 fill linear(90, 0:#1b2a4a, 1:#4a6fa5)\n ellipse 780 90 70 70 fill radial(0.5, 0.5, 0.5, 0:#ffe9a8, 1:#ffe9a800)\n }\n layer \"ground\" {\n path \"M0 430 C240 400 420 460 960 415 L960 540 L0 540 Z\" fill #2e4b34 opacity 0.85\n }\n}\n\\`\\`\\`\n\nA line-art silhouette and a drop shadow:\n\n\\`\\`\\`flatink\nsize 200 400\nscene {\n layer \"tree\" {\n path \"M60 300 L60 180 M60 220 L20 170 M60 230 L100 180\" nofill stroke #23301f 8 cap round\n circle 60 150 60 fill #3f6b3a filter shadow 0 6 12 #00000055\n }\n}\n\\`\\`\\`\n\nA group — the only shape a behavior block can animate:\n\n\\`\\`\\`flatink\nsize 480 320\nscene {\n layer \"life\" {\n group \"Cloud\" at 240,120 {\n layer \"c\" {\n ellipse 0 0 70 26 fill #ffffff opacity 0.22\n ellipse 46 -10 44 22 fill #ffffff opacity 0.18\n }\n }\n }\n}\n\nobject \"Cloud\" {\n dx = 30 * sin(clock)\n}\n\\`\\`\\`\n\nA panel, text, and a clipped porthole:\n\n\\`\\`\\`flatink\nsize 960 540\nscene {\n layer \"panel\" {\n rect 40 40 300 90 24 fill #10141c99 stroke #ffffff33 2\n text \"Workshop\" at 60,66 box 260 40 font \"sans-serif\" size 26 color #f2f6ff\n group \"Porthole\" at 700,300 clip -60 -60 120 120 {\n layer \"c\" { circle 0 0 90 fill linear(0, 0:#0b3d5c, 1:#0f6f92) }\n }\n }\n}\n\\`\\`\\``\n}\n","// @flatkit/compiler -- the FlatInk language and compiler.\n//\n// Parses FlatInk Script (the .flatink DSL) and compiles a program plus its assets into a .flatpack:\n// a playable `Doc` (JSON, with the material already baked). Also ships the `flatc` CLI, via its `bin` -- NOT from this entry, which stays free of Node builtins.\n//\n// The language layer (DSL parser/printer, .flat format) lives in @flatkit/engine and is re-exported\n// here so the compiler package is a single coherent entry point.\n\n// --- Compile a program (+ assets) into a playable .flatpack Doc ---------------\nexport { compileFlatpack, packToJSON, type MediaMap } from './compile'\n\n// --- Check a program the way the CLI does (source in, diagnostics out) --------\nexport { checkProgram, programDiagnostics, formatDiagnostics, applyFixes, repairLoop, type CheckDiagnostic, type CheckOptions, type CheckResult } from './check'\nexport type { TextEdit } from '@flatkit/engine/dsl'\n\n// --- Static analysis: lint a program / a whole Doc ----------------------------\nexport { lint, lintReport, localVariables, type LintContext } from './lint'\nexport {\n lintDoc, lintDocReport, docHasErrors, docLintContext, allScopeVariables,\n scopeProgram, docStructureWarnings, docLayoutWarnings,\n} from './programDoc'\n\n// --- Manifest / LLM context for a Doc -----------------------------------------\nexport { manifestObjects, manifestEvents, docToManifest, llmContext, type ManifestObject } from './manifest'\n\n// --- The reference cards: behavior (languageCard) and composition (drawingCard) -\nexport { languageCard } from './languageCard'\nexport { drawingCard } from './drawingCard'\n\n// --- Scope-program helpers (split/join the per-object behavior blocks) ---------\nexport { splitScopeProgram, scopeRegions, formatObjectBlock, joinScopeProgram } from './scopeProgram'\n\n// --- The flatc CLI entry point (also wired as the `flatc` bin) -----------------\n// `run` (the CLI entry) is deliberately NOT re-exported. It lives in a module that imports `fs`/`path` at\n// the top, so re-exporting it put Node builtins in the root's chunk graph -- and a browser bundle then\n// fails at NAME RESOLUTION (`\"extname\" is not exported by \"__vite-browser-external\"`), before tree-shaking\n// can drop the unused symbol. The root's job is the pure helpers a service or a browser needs\n// (`checkProgram`, `languageCard`, `drawingCard`, `docToManifest`); the CLI's job is the `bin`, which\n// imports `./cli/flatc` directly. `scripts/check-pack.mjs` walks the built chunk graph to keep it that way.\n\n// --- The language layer, re-exported from the engine for convenience ----------\nexport { parseUnits, printUnits } from '@flatkit/engine/dsl'\nexport {\n parseProgram, printProgram, parseProgramFull, printProgramFull,\n parseFlat, parseFlatLib, exportFlatProject,\n} from '@flatkit/engine/flatFormat'\n"],"mappings":"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","names":["EXPR_CHANNELS","EXPR_CHANNELS"]}
1
+ {"version":3,"sources":["../src/manifest.ts","../src/languageCard.ts","../src/drawingCard.ts","../src/index.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// manifest.ts — \"map of the scene\" for an LLM (and tooling introspection).\n//\n// The language fits in < 2k tokens (see languageCard); the real context cost for an LLM\n// is knowing WHAT IT CAN NAME in THIS scene (objects, assets, variables, functions).\n// `docToManifest` derives this compact block from a Doc — the exact counterpart of references\n// by name (sceneRefs): the model then references only REAL names, and the linter catches the rest.\n//\n// Pure, derived (never stored). ~a few hundred tokens for a real scene.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Doc, Group, Image, Instance, ItemEvent, Layer, Text } from '@flatkit/types'\nimport { EXPR_CHANNELS, OFFSET_CHANNELS } from '@flatkit/engine/timeline'\nimport { getSymbol, isGroup, isImage, isInstance, isPoseable, isText } from '@flatkit/engine/layers'\nimport { analyzeExpr } from '@flatkit/engine/expr'\nimport { forEachAction, forEachActionExpression, forEachItemExpression } from './docWalk'\nimport { languageCard } from './languageCard'\nimport { drawingCard } from './drawingCard'\n\n/** Type label of a poseable (hence named) item. */\nfunction kindLabel(doc: Doc, it: Group | Instance | Text | Image): string {\n if (isInstance(it)) { const s = getSymbol(doc, it.symbolId); return s ? `Instance:${s.name}` : 'Instance' }\n if (isText(it)) return 'Text'\n if (isImage(it)) return 'Image'\n return 'Symbol' // Group (\"symbol\" in the UI)\n}\n\n/**\n * A named scene object and the CONTRACT the logic places on it — what a host must honour to reskin the\n * scene without touching its behavior. Deliberately free of coordinates: the names and the roles are the\n * contract, the composition stays entirely the skin's business. (A contract that carried positions would\n * hand every reskin the same layout, which is exactly the failure it exists to prevent.)\n */\nexport type ManifestObject = {\n name: string\n kind: string\n /** Item events the logic handles on it (`click`, `drop`…) — the skin must keep them reachable. */\n events: ItemEvent[]\n /** A `drag`/`turn` interactor targets it: the pointer writes its position, so the skin must not pin it. */\n dragged: boolean\n /** A placement target: something drops on it, or it carries a `hitbox`. Keep the name and a generous box. */\n zone: boolean\n /** Channels the logic drives (bindings and modifier targets) — a skin that sets these fights the logic. */\n channels: string[]\n /** Document variables it reads: the game state a skin can hang its own visuals on. */\n reads: string[]\n}\n\n/** The program's STATE: every variable a skin could meaningfully bind to. Wider than `doc.variables`,\n * which only holds the `var`-declared ones — a variable first assigned in a handler, or written by a\n * `drag` interactor, is just as real at runtime and is often the most interesting one to bind. */\nfunction stateVariables(doc: Doc): Set<string> {\n const out = new Set(Object.keys(doc.variables ?? {}))\n forEachAction(doc, (a) => {\n if (a.do === 'setVar' || a.do === 'setIndex') out.add(a.name)\n else if (a.do === 'repeatRange') out.add(a.var)\n })\n for (const i of doc.interactors ?? []) for (const v of [i.varX, i.varY, i.varT]) if (v) out.add(v)\n return out\n}\n\n/** State variables referenced by an expression — parsed, not grepped, so `open` never matches `reopen`\n * and a name nested inside a call still counts. An unparseable expression yields nothing (the linter\n * reports it; the manifest stays quiet). Runtime scalars (`time`, `mouse.x`) are not state and never\n * appear: they are not something a host can set. */\nfunction readsOf(expr: string, state: Set<string>, into: Set<string>): void {\n const a = analyzeExpr(expr)\n if (!a.ok) return\n for (const id of a.refs.ids) if (state.has(id)) into.add(id)\n}\n\n/** Named scene objects (groups included, library symbols excluded) with their binding contract; first\n * name wins, and nested groups are walked. */\nexport function manifestObjects(doc: Doc): ManifestObject[] {\n const out: ManifestObject[] = []\n const seen = new Set<string>()\n const state = stateVariables(doc)\n const dropZones = new Set((doc.interactions ?? []).filter((i) => i.event === 'drop' && i.over).map((i) => i.over as string))\n const walk = (layers: Layer[]) => {\n for (const l of layers) for (const it of l.items) {\n if (isPoseable(it) && it.name && !seen.has(it.name)) {\n seen.add(it.name)\n const mine = (doc.interactions ?? []).filter((i) => i.targetId === it.id)\n const channels: string[] = []\n const reads = new Set<string>()\n forEachItemExpression(it, (expr, channel) => { channels.push(channel); readsOf(expr, state, reads) })\n for (const i of mine) forEachActionExpression(i.actions, (expr) => readsOf(expr, state, reads))\n out.push({\n name: it.name,\n kind: kindLabel(doc, it),\n events: [...new Set(mine.map((i) => i.event))],\n dragged: !!doc.interactors?.some((i) => i.targetId === it.id),\n zone: dropZones.has(it.name) || ((isGroup(it) || isInstance(it)) && !!it.hitbox),\n channels,\n reads: [...reads],\n })\n }\n if (isGroup(it)) walk(it.layers)\n }\n }\n walk(doc.layers)\n return out\n}\n\n/** The events the program EMITS to its host (`send \"…\"`), in order of first appearance, deduped. The\n * other half of the contract: the moments a host — or a skin's motion pass — can react to. */\nexport function manifestEvents(doc: Doc): string[] {\n const events = new Set<string>()\n forEachAction(doc, (a) => { if (a.do === 'send') events.add(a.event) })\n return [...events]\n}\n\n/** One object's contract, as readable clauses. Empty = pure decor the skin owns outright. */\nfunction contractParts(o: ManifestObject): string[] {\n const parts: string[] = []\n if (o.dragged) parts.push('drag')\n if (o.zone) parts.push('zone')\n if (o.events.length) parts.push(`on ${o.events.join('/')}`)\n if (o.channels.length) parts.push(`driven: ${o.channels.join(' ')}`)\n if (o.reads.length) parts.push(`reads: ${o.reads.join(' ')}`)\n return parts\n}\n\n/** Variables → `name=value` (scalar) or `name[len]` (array). */\nfunction manifestVars(doc: Doc): string[] {\n return Object.entries(doc.variables ?? {}).map(([k, v]) => (Array.isArray(v) ? `${k}[${v.length}]` : `${k}=${v}`))\n}\n\n/**\n * Compact map of the scene (objects/assets/variables/functions/packages) — injectable in a prompt.\n * Only non-empty sections appear. The names are the ones the code can reference.\n */\nexport function docToManifest(doc: Doc): string {\n const objs = manifestObjects(doc)\n const assets = (doc.assets ?? []).map((a) => `${a.kind}:${a.id}`)\n const vars = manifestVars(doc)\n const funcs = (doc.functions ?? []).map((f) => `${f.name}(${f.params.join(', ')})`)\n const lines = ['# SCENE', `size: ${doc.width}x${doc.height}`]\n if (objs.length) lines.push(`objects: ${objs.map((o) => `${o.name}(${o.kind})`).join(', ')}`)\n if (assets.length) lines.push(`assets: ${assets.join(', ')}`)\n if (vars.length) lines.push(`vars: ${vars.join(', ')}`)\n if (funcs.length) lines.push(`funcs: ${funcs.join(', ')}`)\n if (doc.imports?.length) lines.push(`packages: ${doc.imports.join(', ')}`)\n // The binding contract, for the objects that carry one — what a skin must honour. No positions: the\n // composition is the skin's to invent, which is precisely what keeps two scenes from looking alike.\n const contract = objs.map((o) => ({ o, parts: contractParts(o) })).filter(({ parts }) => parts.length)\n if (contract.length) {\n lines.push('contract (honour these; the layout is yours):')\n for (const { o, parts } of contract) lines.push(` ${o.name} - ${parts.join(', ')}`)\n }\n const events = manifestEvents(doc)\n if (events.length) lines.push(`events: ${events.join(', ')}`)\n lines.push(`channels: ${EXPR_CHANNELS.join(' ')} (additive offsets: ${OFFSET_CHANNELS.join(' ')} -> pos = at + d)`)\n return lines.join('\\n')\n}\n\n/**\n * EVERYTHING a model needs to write a whole program: both language references (static) + the scene map\n * derived from the Doc. It is the bundle, not the map — if you only want the names this scene can\n * reference (because you already inject the references yourself), that is **`docToManifest(doc)`**, and\n * calling this instead ships the cards a second time.\n *\n * The DRAWING card is included by default. Handed the behavior card alone, a model asked for decor\n * invents a shapes grammar — and what it invents does not compile. Pass `{ drawing: false }` when the\n * model only edits behavior and the prompt budget is tight.\n */\nexport function llmContext(doc: Doc, opts: { drawing?: boolean } = {}): string {\n const cards = opts.drawing === false ? languageCard() : `${languageCard()}\\n\\n${drawingCard()}`\n return `${cards}\\n\\n${docToManifest(doc)}`\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// languageCard.ts — TERSE reference of FlatInk Script, \"system-prompt\" sized.\n//\n// Single source of truth to steer an LLM (or a human in a hurry): condensed\n// grammar + built-ins, ~1k tokens. The function/constant/channel lists are\n// INTERPOLATED from expr/timeline/stdlib → never out of sync with the real engine.\n// To be paired with `docToManifest` (manifest.ts) for the names specific to a scene.\n// ─────────────────────────────────────────────────────────────────────────────\nimport { STD_CONSTANTS, STD_FUNCTIONS } from '@flatkit/engine/expr'\nimport { EXPR_CHANNELS } from '@flatkit/engine/timeline'\nimport { PACKAGES } from '@flatkit/engine/stdlib'\n\n/** Language reference card (static, kept in sync with the engine via STD_*). */\nexport function languageCard(): string {\n return `# FlatInk Script — reference\nNumbers only (0 = false, anything else = true). No string type (only event/label/asset names in quotes). Comment: // to end of line.\nOne statement per line — a newline ends the statement (write \\`x = 1\\` then \\`y = 2\\` on separate lines, not \\`x = 1 y = 2\\`).\n\n## Objects\nobject \"Name\" { … } attaches behavior BY NAME. The name must be a group / instance / text / image — the only things that carry a pose.\nA SHAPE (\\`rect … as \"N\"\\`) or a LAYER cannot be animated: wrap the shape in \\`group \"N\" { layer \"art\" { … } }\\`. (Naming one is a compile error.)\nOpacities MULTIPLY down the tree — do not set \\`opacity 0\\` on the shape to hide it at rest, or the group's fade-in stays invisible.\n\n## Events (attach to an object or to the scene)\nwhen loaded { } // once, on start\nevery frame { } // every frame\nat frame N { } // when the playhead reaches frame N\nwhen clicked|hovered|unhovered|pressed|dragged|released|held { }\n\n## Actions\nplay · pause · go to frame N [and play|and pause] · go to \"label\"\nname = expr // set a variable\narr[i] = expr // write an array slot\narr = fill(n, v) // replace a WHOLE array (the only array-valued assignment; expressions are scalar)\nif cond { } else { } · repeat N times { } · repeat i from A to B { }\nmyProc() · send \"event\"[, expr | text(\"id\") | { a = expr, b … }] · sound \"assetId\"\n\n## Expressions (drive a channel, or compute in an action)\nchannel = expr channels: ${EXPR_CHANNELS.join(' ')} (expression wins over keyframes)\ndx = expr · dy = expr // ADDITIVE offset: final pos = at + (dx, dy) — oscillate AROUND the anchor (dx = 30*sin(time)); absolute x/y REPLACE at\nrotationDeg = expr // sugar for rotation = rad(expr) — author angles in DEGREES (rotation & sin/cos/atan2 are RADIANS)\noperators: + - * / % < > <= >= == != && || ! cond ? a : b\ncontext: time frame clock value · mouse.x mouse.y mouse.dx mouse.dy · keys.Space keys.ArrowLeft … (keys are 1/0, use directly: keys.Space ? … : …)\n time WRAPS every durationFrames (2.5 s by default) — clock is MONOTONE. Timestamps you capture and compare later MUST use clock\n (\\`when wrong { shown = clock }\\` + \\`opacity = pulse(shown, 4)\\`), or the ramp replays on every loop.\nName.x Name.y Name.rotation Name.scaleX Name.scaleY Name.opacity // any named object (identifier name), live on-screen value (read-only)\nself.x self.y self.rotation self.scaleX self.scaleY self.opacity // the object's own channels, in its bindings (no mirror variable)\n\n## Spaces (local vs world)\nself & channels (x, y, rotation…) = LOCAL (relative to parent — what x = … sets). Name.x, mouse.x = WORLD (the stage).\nAt the scene root, local = world (the common case → nothing to think about). If an object is NESTED in a group and you\nrelate it to a world position, convert: toLocalX(x, y) toLocalY(x, y) (world → your space) · toGlobalX/Y (your space → world).\nconstants: ${STD_CONSTANTS.join(' ')}\nfunctions: ${STD_FUNCTIONS.join(' ')}\n\n## Direct manipulation (interactors)\ndrag x, y // object follows the pointer while held → writes vars x, y (bind them: x = vx, y = vy)\n{ enabled <expr> } gates the GESTURE only — when/pressed/released/clicked STILL fire. Guard the body: when released { if done == 0 { … } }\ndragX vx · dragY vy // single-axis\ndrag x, y { confine to Zone snap 10 } // bound to a named object's box · grid snap\nturn a around cx,cy { snap 15 } // dial/knob → a = pivot→cursor angle in RADIANS (pair: rotation = a)\nturnDeg a around cx,cy { snap 15 } // same in DEGREES (pair: rotationDeg = a) · snap is degrees on both\ntrace p along Route { tolerance 30 step 40 } // follow a named guide → p = 0..1 by ARC LENGTH (pair: draw \"p\"). step = a TRACE, not a cursor: without it one press near the finish reports 1\nreveal c { brush 32 grain 8 erase cells grid } // scratch → c = cleared fraction; brush = finger, grain = resolution; erase = the runtime rubs the target out; grid[row*cols+col] = 1 says WHERE\nwhen dropped on Zone { … } // fires on release when the object's center is inside the named zone\n\n## Declarations\nvar name = 0 · var arr = fill(n, v) · var arr = [a, b, c] // document state: readable EVERYWHERE (bindings included)\nlet name = 0 // the same thing at the top level of a program; inside a scope it stays local\nfn name(a, b) = expr // value function (use in expressions)\nfn name() { … } // procedure (block of actions)\neach \"Symbol\" as i { opacity = data[i] } // bind every instance of a symbol (i = index)\nuse \"package\" packages: ${PACKAGES.join(' ')} // OPTIONAL: calling a package function imports it automatically\n\n## Example\nvar score = 0\nvar lit = 0\nevery frame { score = score + 1 }\nobject \"Ball\" {\n when clicked { lit = 1 }\n opacity = lit == 1 ? 1 : 0.4 // a binding READS the document's state\n rotation = atan2(Target.y - self.y, Target.x - self.x) // aim at another object by name\n}`\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// drawingCard.ts — TERSE reference of the DRAWING half of FlatInk, \"system-prompt\" sized.\n//\n// `languageCard` covers BEHAVIOR (events, channels, expressions) and not one word of the composition:\n// no shapes, no paints, no filters. An integrator handing that card to a model and asking for decor was\n// handing it a reference with nothing about drawing — so the model guessed, and what a model guesses in\n// a DSL does not compile. The two cards are complements: pair them (plus `docToManifest` for the names\n// of a particular scene) whenever a model has to produce a whole `.flatink`.\n//\n// Every ```flatink example below is COMPILED by drawingCard.test.ts. A reference that is copied drifts;\n// one that is compiled cannot. When the grammar moves, that test goes red rather than this card going\n// quietly wrong.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Drawing reference card (composition: shapes, paints, filters, text, clipping). */\nexport function drawingCard(): string {\n return `# FlatInk — drawing (the \\`scene { … }\\` half)\n\nThe unit is the LAYER: \\`layer \"name\" { … }\\`, stacked bottom to top. A layer holds shapes, text, images\nand groups (which nest their own layers). Coordinates are PIXELS in the parent's frame — never percentages.\n\n## Shapes\ncircle <cx> <cy> <r>\nellipse <cx> <cy> <rx> <ry>\nrect <x> <y> <w> <h> [<r>] // r = rounded corners (or <rx> <ry> for distinct)\npath \"M0 0 C40 -20 80 20 120 0 Z\" // raw SVG path data — total freedom; L/H/V are STRAIGHT lines\npath \"M0 0 L8 3 L15 9 …\" smooth // free-hand material / a sampled curve: gentle turns are rounded\ncircle 100 100 40 as \"Ring\" // name it (right after the geometry) → addressable\npolyline <xs> <ys> [count \"<expr>\"] [closed] // points = two ARRAY variables, read every frame (a trajectory, a computed curve)\n\n## Paint\nfill #rrggbb | #rrggbbaa · nofill\nfill linear(<angle>, 0:#…, 1:#…) // angle: 0 = →, 90 = ↓ ; stops are offset:color\nfill radial(<cx>, <cy>, <r>, 0:#…, 1:#…) // cx/cy/r as 0..1 of the box\nstroke <paint> <width> [cap butt|round|square] [join miter|round|bevel] [miter <n>] [dash a,b]\ndraw <0..1> [from <0..1>] // how much of the OUTLINE is stroked, by ARC LENGTH; \"quoted\" = per-frame expression\nopacity <0..1> // opacities MULTIPLY down the tree\n\n## Filters — on any item, no wrapper group needed\nfilter glow <blur> <color> · filter shadow <dx> <dy> <blur> <color>\nfilter blur <radius> · filter adjust <brightness> <contrast> <saturate> <hue>\n\n## Text\ntext \"…\" at <x>,<y> box <w> <h> font \"sans-serif\" size <n> align center line 1.2 color #… [bold] [italic] [wrap]\ntext \"…\" along \"<shapeId>\" align center start 0.5 // laid along a named shape's curve\nWord-wrap is OPT-IN: without \\`wrap\\`, only an explicit \\\\n breaks a line.\n\n## Images\nimage \"<assetId>\" <w> <h> at <x>,<y> // the asset is declared at the top: asset \"logo\" \"logo.svg\" image\n\n## Clipping\ngroup \"Name\" at x,y clip <x> <y> <w> <h> { … } // rectangular cut, in the group's LOCAL coordinates\nmask layer \"Name\" { <shapes> layer \"c\" { … } } // arbitrary shape: the mask's matter clips its child layers\n\n## The five rules that decide whether it compiles\n1. WORD ORDER IS FIXED: content → \\`as \"…\"\\` → \\`at …\\` → style. So \\`text \"Hi\" at 10,10 box 200 40\\`,\n NEVER \\`text \"Hi\" box 200 40 at 10,10\\`. A shape names itself right after its geometry.\n Stroke options (\\`cap\\`/\\`join\\`/\\`miter\\`/\\`dash\\`) belong to the STROKE and follow it directly:\n \\`nofill stroke #888 2 dash 6,5\\`, not \\`stroke #888 2 nofill dash 6,5\\`.\n2. A COMMENT STARTS WITH \\`//\\`. A \\`#\\` opens a COLOR.\n3. ONE STATEMENT PER LINE.\n4. Anything you intend to ANIMATE must be a \\`group \"Name\" at x,y { layer \"c\" { … } }\\`. An \\`object\\`\n block aimed at a shape or a layer is a compile ERROR — shapes are baked material and carry no pose.\n5. A group's children are positioned RELATIVE to it. Draw them around 0,0 and place the group.\n\n## Examples\n\nA sky and a ground, in two gradients:\n\n\\`\\`\\`flatink\nsize 960 540\nscene {\n layer \"sky\" {\n rect 0 0 960 540 fill linear(90, 0:#1b2a4a, 1:#4a6fa5)\n ellipse 780 90 70 70 fill radial(0.5, 0.5, 0.5, 0:#ffe9a8, 1:#ffe9a800)\n }\n layer \"ground\" {\n path \"M0 430 C240 400 420 460 960 415 L960 540 L0 540 Z\" fill #2e4b34 opacity 0.85\n }\n}\n\\`\\`\\`\n\nA line-art silhouette and a drop shadow:\n\n\\`\\`\\`flatink\nsize 200 400\nscene {\n layer \"tree\" {\n path \"M60 300 L60 180 M60 220 L20 170 M60 230 L100 180\" nofill stroke #23301f 8 cap round\n circle 60 150 60 fill #3f6b3a filter shadow 0 6 12 #00000055\n }\n}\n\\`\\`\\`\n\nA group — the only shape a behavior block can animate:\n\n\\`\\`\\`flatink\nsize 480 320\nscene {\n layer \"life\" {\n group \"Cloud\" at 240,120 {\n layer \"c\" {\n ellipse 0 0 70 26 fill #ffffff opacity 0.22\n ellipse 46 -10 44 22 fill #ffffff opacity 0.18\n }\n }\n }\n}\n\nobject \"Cloud\" {\n dx = 30 * sin(clock)\n}\n\\`\\`\\`\n\nA panel, text, and a clipped porthole:\n\n\\`\\`\\`flatink\nsize 960 540\nscene {\n layer \"panel\" {\n rect 40 40 300 90 24 fill #10141c99 stroke #ffffff33 2\n text \"Workshop\" at 60,66 box 260 40 font \"sans-serif\" size 26 color #f2f6ff\n group \"Porthole\" at 700,300 clip -60 -60 120 120 {\n layer \"c\" { circle 0 0 90 fill linear(0, 0:#0b3d5c, 1:#0f6f92) }\n }\n }\n}\n\\`\\`\\``\n}\n","// @flatkit/compiler -- the FlatInk language and compiler.\n//\n// Parses FlatInk Script (the .flatink DSL) and compiles a program plus its assets into a .flatpack:\n// a playable `Doc` (JSON, with the material already baked). Also ships the `flatc` CLI, via its `bin` -- NOT from this entry, which stays free of Node builtins.\n//\n// The language layer (DSL parser/printer, .flat format) lives in @flatkit/engine and is re-exported\n// here so the compiler package is a single coherent entry point.\n\n// --- Compile a program (+ assets) into a playable .flatpack Doc ---------------\nexport { compileFlatpack, packToJSON, type MediaMap } from './compile'\n\n// --- Check a program the way the CLI does (source in, diagnostics out) --------\nexport { checkProgram, programDiagnostics, formatDiagnostics, applyFixes, repairLoop, type CheckDiagnostic, type CheckOptions, type CheckResult } from './check'\nexport type { TextEdit } from '@flatkit/engine/dsl'\n\n// --- Static analysis: lint a program / a whole Doc ----------------------------\nexport { lint, lintReport, localVariables, type LintContext } from './lint'\nexport {\n lintDoc, lintDocReport, docHasErrors, docLintContext, allScopeVariables,\n scopeProgram, docStructureWarnings, docLayoutWarnings,\n} from './programDoc'\n\n// --- Manifest / LLM context for a Doc -----------------------------------------\nexport { manifestObjects, manifestEvents, docToManifest, llmContext, type ManifestObject } from './manifest'\n\n// --- The reference cards: behavior (languageCard) and composition (drawingCard) -\nexport { languageCard } from './languageCard'\nexport { drawingCard } from './drawingCard'\n\n// --- Scope-program helpers (split/join the per-object behavior blocks) ---------\nexport { splitScopeProgram, scopeRegions, formatObjectBlock, joinScopeProgram } from './scopeProgram'\n\n// --- The flatc CLI entry point (also wired as the `flatc` bin) -----------------\n// `run` (the CLI entry) is deliberately NOT re-exported. It lives in a module that imports `fs`/`path` at\n// the top, so re-exporting it put Node builtins in the root's chunk graph -- and a browser bundle then\n// fails at NAME RESOLUTION (`\"extname\" is not exported by \"__vite-browser-external\"`), before tree-shaking\n// can drop the unused symbol. The root's job is the pure helpers a service or a browser needs\n// (`checkProgram`, `languageCard`, `drawingCard`, `docToManifest`); the CLI's job is the `bin`, which\n// imports `./cli/flatc` directly. `scripts/check-pack.mjs` walks the built chunk graph to keep it that way.\n\n// --- The language layer, re-exported from the engine for convenience ----------\nexport { parseUnits, printUnits } from '@flatkit/engine/dsl'\nexport {\n parseProgram, printProgram, parseProgramFull, printProgramFull,\n parseFlat, parseFlatLib, exportFlatProject,\n} from '@flatkit/engine/flatFormat'\n"],"mappings":"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","names":["EXPR_CHANNELS","EXPR_CHANNELS"]}
@@ -303,18 +303,23 @@ symbol "Boat" {
303
303
  ```
304
304
 
305
305
  - `params { <type> <name> = <default> [range <min> <max>] ["doc"] … }` — `<type>` is `color`, `number`,
306
- or `bool`. The default, range, and doc string make the interface self-describing.
306
+ `bool` or `text`. The default, range, and doc string make the interface self-describing.
307
307
  - **`color` params** are used as a paint — `fill hull`, `stroke hull <width>`, a **gradient stop**
308
308
  (`0:hull@0.8`, optional `@alpha`), or a **`tint hull <amount>`** (anywhere a `#color` literal goes).
309
309
  Resolved per instance at render; *not* available in numeric expressions.
310
+ - **`text` params** are what a text says — `text libelle at …` (the bare name instead of a quoted
311
+ string) draws the instance's value: a reusable button carries its own label. Declared with a quoted
312
+ default: `text libelle = "OK" "label of the button"`.
310
313
  - **`number` / `bool` params** become **variables in the symbol's expressions** (`wave`, `flag`). `bool`
311
314
  reads as `1`/`0`. (`flatc --check` knows them — reading a declared param in an `expr` is not an "unknown
312
315
  variable".)
313
316
 
314
317
  > The `timeline`, `params`, and `states` header blocks may appear in **any order** before the layers.
315
318
 
316
- Set params at the instance **call-site** (literals), in `--preview`, or — for `number`/`bool` — at
317
- runtime (`Boat.wave = 1.5`, see below):
319
+ Set params at the instance **call-site** (literals), in `--preview`, or at runtime (see below):
320
+ `Boat.wave = 1.5` for a `number`/`bool` (any expression), `Boat.hull = #33aa33` for a `color` (a color
321
+ literal), `Bouton.libelle = "Bravo"` for a `text` (a quoted text). `flatc --check` reports a param the
322
+ symbol does not declare, a value of the wrong type or out of its range, and a state that does not exist.
318
323
 
319
324
  ```
320
325
  instance "Boat" as "Hero" at center { hull = #1a5f3a, wave = 1.5, flag = false }
@@ -154,7 +154,8 @@ object "Piece" {
154
154
  target index is the exception: it resolves to `0` — "no target reached" — on a gated-off release, so it
155
155
  can never hand you the previous gesture's answer.)
156
156
  - **Drop zones**: by default the object's **center** is tested against the zone; `at pointer` tests the
157
- pointer instead. Define an explicit rectangle with `group "Zone" … hitbox <w> <h> { … }`.
157
+ pointer instead. Define an explicit rectangle with `group "Zone" … hitbox <w> <h> { … }` — it is also
158
+ where the object is touched (click, press, drag, hover) when nothing drawn inside it is hit first.
158
159
  - Several `when dropped on` per object are evaluated in declaration order (the right-zone / wrong-zones pattern).
159
160
  - **`match` sugar** factors the whole drag+drop boilerplate — see [factoring](#reuse--factoring).
160
161
 
@@ -393,9 +394,33 @@ object "Card" {
393
394
  }
394
395
  ```
395
396
 
397
+ ## Keyboard access (`focusable`)
398
+
399
+ An object that declares `focusable` can be reached and used **without a pointer**:
400
+
401
+ ```
402
+ object "Validate" {
403
+ focusable order 3 # order is optional: ranked objects first (lower first), then the others in document order
404
+ when clicked { check() } # Enter or Space on the focused object fires this too
405
+ }
406
+ object "Hint" {
407
+ focusable noring # no default ring: this object draws its own…
408
+ opacity = self.focused ? 1 : 0.7 # …from `self.focused` (1 while it holds the focus)
409
+ }
410
+ ```
411
+
412
+ - **Tab / Shift+Tab** walk the focusable objects; **Enter** and **Space** click the focused one (its
413
+ `when clicked`). An object that is not shown (opacity 0, or absent from the picture) is skipped.
414
+ - The player draws a **focus ring** around the focused object — around its `hitbox` when it has one.
415
+ `noring` leaves the drawing to the scene.
416
+ - The focus is a stop, not a trap: past the last object (or before the first), Tab goes back to the page.
417
+ - Using the pointer drops the keyboard focus, like `:focus-visible` on a web page.
418
+ - Under `flatc --play`, `{ "type": "key", "name": "Tab" }` moves the focus and `"Enter"` clicks, so a
419
+ keyboard path is tested like any other.
420
+
396
421
  ## Feedback
397
422
 
398
- An object can read **its own interaction state** in channel expressions: `self.hovered`, `self.grabbed`,
423
+ An object can read **its own interaction state** in channel expressions: `self.focused`, `self.hovered`, `self.grabbed`,
399
424
  `self.pressed` (each `0`/`1`). So hover-lift and grab-squash are just expressions — no mirror variable,
400
425
  no handler:
401
426
 
@@ -114,7 +114,9 @@
114
114
  (local, on the roster item) = the center of rotation/scale; `at x,y` (on the pose) = where the local
115
115
  origin lands. `spin cw|ccw` / `turns N` also turn around the pivot.
116
116
  - **`expr rotation` is in RADIANS**, like `sin`/`cos`. Stay in degrees with the helpers: `rad(45)`,
117
- `turns(time)` (one turn per second), `deg(r)`. e.g. `expr rotation "turns(time * 0.5)"`.
117
+ `turns(time)` (one turn per second), `deg(r)`. e.g. `expr rotation "turns(time * 0.5)"`. Or write
118
+ `expr rotationDeg "<degrees>"`, the degree twin (as in a behavior block). Any other channel name is a
119
+ compile error.
118
120
  - **`time` WRAPS at `durationFrames`** (the timeline loops), so `sin(time * f)` with an arbitrary `f`
119
121
  **jumps** every loop — and a `.flatink` with no `timeline` defaults to **60 frames (2.5 s @24fps)**, so
120
122
  the jump is frequent. For free-running ambiance use **`clock`** (monotone, never wraps): `sin(clock * f)`.
@@ -170,8 +172,16 @@
170
172
  - **Drop semantics**: by default the **object's center** (its x/y channels) is tested against
171
173
  the zone. Two levers to match human expectations:
172
174
  - `when dropped on Zone at pointer { … }`: tests the **POINTER position** (not the center).
173
- - `group "Zone" … hitbox <W> <H> { … }`: an **explicit drop rectangle** (centered on the
174
- origin, ±W/2 × ±H/2) instead of the content bbox. Replaces invisible `#ffffff01` paths.
175
+ - `group "Zone" … hitbox <W> <H> { … }`: an **explicit rectangle** (centered on the
176
+ origin, ±W/2 × ±H/2) instead of the content bbox. Replaces invisible `#ffffff01` paths. Works on an
177
+ `instance` too.
178
+ - **`hitbox` is also where the object is TOUCHED** — clicked, pressed, dragged, hovered — whenever
179
+ nothing drawn inside it is hit first. Use it for a stroke-only ring (only the stroke is touched
180
+ otherwise, not the middle), a small handle that deserves a finger-sized target, or an empty group laid
181
+ over a picture. The rectangle follows the object. What is drawn ABOVE it keeps the pointer.
182
+ - **What lets the pointer through**: `nohit`, a hidden item, and anything whose `opacity` is `0.01` or
183
+ less — with or without a `hitbox`. An invisible touch area is an empty group with a `hitbox`, at full
184
+ opacity: there is nothing to draw.
175
185
  - **Locking a placed object**: `drag x, y { enabled <expr> }`. The drag is active only while
176
186
  the expression is ≠ 0. No more `x = (p==1) ? Zone.x : xv` + `if p==0` guard patterns.
177
187
  - **Event order on release**: `when released` fires **BEFORE** the drop test (useful to lower a
@@ -225,9 +235,9 @@ Inside an `object "Name" { … }`, besides `drag x, y` / `dragX` / `dragY`:
225
235
  See [Behavior](behavior-and-interactions.md#seeing-where-it-was-scratched-reveal--cells).
226
236
  - A `reveal` target is grabbable **over its whole zone**, whatever its content looks like — so a veil
227
237
  stays scratchable where its cells have already gone to `opacity 0`. (Everywhere else, an item at
228
- `opacity 0` lets the pointer through; a group whose children are ALL invisible is not hit, and
229
- `hitbox` does not change that — it is the drop-zone rectangle, not a hit surface. Only a replayed
230
- `down/move/up` script shows this class of bug: a static render looks perfect.)
238
+ `opacity 0` lets the pointer through; a group whose children are ALL invisible is not hit unless
239
+ it declares a `hitbox`. Only a replayed `down/move/up` script shows this class of bug: a static
240
+ render looks perfect.)
231
241
  - **`link <endX>, <endY>, <target> to <TargetsGroup> [{ enabled <expr> }]`**: pull an elastic
232
242
  thread toward a target. During the drag, `<endX>`/`<endY>` = pointer position (DRAW the
233
243
  thread yourself with expressions, e.g. a region connecting the object to `endX,endY`). On
@@ -50,9 +50,10 @@ per second), `deg(r)` (the inverse, for readouts). Or bind the **`rotationDeg`**
50
50
  | `frame` | current frame (0-based; also wraps at `durationFrames`) |
51
51
  | `value` | the channel's current value (in a channel binding) |
52
52
  | `mouse.x` `mouse.y` | pointer position (scene units) |
53
- | `keys.<Key>` | `1` while a key is held, `0` otherwise — `<Key>` is the browser `KeyboardEvent.key` value (`keys.ArrowRight`, `keys.a`, `keys.Escape`), plus the alias `keys.Space` for the space bar. Naming a key here also makes the player **consume** it (no page scroll) — see [host integration](host-integration.md#keyboard) |
53
+ | `random()` | a number in `[0, 1[`, a new one at each call. Reproducible when the player is given a `seed` (always the case under `flatc --play`, see `--seed`) — see [host integration](host-integration.md) |
54
+ | `keys.<Key>` | `1` while a key is held, `0` otherwise — `<Key>` is the browser `KeyboardEvent.key` value (`keys.ArrowRight`, `keys.a`, `keys.Escape`), plus the alias `keys.Space` for the space bar, **or** the physical key, `KeyboardEvent.code` (`keys.ShiftLeft`, `keys.Digit1`, `keys.Numpad1`, `keys.KeyA`) — the way to tell the two Shift keys apart and to read the digit row. Naming a key here also makes the player **consume** it (no page scroll) — see [host integration](host-integration.md#keyboard) |
54
55
  | `self.x` `self.y` `self.scaleX` … | the object's own current pose (in its channel bindings) |
55
- | `self.hovered` `self.grabbed` `self.pressed` | the object's own interaction state (`0`/`1`) — see [feedback](behavior-and-interactions.md#feedback) |
56
+ | `self.hovered` `self.grabbed` `self.pressed` `self.focused` | the object's own interaction state (`0`/`1`; `focused` = it holds the keyboard focus, see `focusable`) — see [feedback](behavior-and-interactions.md#feedback) |
56
57
  | `<Name>.x` `<Name>.y` … | a named object's live channels (e.g. `Target.x`) |
57
58
 
58
59
  ## Arrays
@@ -25,11 +25,26 @@ const player = new FlatPlayer(canvas, doc, {
25
25
  | `autoplay` | `false` | starts the timeline on mount |
26
26
  | `loop` | `true` | loops the timeline |
27
27
  | `padding` | `0` | margin around the page, in CSS px |
28
- | `audio` | `true` | `false` mutes `sound "…"` and audio tracks |
28
+ | `audio` | `true` | `false` mutes `sound "…"` and audio tracks. Sounds are decoded when the document loads, so the first one plays on time |
29
29
  | `input` | `true` | `false` = non-interactive preview: it animates but ignores pointer **and keyboard** |
30
30
  | `render` | `true` | `false` = headless (logic + `send`s only, no Canvas API needed) |
31
31
  | `resolveAsset` | embedded only | maps an asset to a URL. Default: embedded `data:` URIs only — see [Security](#security) |
32
32
  | `onEvent` | — | called on every `send` |
33
+ | `focusRing` | `true` | the ring drawn around the object that holds the keyboard focus (`focusable`). `false` = none, or `{ color, width }` |
34
+ | `seed` | none | seed of `random()`: the scene then draws the same numbers on every run. Absent, it draws from `Math.random` |
35
+ | `maxPixelRatio` | none | upper bound on the device pixel ratio the canvas is sized with. The backing store grows with the square of the ratio; on a 3x phone, `2` trades a little sharpness for a much cheaper frame |
36
+
37
+ ### When the player paints
38
+
39
+ While it plays, the player repaints a frame **only when the picture can have changed**: a variable an
40
+ expression reads was written, a param or a state moved, a spring is still settling, the pointer or a key
41
+ changed, an image or a font finished loading. A scene at rest costs no paint at all. A scene whose
42
+ expressions read `time`, `clock`, `frame` or `random()`, or whose keyframes play with the playhead, is
43
+ repainted every frame as before.
44
+
45
+ Everything the player exposes paints on its own (`setVar`, `setKey`, `seek`, `load`…). If the host changes
46
+ what is drawn behind the player's back — it mutates the document it handed over, say — it calls
47
+ `player.render()`.
33
48
 
34
49
  ## Receiving events (`send` → `onEvent`)
35
50
 
@@ -124,6 +139,28 @@ place, and `render()` forces a repaint (useful after a late font settles).
124
139
  `KeyboardEvent.key` value — `keys.ArrowRight`, `keys.a`, `keys.Escape` — plus one alias: the space bar
125
140
  (`key === ' '`) is also exposed as **`keys.Space`**.
126
141
 
142
+ A physical key answers to its **`KeyboardEvent.code`** as well: `keys.ShiftLeft` and `keys.ShiftRight` are
143
+ two keys (both are `keys.Shift`), the digit row is `keys.Digit1`…`keys.Digit0` (its `key` is `"1"`, which
144
+ an expression cannot spell), the keypad `keys.Numpad1`, a letter wherever the layout puts it `keys.KeyA`.
145
+
146
+ ### Keyboard focus
147
+
148
+ A scene whose objects declare `focusable` makes its canvas a **stop in the page's tab order** (the player
149
+ sets `tabindex="0"` on it unless you set one). Reached with Tab, the first object takes the focus (the last
150
+ one when the page came backwards); Tab then walks the objects, Enter and Space click the focused one, and
151
+ past either end the key is left to the page. The player handles Tab only while the canvas has the page's
152
+ focus, so it never interferes with the rest of your page.
153
+
154
+ - `focusRing` option: `false` to draw no ring (the scene uses `self.focused`), or `{ color, width }`.
155
+ - `player.focused` is the name of the focused object (or `null`); `player.focusNext(1 | -1)` moves the
156
+ focus from the host (it returns `false` when there is nothing left that way).
157
+ - The canvas keeps the browser's own focus outline: style it as you would any focusable element.
158
+
159
+ ### Pointers
160
+
161
+ Each pointer has its own gesture: two fingers can press, hold and drag two objects at once, and lifting
162
+ one releases only what it was holding. `mouse.x` / `mouse.y` follow the pointer that moved last.
163
+
127
164
  The listeners are attached to the **window** (a scene reacts immediately, with no click-to-focus step),
128
165
  but the player is a good citizen about it — you should not have to do anything:
129
166
 
@@ -24,8 +24,15 @@ ellipse <cx> <cy> <rx> <ry>
24
24
  rect <x> <y> <w> <h> # · <r> for uniform rounded corners · <rx> <ry> for distinct
25
25
  path "M0 0 L10 0 L10 10 Z" # raw SVG path data
26
26
  circle 100 100 40 as "Ring" # name a shape (right after the geometry) → addressable, e.g. text `along "Ring"`
27
+ path "M0 0 L8 3 L15 9 …" smooth # free-hand material: rounded wherever the outline turns gently
27
28
  ```
28
29
 
30
+ A `path` means what its data says: `L`, `H` and `V` are **straight lines**, next to a curve or on their own
31
+ — a hexagon has six flat sides. **`smooth`**, right after the path data, reads the path as **free-hand
32
+ material** instead: its points are joined by a curve that rounds every gentle turn (under 60°) and keeps
33
+ the sharp ones. It is what the editor's brush exports, and what a curve sampled as many small `L` steps
34
+ wants. `flatc --check` points at a long run of points with gentle turns that does not say `smooth`.
35
+
29
36
  ### Fill, stroke, opacity
30
37
 
31
38
  ```
@@ -35,6 +42,37 @@ path "…" nofill stroke #888 2 # outline only
35
42
  rect 0 0 40 40 fill #00aaff opacity 0.5 # 0..1 (8-digit hex alpha also works)
36
43
  ```
37
44
 
45
+ ### A shape computed while the scene runs (`polyline`)
46
+
47
+ `polyline` is a shape whose points are **two array variables**, read every frame — a trajectory, a curve as
48
+ it is being computed, a polygon the learner deforms:
49
+
50
+ ```
51
+ var tx = fill(200, 0)
52
+ var ty = fill(200, 0)
53
+ var n = 0
54
+ scene {
55
+ layer "c" {
56
+ polyline tx ty count "n" nofill stroke #cc3333 3 cap round # the first n points, as a line
57
+ polyline px py closed fill #3366cc # all the points, as a filled polygon
58
+ }
59
+ }
60
+ every frame {
61
+ if n < 200 {
62
+ tx[n] = 20 + n * 2
63
+ ty[n] = 90 - 60 * sin(n / 10)
64
+ n = n + 1
65
+ }
66
+ }
67
+ ```
68
+
69
+ - `polyline <xs> <ys>` takes the **names** of the two arrays. `count <n>` or `count "<expr>"` keeps the
70
+ first points only (never past the arrays); `closed` joins the last point to the first.
71
+ - The segments are **straight**. Fill, stroke, `opacity`, `draw` and `nohit` are those of any shape.
72
+ - The arrays are the truth: write them from `every frame` or a handler, and the line follows. A scene that
73
+ stops writing them stops being repainted. `flatc --check` reports a name that is not a declared array.
74
+ - Fewer than two points draw nothing.
75
+
38
76
  ### Drawing a stroke progressively (`draw`)
39
77
 
40
78
  `draw` sets **how much of an outline is stroked**, as a fraction of its **arc length** — ink that appears
package/docs/tooling.md CHANGED
@@ -233,6 +233,11 @@ flatc <file> --play --script gestures.json [--trace]
233
233
  ```
234
234
 
235
235
  - `drag` / `tap` / `scratch` (sweeps a `reveal` zone) / `connect` (pulls a `link` wire) — by name.
236
+ `tap` also takes a point instead of a name — `{ "type": "tap", "x": 120, "y": 80 }` — for a rail or
237
+ an area that has no name.
238
+ - Audio is off in `--play`: a `sound` action is a silent no-op, so a program is replayed as written.
239
+ - `random()` is seeded in `--play` (seed `1`), so a replay says the same thing twice and an `expect` can
240
+ assert on a draw. `--seed N` picks another one.
236
241
  - **`turn`** rotates a `turn`/`turnDeg` target by `angle` (degrees for `turnDeg`, radians for `turn`),
237
242
  swept in sub-steps so a multi-turn rotation lands. It presses the object where the engine finds it, i.e.
238
243
  on **whatever is topmost there** — two clock hands overlapping at noon give the gesture to the one on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flatkit/compiler",
3
- "version": "0.35.3",
3
+ "version": "0.36.0",
4
4
  "description": "The FlatInk language (parser + AST) and compiler (.flatink → .flatpack). Ships the flatc CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Zwyk Studio",
@@ -57,9 +57,9 @@
57
57
  "docs"
58
58
  ],
59
59
  "dependencies": {
60
- "@flatkit/engine": "0.35.3",
61
- "@flatkit/player": "0.35.3",
62
- "@flatkit/types": "0.35.3"
60
+ "@flatkit/engine": "0.36.0",
61
+ "@flatkit/player": "0.36.0",
62
+ "@flatkit/types": "0.36.0"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "skia-canvas": "^3.0.8"
@@ -252,7 +252,8 @@ State & helpers:
252
252
  var x = 0 var arr = [0,0,0] var z = fill(8, 0) // runtime state (arrays via fill)
253
253
  fn dist(ax,ay,bx,by) = hypot(ax-bx, ay-by) // value fn
254
254
  fn reset() { score = 0 go to frame 0 } // procedure fn
255
- self.hovered self.grabbed self.pressed // own interaction state (0/1)
255
+ self.hovered self.grabbed self.pressed self.focused // own interaction state (0/1)
256
+ focusable [order <n>] [noring] // in an object block: Tab reaches it, Enter/Space fire `when clicked`, the player rings it
256
257
  feedback lift tilt dim shake(<expr>) // one-liner reactions (auto use "feedback")
257
258
  ```
258
259
 
@@ -323,7 +324,7 @@ ramp over `dur` s for a readable timed feedback — capture the instant with **`
323
324
  13. **`$()` is for compile-time interpolation in scene coords**; runtime expressions use bare
324
325
  identifiers and `[]` indexing. Arrays must exist before indexed write (`var hx = fill(n,0)`).
325
326
  14. **Drop test = object center** by default; use `when dropped on Zone at pointer` for the pointer, or
326
- `group "Zone" … hitbox W H { … }` for an explicit rectangle. `when released` fires BEFORE the drop test.
327
+ `group "Zone" … hitbox W H { … }` for an explicit rectangle (also the object's TOUCH area: a stroke-only ring is clicked in its middle only with a `hitbox`). `when released` fires BEFORE the drop test.
327
328
  15. **`reveal`/`trace` progress is monotone** (never decreases). `link` works in WORLD coords.
328
329
  16. **Stroke width scales with the group** (drawn in scaled space) — don't compensate by hand.
329
330
  17. **All rotation is radians; author degrees with the `*Deg` twins.** The `rotation` channel,
@@ -87,7 +87,8 @@ link endX,endY,target to <Group> // target = hit index 1..n (0=none), WORLD
87
87
  // link draws NO thread. A bar drawn from its own origin, then:
88
88
  // rotation = angle(srcX, srcY, endX, endY) scaleX = dist(srcX, srcY, endX, endY) / <drawn length>
89
89
  ```
90
- Self-state & feedback: `self.hovered self.grabbed self.pressed` (0/1) ·
90
+ Keyboard: `focusable [order n]` in an `object` block → Tab reaches it, Enter/Space fire its `when clicked`.
91
+ Self-state & feedback: `self.hovered self.grabbed self.pressed self.focused` (0/1) ·
91
92
  `feedback lift tilt dim shake(<expr>)`.
92
93
  State/funcs: `var a = 0` · `var arr = fill(8,0)` (also as an ASSIGNMENT: `arr = fill(n, 0)` blanks a grid) · `fn dist(ax,ay,bx,by) = hypot(ax-bx,ay-by)` ·
93
94
  `fn reset() { score = 0 }`.
@@ -69,7 +69,9 @@ symbol "Boat" {
69
69
  }
70
70
  }
71
71
  ```
72
- - Types: `color`, `number` (`number wave = 1 range 0 2 "…"`), `bool`.
72
+ - Types: `color`, `number` (`number wave = 1 range 0 2 "…"`), `bool`, `text` (`text label = "OK" "…"`).
73
+ - A `text` param is drawn with `text label at …` (the bare name instead of a quoted string): a button
74
+ carries its own label, set per instance with `{ label = "Play" }`.
73
75
  - `color` params go anywhere a `#color` literal goes (`fill hull`, `stroke hull 3`).
74
76
  - Preview a restyle: `flatc --preview Boat.flat --render --set hull=#1a5f3a -o boat.png`.
75
77