@flatkit/compiler 0.23.0 → 0.24.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/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, 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 { languageCard } from './languageCard'\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\nexport type ManifestObject = { name: string; kind: string }\n\n/** Named scene objects (groups included, library symbols excluded), first name wins. */\nexport function manifestObjects(doc: Doc): ManifestObject[] {\n const out: ManifestObject[] = []\n const seen = new Set<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)) { seen.add(it.name); out.push({ name: it.name, kind: kindLabel(doc, it) }) }\n if (isGroup(it)) walk(it.layers)\n }\n }\n walk(doc.layers)\n return out\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 lines.push(`channels: ${EXPR_CHANNELS.join(' ')} (additive offsets: ${OFFSET_CHANNELS.join(' ')} -> pos = at + d)`)\n return lines.join('\\n')\n}\n\n/** Full LLM context: language reference (static) + scene map (derived from the Doc). */\nexport function llmContext(doc: Doc): string {\n return `${languageCard()}\\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\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\nwhen dropped on Zone { … } // fires on release when the object's center is inside the named zone\n\n## Declarations\nlet name = 0 · let arr = fill(n, v) · let arr = [a, b, c]\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\nlet score = 0\nevery frame { score = score + 1 }\nobject \"Ball\" {\n when clicked { score = score + 1 }\n rotation = atan2(Target.y - self.y, Target.x - self.x) // aim at another object by name\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 (`run`).\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// --- 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, docToManifest, llmContext, type ManifestObject } from './manifest'\n\n// --- The language reference card ----------------------------------------------\nexport { languageCard } from './languageCard'\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) -----------------\nexport { run } from './cli/flatc'\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) && !!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\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\nwhen dropped on Zone { … } // fires on release when the object's center is inside the named zone\n\n## Declarations\nlet name = 0 · let arr = fill(n, v) · let arr = [a, b, c]\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\nlet score = 0\nevery frame { score = score + 1 }\nobject \"Ball\" {\n when clicked { score = score + 1 }\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]\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 (`run`).\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, type CheckDiagnostic, type CheckOptions, type CheckResult } from './check'\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) -----------------\nexport { run } from './cli/flatc'\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flatkit/compiler",
3
- "version": "0.23.0",
3
+ "version": "0.24.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",
@@ -26,15 +26,18 @@
26
26
  "exports": {
27
27
  ".": {
28
28
  "types": "./dist/index.d.ts",
29
- "import": "./dist/index.js"
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.js"
30
31
  },
31
32
  "./compile": {
32
33
  "types": "./dist/compile.d.ts",
33
- "import": "./dist/compile.js"
34
+ "import": "./dist/compile.js",
35
+ "require": "./dist/compile.js"
34
36
  },
35
37
  "./analysis": {
36
38
  "types": "./dist/analysis.d.ts",
37
- "import": "./dist/analysis.js"
39
+ "import": "./dist/analysis.js",
40
+ "require": "./dist/analysis.js"
38
41
  }
39
42
  },
40
43
  "bin": {
@@ -42,12 +45,13 @@
42
45
  },
43
46
  "files": [
44
47
  "dist",
45
- "bin"
48
+ "bin",
49
+ "prompts"
46
50
  ],
47
51
  "dependencies": {
48
- "@flatkit/engine": "0.23.0",
49
- "@flatkit/player": "0.23.0",
50
- "@flatkit/types": "0.23.0"
52
+ "@flatkit/engine": "0.24.0",
53
+ "@flatkit/player": "0.24.0",
54
+ "@flatkit/types": "0.24.0"
51
55
  },
52
56
  "peerDependencies": {
53
57
  "skia-canvas": "^3.0.8"
@@ -0,0 +1,45 @@
1
+ # FlatInk prompts — teaching the language to a model
2
+
3
+ These files ship inside `@flatkit/compiler`, so an integrator never has to copy the grammar by hand. A
4
+ copied reference drifts from the language, and the drift is paid in DSL that the end user's compiler
5
+ rejects. Read them from the installed package:
6
+
7
+ ```js
8
+ import { readFileSync } from 'node:fs'
9
+ const core = readFileSync(new URL('../prompts/flatink-core.md', import.meta.resolve('@flatkit/compiler')), 'utf8')
10
+ ```
11
+
12
+ In a browser (or when you want something smaller and always in sync with the engine), prefer the
13
+ generated cards instead — they interpolate the real function/channel lists and are a fraction of the size:
14
+
15
+ ```js
16
+ import { languageCard, drawingCard, llmContext } from '@flatkit/compiler'
17
+
18
+ llmContext(doc) // behavior card + drawing card + the names of THIS scene
19
+ languageCard() // behavior only: events, channels, expressions
20
+ drawingCard() // composition only: shapes, paints, filters, text, clipping
21
+ ```
22
+
23
+ ## What each file is for
24
+
25
+ | File | Use it when |
26
+ |---|---|
27
+ | `flatink-core.md` | the model writes a whole `.flatink` or `.flat` from scratch — the full reference |
28
+ | `flatink-lite.md` | the task is small and the prompt budget is tight — the same language, condensed |
29
+ | `role-asset-creator.md` | the model draws a **symbol library** (`.flat`): shapes, gradients, filters |
30
+ | `role-motion-designer.md` | the model animates: timelines, cels, tweens, channel expressions |
31
+ | `role-coder.md` | the model writes behavior: events, variables, interactions, `send` |
32
+
33
+ Pair a role file with `flatink-core.md`: the role sets the job, the core sets the grammar.
34
+
35
+ ## Validating what comes back
36
+
37
+ Never ship generated DSL unchecked. `checkProgram` runs exactly what `flatc --check` runs, on a string,
38
+ with no subprocess — and its report is a serviceable repair prompt:
39
+
40
+ ```js
41
+ import { checkProgram } from '@flatkit/compiler'
42
+
43
+ const { ok, report } = checkProgram(srcFromTheModel)
44
+ if (!ok) retry(report)
45
+ ```
@@ -0,0 +1,273 @@
1
+ # FlatInk — generation reference (core)
2
+
3
+ You generate **FlatInk** source: a small text language for animations and interactive scenes that
4
+ compile to a single playable `.flatpack`. Output **only valid source code** in a fenced block, nothing
5
+ else, unless the user asks for explanation. Author can verify with `flatc <file> --check`.
6
+
7
+ ## Two file types — pick the right one
8
+
9
+ | Type | Holds | Top-level shape |
10
+ |---|---|---|
11
+ | `.flat` | a **symbol library** (reusable visual assets, animated or not). Not playable on its own. | one or more `symbol "Name" { … }` |
12
+ | `.flatink` | a **program**: a scene + behavior. Playable. | `size W H` then `scene { … }` then behavior |
13
+
14
+ A `.flatink` is split in **two halves with different grammars**:
15
+ - **`scene { … }`** = composition (what you see): `layer`, shapes, `text`, `image`, `group`, `instance`.
16
+ - **behavior** (everything after `scene`) = logic: `object "Name" { … }`, `every frame`, `var`, `fn`.
17
+
18
+ ## `.flatink` skeleton
19
+
20
+ ```
21
+ size 480 320 # REQUIRED, must be the very first line (canvas units)
22
+ background #0a0e1c # optional
23
+ timeline 30 300 # optional root timeline: fps, duration(frames). default 24 fps / 60 frames
24
+ use "collision" # optional stdlib/local packages
25
+ asset "logo" "logo.svg" image # optional media declarations
26
+ var score = 0 # optional global state
27
+
28
+ scene {
29
+ layer "bg" { rect 0 0 480 320 fill #0a0e1c }
30
+ layer "game" {
31
+ circle 240 160 40 fill #ffcc00 as "Sun"
32
+ }
33
+ }
34
+
35
+ object "Sun" {
36
+ when clicked { score = score + 1 }
37
+ rotation = time * 30
38
+ }
39
+ every frame { if (score >= 10) { send "win" } }
40
+ ```
41
+
42
+ ## Drawing (inside `scene`/`symbol` layers)
43
+
44
+ Coordinates are plain numbers; canvas origin is **top-left**. Layers stack bottom-to-top.
45
+
46
+ ```
47
+ circle <cx> <cy> <r>
48
+ ellipse <cx> <cy> <rx> <ry>
49
+ rect <x> <y> <w> <h> [<r> | <rx> <ry>] # optional rounded corners
50
+ path "M0 0 L10 0 L10 10 Z" # raw SVG path data
51
+ text "Hi" font "sans-serif" size 24 align center line 1.2 color #fff box 200 40
52
+ image "logo" 80 80 at -40,-40 # origin = top-left → center with at -w/2,-h/2
53
+ group "Name" at x,y pivot px,py { layer "c" { … } } # nests its own layers
54
+ instance "Symbol" as "Name" at x,y # place a symbol from a .flat lib
55
+ ```
56
+
57
+ Paint / style (work on shapes; most on text & groups too):
58
+ ```
59
+ fill #rrggbb | nofill
60
+ stroke #rrggbb <width> [cap butt|round|square] [join …] [miter n] [dash a,b,…]
61
+ opacity 0..1 # also 8-digit hex alpha #rrggbbaa
62
+ fill linear(90, 0:#bdecff, 1:#2f8fe0) # angle 0 = →, 90 = ↓ ; stops offset:color
63
+ fill radial(0.5, 0.5, 0.5, 0:#fff, 1:#000) # cx, cy, r (0..1), then stops
64
+ filter glow <blur> <color> | shadow <dx> <dy> <blur> <color> | blur <r> | adjust <b> <c> <s> <h>
65
+ tint <color> <amount(0..1)> # Flash-style tint
66
+ nohit # drawn but ignored by hit-test
67
+ ```
68
+
69
+ ## Animation — timeline / cel / pose (in a `symbol`)
70
+
71
+ A symbol owns a **timeline**; an animated **layer** is a track of **cels** (keyframes). Each cel lists
72
+ the **poses** of containers declared once in the layer roster, plus (optionally) the layer's **matter**
73
+ — the drawing at that key.
74
+
75
+ ```
76
+ symbol "Wheel" {
77
+ timeline 24 24 # fps, durationFrames
78
+ layer "spin" {
79
+ group "Rim" at 100,100 pivot 0,0 { # roster: declared ONCE, posed by the cels below
80
+ layer "art" { circle 0 0 40 nofill stroke #333 8 }
81
+ }
82
+ cel 0 tween { pose "Rim" rotate 0 }
83
+ cel 24 { pose "Rim" rotate 360 } # one full turn around the pivot, in DEGREES
84
+ }
85
+ }
86
+
87
+ pose "Name" [at x,y] [rotate <deg>] [scale s | scaleX sx scaleY sy]
88
+ [opacity o] [tint #c amt] [spin cw|ccw] [turns n] [filter …]
89
+ ```
90
+
91
+ - `cel N tween { … }` interpolates this cel → the next; without `tween` the cel **holds**.
92
+ - `ease linear|easeIn|easeOut|easeInOut|cubic(a,b,c,d)` on a cel.
93
+ - `pose` units are **human**: degrees, multipliers, around the group's **`pivot`**.
94
+ - A `pose` is a **patch**: it only overrides channels it names (keeps declared position/scale/etc.).
95
+
96
+ ### Frame-by-frame — one DRAWING per cel (`matter`)
97
+
98
+ A cel can carry the layer's drawing in a `matter { … }` block. A new one per cel = classic cel animation:
99
+
100
+ ```
101
+ layer "draw" {
102
+ cel 0 { matter { circle 0 0 30 fill #e33 } }
103
+ cel 1 { matter { rect -30 -30 60 60 fill #3a3 } }
104
+ cel 2 { matter { path "M -30 30 L 0 -30 L 30 30 Z" fill #33e } }
105
+ }
106
+ ```
107
+ - The matter **holds** until the next cel defining one (write a drawing once, not once per frame);
108
+ `morph` on the cel tweens its **shape** toward the next key instead of cutting.
109
+ - A cel may carry both `matter { … }` and `pose "…"` (matter draws behind the posed containers).
110
+ - Same thing with existing objects: put each drawing in a roster `group` (or `image`) and pose only the
111
+ one wanted on each cel — an unposed container disappears.
112
+
113
+ ## Channel expressions (behavior, every frame)
114
+
115
+ Bind a named item's channel to an expression. Absolute channels: `x y scaleX scaleY rotation opacity`.
116
+ Additive position offsets: `dx dy` → **`pos = at + (dx, dy)`** (binding-only; no keyframe/spring form).
117
+
118
+ ```
119
+ object "Needle" {
120
+ rotation = atan2(mouse.y - 160, mouse.x - 240) # RADIANS
121
+ opacity = lit ? 1 : 0.3
122
+ dx = 30 * sin(clock) # sways AROUND its declared at — no base to re-inject
123
+ }
124
+ ```
125
+ Or bind a channel on a symbol container directly: `group "Fan" pivot 0,0 expr rotation "turns(time)" { … }`.
126
+
127
+ **Stateful easing — `spring` / `smooth`.** A channel that chases its target with inertia instead of
128
+ snapping to it. Per instance, zero cost when unused, and it snaps to the target on a seek (so tune it by
129
+ PLAYING the preview, not by scrubbing).
130
+ ```
131
+ group "Cable" spring rotation "hookX" stiffness 0.08 damping 0.86 { … } # in a .flat: overshoot, settle
132
+ group "Panel" smooth y "target" k 0.15 { … } # 1st order: no overshoot
133
+ object "Dial" { spring rotation = aim { stiffness 0.08 damping 0.86 } } # scene-side form, in .flatink
134
+ ```
135
+ The quoted target's names must EXIST in that scope (a symbol `param`, a scene `var`) or `--check` errors.
136
+
137
+ ## Behavior — events, actions, interaction
138
+
139
+ Events (inside `object`): `when clicked | hovered | unhovered | pressed | released | dragged | held |
140
+ dropped on <Zone> [at pointer]`. Scene-wide: `when loaded`, `every frame`, `at frame <n>`.
141
+
142
+ Actions (one per line): `<var> = <expr>` · `arr[i] = <expr>` · `if/else if/else` ·
143
+ `repeat <n> times { }` · `repeat i from a to b { }` · `play`/`pause` · `go to frame N [and play]` ·
144
+ `go to "label" [and play]` · `send "evt" [, <expr> | , text("id") | , { a = <expr>, b }]` · `sound "id"` · `<fn>(args)`.
145
+
146
+ Drag & interactors (each writes into your vars; all accept `{ enabled <expr> }`):
147
+ ```
148
+ drag x, y [{ confine to <Zone> · snap <grid> · enabled <expr> }] # dragX / dragY too
149
+ turn <angle> around <x>,<y> [{ snap <deg> }] # → <angle> in RADIANS → rotation = <angle> directly
150
+ turnDeg <angle> around <x>,<y> [{ snap <deg> }] # → <angle> in DEGREES → pair with rotationDeg = <angle>
151
+ trace <progress> along <Group> [{ tolerance <px> }]# follow a path → 0..1 monotone
152
+ reveal <progress> [{ brush <px> }] # scratch/wipe → 0..1 cumulative
153
+ link <endX>,<endY>,<target> to <Group> # elastic thread → target = hit index 1..n (0=none)
154
+ ```
155
+
156
+ State & helpers:
157
+ ```
158
+ var x = 0 var arr = [0,0,0] var z = fill(8, 0) # runtime state (arrays via fill)
159
+ fn dist(ax,ay,bx,by) = hypot(ax-bx, ay-by) # value fn
160
+ fn reset() { score = 0 go to frame 0 } # procedure fn
161
+ self.hovered self.grabbed self.pressed # own interaction state (0/1)
162
+ feedback lift tilt dim shake(<expr>) # one-liner reactions (auto use "feedback")
163
+ ```
164
+
165
+ ## Factoring (compile-time, zero runtime cost)
166
+
167
+ ```
168
+ def gap = 70 # compile-time constant, used via $()
169
+ repeat i from 0 to 4 { circle $(40 + i*gap) 80 6 fill #ffd98a } # $(expr) = compile-time arithmetic
170
+ symbol "Card"(label, tint = "#fff") { … text "$(label)" … fill $(tint) … } # parameterized symbol
171
+ instance "Card"($(i+1)) as "C$(i)" at $(80 + i*90),200
172
+ each "Key" as i { when clicked { input = input*10 + (i+1) } } # shared behavior over instances
173
+ match Word1, Word2 onto Good, Bad { # declarative drag+drop pairing
174
+ correct Word1 -> Good, Word2 -> Bad
175
+ on done { send "win" }
176
+ }
177
+ at center | at center,540 | at 120,center # canvas-relative anchor
178
+ align top of "Bin" [offset dx,dy] # pin origin onto another item's bbox
179
+ ```
180
+
181
+ ## Expressions & stdlib
182
+
183
+ Pure & numeric (no booleans: comparisons/logic yield `1`/`0`). Operators: `?: || && == != < > <= >=
184
+ + - * / % - ! . [] fn()`.
185
+ Built-ins: `sin cos tan asin acos atan atan2 abs sqrt pow exp log floor ceil round sign min max hypot
186
+ clamp(x,lo,hi) lerp(a,b,t) mod(a,b) between(x,lo,hi) rad(deg) deg(rad) turns(n)`. Constants `PI TAU E`.
187
+ Reserved: `time` (seconds, **wraps** every `durationFrames`), `clock` (seconds, **monotone**), `frame`,
188
+ `value`, `mouse.x/y`, `keys.<Key>`, `self.*`, `<Name>.*`.
189
+ Packages: `use "collision" | "easing" | "gesture" | "feedback"`; functions are available bare and
190
+ qualified (`collision.boxHit(…)`). **The `use` line is optional** — calling a package function imports its
191
+ package automatically (your own `fn` of the same name still wins). Timing: `lerp(v, target, k)` (builtin)
192
+ eases toward a target each frame (`niv = lerp(niv, target, 0.1)`); `feedback.pulse(since, dur)` is a 1→0
193
+ ramp over `dur` s for a readable timed feedback — capture the instant with **`clock`**:
194
+ `var shown = -999` + `when wrong { shown = clock }`, `opacity = pulse(shown, 4)`.
195
+
196
+ ## CRITICAL GOTCHAS — do not get these wrong
197
+
198
+ 1. **`size W H` is required and MUST be the first line** of a `.flatink`. A `.flat` has no `size`.
199
+ 2. **Two grammars.** Drawing keywords live in `scene`/`symbol` layers; logic keywords live in
200
+ `object`/`every frame`/`fn`. Don't mix (no `var`/`when` inside `scene`; no `circle` inside `object`).
201
+ 3. **One action per line** in handlers. `x = 1 y = 2` is an error.
202
+ 4. **Angles split by context:** `pose rotate`/`scale` are **degrees & multipliers**. The
203
+ **`rotation` channel and `expr rotation`, plus `sin/cos/atan2`, are RADIANS.** Convert with
204
+ `rad(deg)`, `turns(n)` (1 turn/sec), `deg(rad)`.
205
+ 5. **Pivot or it orbits.** Rotation/scale happen around the container's `pivot` (local coords, default
206
+ `0,0`). Off-origin art with no pivot **orbits** instead of spinning in place.
207
+ 6. **A pose is a patch**, not a replace — it keeps every channel it doesn't mention. Don't re-state
208
+ `at x,y` just to change `opacity`.
209
+ 7. **A cel is a full snapshot.** A container is shown only on cels that `pose` it; omit it and it
210
+ **disappears** (that's how exits work). Keep a static element on its **own cel-less layer**, or use
211
+ `cel N hold { … }` to carry unchanged poses forward. It's per-**keyframe**, not per-frame.
212
+ 8. **A layer WITH cels draws ONLY the cel's `matter` + the containers that cel poses.** A bare shape
213
+ written straight into such a layer is **silently never drawn** — put it inside `cel N { matter { … } }`
214
+ (that IS frame-by-frame), or on a **cel-less layer** if it's static. Render order: the `matter` draws
215
+ **behind** the posed containers, declaration order is NOT preserved — to put a static shape in front of
216
+ the animation give it its **own layer above**. `flatc --check` warns on the silent drops.
217
+ 9. **`image` origin is top-left** → center with `at -w/2,-h/2`; and `as "Name"` comes **before** `at x,y`
218
+ (`image "id" w h as "F1" at -50,-50`) — the reverse order is a parse error.
219
+ 10. **Text doesn't wrap** unless you add `wrap` (only explicit `\n` breaks otherwise).
220
+ 11. **Rings/holes = ONE path with multiple closed subpaths** (fill is even-odd); a nested subpath cuts
221
+ a hole. `stroke`, `opacity`, and `filter` all exist on `path` and `text` — don't fake them.
222
+ 12. **`def`/`repeat`/`$()`/parameterized symbols are compile-time** (vanish from the model). For values
223
+ that change at runtime use `var`. A symbol param body sees only its params (not an outer `repeat`'s `i`).
224
+ A `def` is **not** a runtime variable: don't use it as a bare identifier in a behavior expression —
225
+ inject it with `$(name)` (`angle($(hubx), $(huby), mouse.x, mouse.y)`) or use a literal.
226
+ 13. **`$()` is for compile-time interpolation in scene coords**; runtime expressions use bare
227
+ identifiers and `[]` indexing. Arrays must exist before indexed write (`var hx = fill(n,0)`).
228
+ 14. **Drop test = object center** by default; use `when dropped on Zone at pointer` for the pointer, or
229
+ `group "Zone" … hitbox W H { … }` for an explicit rectangle. `when released` fires BEFORE the drop test.
230
+ 15. **`reveal`/`trace` progress is monotone** (never decreases). `link` works in WORLD coords.
231
+ 16. **Stroke width scales with the group** (drawn in scaled space) — don't compensate by hand.
232
+ 17. **All rotation is radians; author degrees with the `*Deg` twins.** The `rotation` channel,
233
+ `expr rotation`, `sin/cos/atan2`, `gesture.angle`, AND the **`turn`** interactor are **radians** — so
234
+ wire directly: `rotation = angle(self.x, self.y, mouse.x, mouse.y)`, or `turn a around cx,cy` then
235
+ `rotation = a`. To author in **degrees**, bind **`rotationDeg = <expr>`** (sugar for
236
+ `rotation = rad(<expr>)`) and use the **`turnDeg`** interactor (writes degrees). `snap` is always degrees.
237
+ 18. **Tweens move position straight but rotation in an ARC around the pivot.** Unwanted sideways drift
238
+ during a vertical move (a "sinking boat" sliding right) means either the two cels' `at x` differ, or
239
+ a `rotate` is arcing around an **off-center pivot**. Fix: keep the pivot at the visual center, match
240
+ `at x` across the cels, and descend with `at y` rather than a large tilt.
241
+ 19. **`object "X"` must name a group / instance / text / image — those alone carry a pose.** A shape *can*
242
+ take `as "N"` (it makes it addressable, e.g. `text … along "N"`) but it is baked material and can never
243
+ be animated; a LAYER can't either. Naming one is a **compile error** — to drive a shape, wrap it:
244
+ `group "N" { layer "art" { rect … } }`. Note that **opacities MULTIPLY**: putting `opacity 0` on the
245
+ shape to hide it at rest cancels the group's fade-in. `as "…"` and `at x,y` must come **right after the
246
+ geometry / content**, before style attributes (`font`/`box`/`fill`…): `text "…" at x,y box W H` works,
247
+ `text "…" box W H at x,y` fails. (`group`/`instance` take `at`/`as` normally.)
248
+ 20. **`x`/`y` REPLACE the group's `at` — they are not deltas.** `object "G" { x = bump }` overwrites the
249
+ local x of a group declared `at X,Y` (x snaps to `bump` ≈ 0 → it jumps to the edge). To move
250
+ **around** the anchor, bind the additive offsets instead: **`dx = bump`** (`pos = at + (dx, dy)`),
251
+ which needs no base and survives a re-layout. Re-injecting the base (`x = $(X) + bump`) is only for
252
+ when you genuinely want an absolute position. This is the most common "anim in the wrong place."
253
+ 21. **Parameterized symbols are `.flatink`-inline only.** `symbol "X"(args)` lives in the program, not in a
254
+ `.flat` lib (libs hold non-parameterized symbols, instanced without parens). Parens ⇔ parameterized.
255
+ A `(…)` symbol in a `.flat` (incl. one `flatc` auto-discovers in the folder) errors `"{" expected, "("`.
256
+ 22. **`{ enabled <expr> }` gates the GESTURE, not the handlers.** Once it is off the object stops being
257
+ draggable, but `when pressed` / `released` / `clicked` **still fire**. Guard the body yourself:
258
+ `when released { if done == 0 { … } }`. (A `link`'s target index is the exception — it resolves to 0.)
259
+ 23. **Timestamps use `clock`, never `time`.** `time` resets every `durationFrames` (2.5 s by default), so a
260
+ one-shot ramp captured on `time` REPLAYS for ever and a `shake` SKIPS on every loop. Set a long
261
+ `timeline` only if you need `time` itself; for instants, `clock` is the answer.
262
+ 24. **Check the program as `.flatink`.** The extension decides how a file is read: a `.flat` is a symbol
263
+ library, so a program saved under that name has nothing checked. `flatc` refuses it now. In a working
264
+ folder, `--no-libs` stops it pulling in the neighbouring `.flat` scratch files.
265
+
266
+ ## Self-check before returning
267
+ - `.flatink` starts with `size`; keywords are in the correct half (scene vs behavior).
268
+ - Every named target referenced by behavior (`object "X"`, drop zones, `align of`) exists in the scene
269
+ **and is a group / instance / text / image** — never a bare shape or a layer.
270
+ - Every captured instant compared later uses `clock`, not `time`.
271
+ - Every handler on an object whose gesture is `{ enabled … }`-gated guards its own body.
272
+ - Pivots set for anything that rotates/scales in place; radians vs degrees correct per context.
273
+ - Containers that persist are on cel-less layers or use `hold`; no accidental disappearance.
@@ -0,0 +1,132 @@
1
+ # FlatInk — lite reference (no tools)
2
+
3
+ You write **FlatInk**, a text language for animations & interactive scenes. Output **only valid source**
4
+ in one fenced block. You won't have a compiler — get it right in one pass. Keep DSL keywords in English.
5
+
6
+ ## Two file types
7
+ - **`.flat`** = a symbol library: one or more `symbol "Name" { … }`. **No `size` line.** Not playable alone.
8
+ - **`.flatink`** = a program: `size W H` (first line, required) → `scene { … }` → behavior. Playable.
9
+
10
+ A `.flatink` has **two halves with different grammars**: `scene { … }` = composition (shapes/text/image/
11
+ group/instance); everything after = behavior (`object`, `every frame`, `var`, `fn`). Never mix them.
12
+
13
+ ## `.flatink` skeleton
14
+ ```
15
+ size 480 320 # REQUIRED first line (canvas units; origin = top-left)
16
+ background #0a0e1c # optional
17
+ var score = 0 # optional global runtime state
18
+ scene {
19
+ layer "bg" { rect 0 0 480 320 fill #0a0e1c } # layers stack bottom → top
20
+ layer "game" { circle 240 160 40 fill #ffcc00 as "Sun" } # `as` names an item for behavior
21
+ }
22
+ object "Sun" { # behavior attaches by name
23
+ when clicked { score = score + 1 }
24
+ rotation = time * 30 # channel binding, every frame (RADIANS)
25
+ }
26
+ every frame { if (score >= 10) { send "win" } }
27
+ ```
28
+
29
+ ## Drawing (in scene/symbol layers)
30
+ ```
31
+ circle cx cy r · ellipse cx cy rx ry · rect x y w h [r | rx ry] · path "M0 0 L10 0 L10 10 Z"
32
+ text "Hi" font "sans-serif" size 24 align center line 1.2 color #fff box 200 40 [bold] [italic] [wrap]
33
+ image "id" w h at -w/2,-h/2 # origin top-left → center yourself ; needs: asset "id" "f.png" image
34
+ group "Name" at x,y pivot px,py { layer "c" { … } } # nests its own layers
35
+ instance "Symbol" as "Name" at x,y # place a symbol from a .flat
36
+ ```
37
+ Style: `fill #rrggbb | nofill` · `stroke #rgb <w> [cap round][join round][dash a,b]` · `opacity 0..1` ·
38
+ `fill linear(90, 0:#a, 1:#b)` (0=→,90=↓) · `fill radial(0.5,0.5,0.5, 0:#fff,1:#000)` ·
39
+ `filter glow <blur> <color> | shadow <dx> <dy> <blur> <color> | blur <r>` · `tint <color> <amt>` · `nohit`.
40
+
41
+ ## Animation (in a symbol): timeline / cel / pose
42
+ ```
43
+ symbol "Wheel" {
44
+ timeline 24 24 # fps, durationFrames (loops [0,dur))
45
+ layer "spin" {
46
+ group "Rim" at 100,100 pivot 0,0 { # roster: declared ONCE, posed by cels below
47
+ layer "art" { circle 0 0 40 nofill stroke #333 8 }
48
+ }
49
+ cel 0 tween { pose "Rim" rotate 0 } # tween = interpolate to next cel; no tween = hold
50
+ cel 24 { pose "Rim" rotate 360 } # DEGREES, around the pivot
51
+ }
52
+ }
53
+ pose "Name" [at x,y] [rotate deg] [scale s | scaleX sx scaleY sy] [opacity o] [spin cw|ccw] [turns n]
54
+ ```
55
+ - `ease linear|easeIn|easeOut|easeInOut|cubic(a,b,c,d)` on a cel.
56
+ - States: `states door { closed at 0 open at 24 initial closed transition 12 ease easeInOut }`
57
+ → param drives the playhead (`door=0`→f0, `door=0.5`→f12). Driven by `Name.door = open`.
58
+ - Expr channel on a container: `group "Fan" pivot 0,0 expr rotation "turns(time)" { … }` (RADIANS).
59
+
60
+ ## Behavior
61
+ Events (in `object`): `when clicked | hovered | unhovered | pressed | released | dragged | held |
62
+ dropped on <Zone> [at pointer]`. Scene-wide: `when loaded`, `every frame`, `at frame n`.
63
+ Actions (one per line): `<var> = <expr>` · `arr[i] = <expr>` · `if/else if/else` · `repeat n times {}` ·
64
+ `repeat i from a to b {}` · `play`/`pause` · `go to frame n [and play]` · `send "evt" [, <expr> | , text("id") | , { a = <expr>, b }]` · `sound "id"`.
65
+ Drag/interactors (write into your vars; all take `{ enabled <expr> }`):
66
+ ```
67
+ drag x, y [{ confine to <Zone> · snap <grid> }] # then USE them: x = px y = py
68
+ turn <angle> around x,y · trace <progress> along <Group> · reveal <progress>
69
+ link endX,endY,target to <Group> # target = hit index 1..n (0=none), WORLD coords
70
+ ```
71
+ Self-state & feedback: `self.hovered self.grabbed self.pressed` (0/1) ·
72
+ `feedback lift tilt dim shake(<expr>)`.
73
+ State/funcs: `var a = 0` · `var arr = fill(8,0)` · `fn dist(ax,ay,bx,by) = hypot(ax-bx,ay-by)` ·
74
+ `fn reset() { score = 0 }`.
75
+
76
+ ## Factoring (compile-time, vanish from model — use `var` for runtime)
77
+ `def gap = 70` · `repeat i from 0 to 4 { circle $(40 + i*gap) 80 6 fill #fff }` (`$()` = compile-time math)
78
+ · `symbol "Card"(label, tint="#fff") { … text "$(label)" … fill $(tint) }` · `instance "Card"($(i+1)) as "C$(i)" at …`
79
+ · `each "Key" as i { when clicked { … } }` · `at center` · `align top of "Bin" [offset dx,dy]`.
80
+
81
+ ## Expressions
82
+ Pure numeric, no booleans (compare/logic → 1/0). Ops: `?: || && == != < > <= >= + - * / % - ! . [] fn()`.
83
+ Funcs: `sin cos tan atan2 abs sqrt pow floor ceil round sign min max hypot clamp(x,lo,hi) lerp(a,b,t)
84
+ mod(a,b) between(x,lo,hi) rad(deg) deg(rad) turns(n)`. Const `PI TAU E`.
85
+ Reserved: `time`(s, **wraps**) `clock`(s, **monotone**) `frame` `value` `mouse.x/y` `keys.<Key>` `self.*` `<Name>.*`.
86
+ Channels: `x y scaleX scaleY rotation opacity` (absolute) + `dx dy` (additive: `pos = at + (dx, dy)`).
87
+ Stateful easing: `spring <ch> "<target>" stiffness <0..1> damping <0..1>` · `smooth <ch> "<target>" k <0..1>`
88
+ (on a symbol container; scene-side: `spring rotation = aim { stiffness 0.08 damping 0.86 }` in an `object`).
89
+
90
+ ## CRITICAL GOTCHAS
91
+ 1. **`size W H` = first line of a `.flatink`** (none in `.flat`). Two grammars: drawing in `scene`, logic
92
+ after. Never put `var`/`when` in `scene` or `circle`/`path` in `object`.
93
+ 2. **One action per line.** A `send` carries ≤1 payload (`send "evt"` / `, <expr>` / `, text("id")` / `, { a = <expr>, b }` = named numbers).
94
+ 3. **Degrees vs radians:** `pose rotate`/`scale` = degrees & multipliers. The `rotation` channel,
95
+ `expr rotation`, `sin/cos/atan2`, `gesture.angle`, and the **`turn`** interactor = **RADIANS** → wire
96
+ directly (`rotation = angle(...)`, or `turn a around c` + `rotation = a`). To author in degrees: bind
97
+ **`rotationDeg`** (sugar for `rotation = rad(…)`) and use **`turnDeg`**. Helpers: `rad()`/`deg()`/`turns()`.
98
+ 4. **Set a `pivot` or it ORBITS** instead of spinning in place (pivot = local center of rotation/scale).
99
+ Same in a tween: position lerps straight, rotation ARCS around the pivot — a `rotate` on an off-center
100
+ pivot makes a sinking object drift sideways (match `at x` across cels; keep the pivot centered).
101
+ 5. **A pose is a PATCH** — keeps every channel it doesn't name. Don't restate `at x,y` to change opacity.
102
+ 6. **A cel is a full snapshot:** a container shows only on cels that `pose` it (omit → it disappears).
103
+ Per-**keyframe**, not per-frame. Keep statics on a **cel-less layer**, or carry forward with `cel N hold {…}`.
104
+ 7. **Render order in an animated layer:** static `path`s draw **behind** posed containers (declaration
105
+ order not preserved). To put a static shape in front, give it its **own layer above**.
106
+ 8. **`image` origin = top-left** (center with `at -w/2,-h/2`). **Text doesn't wrap** without `wrap`.
107
+ 9. **Rings/holes = ONE path, multiple closed subpaths** (even-odd fill). `stroke`/`opacity`/`filter`
108
+ exist on `path` AND `text` — don't fake them.
109
+ 10. **Drop test = object CENTER** by default (`at pointer` for the pointer; `hitbox W H` for an explicit
110
+ rect). `when released` fires BEFORE the drop test. `reveal`/`trace` progress is monotone.
111
+ 11. **Compile-time (`def`/`$()`/`repeat` in scene/param symbols) vs runtime (`var`)** — don't confuse. A
112
+ `def` isn't a runtime var: inject it in behavior expressions with `$(name)`, not as a bare identifier.
113
+ Use the vars an interactor writes (`drag px,py` only moves if you bind `x = px y = py`); arrays must
114
+ exist before indexed writes.
115
+ 12. **`object "X"` must name a group/instance/text/image** — a shape or a layer carries no pose and is a
116
+ **compile error**; wrap the shape in a `group "Name"`. Opacities MULTIPLY (an `opacity 0` shape cancels
117
+ its group's fade-in). `as "…"`/`at x,y` go **right after the geometry/content**, before
118
+ `font`/`box`/`fill` (`text "…" at x,y box W H`, not `… box W H at x,y`).
119
+ 13. **`x`/`y` REPLACE a group's `at` (not deltas):** `object "G" { x = bump }` overwrites the x of a group
120
+ `at X,Y` → it jumps to the edge. To move **around** the anchor bind the additive offsets **`dx`/`dy`**
121
+ (`pos = at + (dx, dy)`): `dx = 30*sin(clock)` needs no base. Re-inject (`x = $(X) + bump`) only when you
122
+ really want an absolute position. Parameterized symbols
123
+ (`symbol "X"(args)`) are `.flatink`-inline only — never in a `.flat` lib (parens ⇔ parameterized).
124
+ 14. **`time` WRAPS every `durationFrames` (2.5 s by default); `clock` is monotone.** Capture instants with
125
+ `clock` (`when wrong { shown = clock }` + `opacity = pulse(shown, 4)`), or the ramp replays for ever.
126
+ 15. **`{ enabled <expr> }` gates the GESTURE only** — `when pressed`/`released`/`clicked` still fire. Guard
127
+ the body: `when released { if done == 0 { … } }`.
128
+
129
+ ## Before returning: check
130
+ `size` first; right half for each keyword; one action/line; pivots set; radians vs degrees correct;
131
+ every behavior target exists in the scene **and is a group/instance/text/image**; instants captured with
132
+ `clock`; gated handlers guard their own body; persistent containers won't vanish; arrays pre-allocated.