@flatkit/compiler 0.22.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scopeProgram.ts","../src/lint.ts","../src/programDoc.ts","../src/docWalk.ts","../src/check.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// scopeProgram.ts — the \"unified file\" of a scope (the Scene, or a Symbol).\n//\n// Vision: one file = one scope. All the code of a scope fits in ONE text:\n// var … (global variables, root only)\n// when loaded {} (lifecycle of the scope's timeline)\n// every frame {}\n// object \"name\" {} (one block per scripted object of the scope)\n//\n// This is the `.flatink` structure WITHOUT the geometry (which stays visual). This module\n// ONLY deals with the text: splitting the `object` blocks from the rest, and formatting a block.\n// The wiring to the Doc (a single commit) lives in the store, which reuses these functions\n// + scriptDoc/dsl. Pure, no DOM, testable.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** End of the block opened at `open` (index of the matching `}`), ignoring strings and comments. -1 if unclosed. */\nfunction matchBrace(s: string, open: number): number {\n let depth = 0\n for (let i = open; i < s.length; i++) {\n const c = s[i]\n if (c === '\"') { i++; while (i < s.length && s[i] !== '\"') { if (s[i] === '\\\\') i++; i++ }; continue }\n if (c === '/' && s[i + 1] === '/') { while (i < s.length && s[i] !== '\\n') i++; continue }\n if (c === '{') depth++\n else if (c === '}') { depth--; if (depth === 0) return i }\n }\n return -1\n}\n\n/** Splits a scope file: the `object \"name\" { … }` blocks on one side, the REST (var + lifecycle) on the other. */\nexport function splitScopeProgram(text: string): { rest: string; objects: { name: string; body: string }[] } {\n let rest = ''\n let i = 0\n const objects: { name: string; body: string }[] = []\n while (i < text.length) {\n const m = /object\\s+\"((?:[^\"\\\\]|\\\\.)*)\"\\s*\\{/.exec(text.slice(i))\n if (!m) { rest += text.slice(i); break }\n const start = i + m.index\n rest += text.slice(i, start)\n const ob = start + m[0].length - 1 // the opening brace\n const oc = matchBrace(text, ob)\n if (oc < 0) { rest += text.slice(start); break } // unclosed block → left to the rest (diagnostic downstream)\n objects.push({ name: m[1].replace(/\\\\(.)/g, (_, c) => (c === 'n' ? '\\n' : c)), body: text.slice(ob + 1, oc) })\n i = oc + 1\n }\n return { rest, objects }\n}\n\n/** LINTABLE regions of a scope file (the \"rest\" gaps + the `object` bodies), with their\n * 1-based start line in the original text — to lint each piece as pure DSL and\n * reproject the positions. The `object` keyword and its braces are NOT DSL: we skip them. */\nexport function scopeRegions(text: string): { body: string; line: number }[] {\n const lineAt = (off: number) => text.slice(0, off).split('\\n').length\n const regions: { body: string; line: number }[] = []\n let i = 0\n let restStart = 0\n while (i < text.length) {\n const m = /object\\s+\"((?:[^\"\\\\]|\\\\.)*)\"\\s*\\{/.exec(text.slice(i))\n if (!m) break\n const start = i + m.index\n const gap = text.slice(restStart, start)\n if (gap.trim()) regions.push({ body: gap, line: lineAt(restStart) })\n const ob = start + m[0].length - 1\n const oc = matchBrace(text, ob)\n if (oc < 0) break\n regions.push({ body: text.slice(ob + 1, oc), line: lineAt(ob + 1) })\n i = oc + 1\n restStart = i\n }\n const tail = text.slice(restStart)\n if (tail.trim()) regions.push({ body: tail, line: lineAt(restStart) })\n return regions\n}\n\nconst esc = (s: string) => s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n')\nconst indent = (body: string) => body.split('\\n').map((l) => (l.trim() ? ' ' + l : l)).join('\\n')\n\n/** Formats an `object \"name\" { … }` block (body already in DSL). */\nexport function formatObjectBlock(name: string, body: string): string {\n const inner = body.trim()\n return `object \"${esc(name)}\" {\\n${inner ? indent(inner) + '\\n' : ''}}`\n}\n\n/** Assembles a scope file: rest (var + lifecycle) then the object blocks, separated by a blank line. */\nexport function joinScopeProgram(rest: string, objects: { name: string; body: string }[]): string {\n const parts: string[] = []\n if (rest.trim()) parts.push(rest.trim())\n for (const o of objects) if (o.body.trim()) parts.push(formatObjectBlock(o.name, o.body))\n return parts.length ? parts.join('\\n\\n') + '\\n' : ''\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// lint.ts — linter for the FlatInk Script language (batch L3).\n//\n// Builds on the parser (dsl.ts) for syntax errors + positions, then adds\n// SEMANTIC validation, ~free and PURE:\n// • each expression is compiled (compileExpr) → expression syntax error;\n// • referenced functions / member objects / identifiers are checked against\n// the standard environment (expr.ts) + the known variables → \"unknown\";\n// • `go to \"label\"` is checked against the set of known labels.\n//\n// Tolerant and kid-friendly: a variable is \"known\" if it is declared\n// (`let`) OR simply assigned (`x = …`) somewhere in the source, or provided\n// by the caller (document global variables). Labels are only checked\n// if the caller provides their list (otherwise we don't know the universe).\n// ─────────────────────────────────────────────────────────────────────────────\nimport { analyzeExpr, STD_CONSTANTS, STD_FUNCTIONS, STD_IDS, STD_OBJECTS } from '@flatkit/engine/expr'\nimport { packageFunctionNames } from '@flatkit/engine/stdlib'\nimport { parseUnits, type Diagnostic, type ScriptUnit } from '@flatkit/engine/dsl'\nimport type { Action } from '@flatkit/engine/actions'\n\nexport type LintContext = {\n /** Known variables in addition to those declared/assigned in the source. */\n variables?: Iterable<string>\n /** Known labels (timeline). Absent = we don't check `go to \"…\"`. */\n labels?: Iterable<string>\n /** Known functions in addition to those defined in the source. */\n functions?: Iterable<string>\n /** Scene objects referenceable by name (`Hero.x`) — in addition to mouse/keys. */\n objects?: Iterable<string>\n}\n\n/** Collects the names of ASSIGNED variables (setVar) + loop vars, inside action bodies. */\nfunction collectAssigned(actions: Action[], into: Set<string>): void {\n for (const a of actions) {\n if (a.do === 'setVar') into.add(a.name)\n else if (a.do === 'if') {\n collectAssigned(a.then, into)\n if (a.else) collectAssigned(a.else, into)\n } else if (a.do === 'repeat') collectAssigned(a.body, into)\n else if (a.do === 'repeatRange') { into.add(a.var); collectAssigned(a.body, into) } // i is known in the body\n }\n}\n\n/** Locally known variables: `let` declarations + every assigned variable. */\nexport function localVariables(units: ScriptUnit[]): Set<string> {\n const vars = new Set<string>()\n for (const u of units) {\n if (u.kind === 'declare') vars.add(u.name)\n else if (u.kind === 'event' || u.kind === 'frameActions') collectAssigned(u.body, vars)\n else if (u.kind === 'each') vars.add(u.as) // the index is known in the each bindings\n else if (u.kind === 'interactor') { if (u.varX) vars.add(u.varX); if (u.varY) vars.add(u.varY) } // drag writes these variables\n else if (u.kind === 'drop') collectAssigned(u.body, vars)\n else if (u.kind === 'func') { for (const p of u.func.params) vars.add(p); if (u.func.kind === 'proc') collectAssigned(u.func.body, vars) } // params + assignments\n }\n return vars\n}\n\n/** Detects a `text(` call (whole word) in an expression → only allowed in a `send` payload. */\nconst TEXT_CALL = /\\btext\\s*\\(/\n\n/** A bare assignment (`x =`, `a[i] =`; NOT ==/<=/>=/!=) left inside an expression: the tell-tale of a\n * SECOND statement crammed onto one line (FlatInk ends a statement at the newline, so it got swallowed). */\nconst SECOND_ASSIGN = /[\\w\\]]\\s*=(?![=])/\n\n/** Same tell-tale for the statements that are NOT assignments — `score = score + 1 send \"correct\", 1`\n * swallowed the `send` and reported a bewildering `unexpected character \"\"\"` instead. All of these are\n * RESERVED words (cf. ident.ts), so none can legitimately appear as a name inside an expression. */\nconst SECOND_ACTION = /\\b(send|play|pause|sound|set|go|repeat)\\b/\n\n/** Analyze a DSL source and return all diagnostics (syntax + semantic). */\nexport function lint(src: string, ctx: LintContext = {}): Diagnostic[] {\n const { units, diagnostics, sites } = parseUnits(src)\n const out: Diagnostic[] = [...diagnostics]\n\n const variables = localVariables(units)\n for (const v of ctx.variables ?? []) variables.add(v)\n const knownIds = new Set<string>([...STD_IDS, ...STD_CONSTANTS, ...variables])\n const knownFns = new Set(STD_FUNCTIONS)\n knownFns.add('velocity') // valid inside a spring/smooth target (resolved by the modifier advance); NOT a stdlib\n for (const u of units) {\n if (u.kind === 'func') knownFns.add(u.func.name) // user-defined functions\n else if (u.kind === 'use') for (const name of packageFunctionNames(u.name)) knownFns.add(name) // package functions (bare + qualified)\n }\n for (const f of ctx.functions ?? []) knownFns.add(f)\n const knownObjs = new Set(STD_OBJECTS)\n for (const o of ctx.objects ?? []) knownObjs.add(o) // scene objects (Hero.x…)\n const labels = ctx.labels ? new Set(ctx.labels) : null\n\n for (const s of sites) {\n if (s.kind === 'expr') {\n // `text(…)` is valid ONLY as a `send` payload (consumed by the parser, never\n // exposed as an expression site). Seeing it here = out-of-context use → dedicated error.\n if (TEXT_CALL.test(s.text)) {\n out.push({ line: s.line, col: s.col, message: 'text(\"…\") is only allowed as an argument to \"send\"' })\n continue\n }\n const a = analyzeExpr(s.text)\n if (!a.ok) {\n // #1 footgun: two statements on one line. The 2nd one got swallowed into this expression → a\n // cryptic \"unexpected character = / \\\"\". Detect the leftover statement and NAME it, so the rule\n // (\"one action per line\") is visible from the message rather than only from the docs.\n const leftover = SECOND_ASSIGN.test(s.text) ? 'an assignment' : SECOND_ACTION.test(s.text) ? `\\`${SECOND_ACTION.exec(s.text)![1]} …\\`` : ''\n const message = leftover\n ? `two statements on one line — ${leftover} was swallowed into the expression before it. FlatInk ends a statement at the NEWLINE: put each action on its own line.`\n : `invalid expression: ${a.error}`\n out.push({ line: s.line, col: s.col, message })\n continue\n }\n for (const fn of a.refs.calls) if (!knownFns.has(fn)) out.push({ line: s.line, col: s.col, message: `unknown function \"${fn}\"` })\n for (const o of a.refs.members)\n if (!knownObjs.has(o)) out.push({ line: s.line, col: s.col, message: `unknown object \"${o}\" (expected: ${[...knownObjs].join(', ')})` })\n for (const id of a.refs.ids)\n if (!knownIds.has(id))\n out.push({ line: s.line, col: s.col, message: `unknown variable \"${id}\"${variables.size ? '' : ' — declare it with \"let\"'}` })\n } else if (labels && !labels.has(s.name)) {\n out.push({ line: s.line, col: s.col, message: `unknown label \"${s.name}\"` })\n }\n }\n\n return out.sort((a, b) => a.line - b.line || a.col - b.col)\n}\n\n/**\n * Lint report ready to feed back to a generator (LLM/CLI): `''` = no issue (the code passes),\n * otherwise one `line:col: message` line per diagnostic. Primitive of the \"generate → lint →\n * fix\" loop: the language fits in few tokens, robustness comes from this feedback, not from context.\n */\nexport function lintReport(src: string, ctx: LintContext = {}): string {\n return lint(src, ctx).map((d) => `${d.line}:${d.col}: ${d.message}`).join('\\n')\n}\n","// -----------------------------------------------------------------------------\n// programDoc.ts — PURE bridge Doc <-> \"scope program\" (text) + lint of a whole Doc.\n//\n// Both the editor AND the CLI need to rebuild the DSL text of a scope (to display it,\n// or to lint it) and to assemble the lint context (variables/labels/functions/objects)\n// FROM a Doc — without depending on the React store. Everything lives here, pure and testable:\n// • `scopeProgram` : Doc fragments -> program text of a scope (round-trip via printUnits);\n// • `docLintContext` : Doc + scope -> LintContext (known names);\n// • `lintDoc` / report : semantic lint of ALL the scopes of a Doc (scene + symbols).\n// -> the CLI gains a `flatc --check`, the editor stops duplicating the logic.\n// -----------------------------------------------------------------------------\nimport type { Doc, EditFrame, Group, Instance } from '@flatkit/types'\nimport { printUnits, type Diagnostic } from '@flatkit/engine/dsl'\nimport { joinScopeProgram, scopeRegions } from './scopeProgram'\nimport { functionsToUnits, importsToUnits, objectToUnits, timelineToUnits, variablesToUnits } from '@flatkit/engine/scriptDoc'\nimport { contextLayers, getScopeTimeline, isContainer, isGroup, isInstance, isText, isImage, isPoseable, isRegion } from '@flatkit/engine/layers'\nimport { importedFunctions } from '@flatkit/engine/stdlib'\nimport { EXPR_CHANNELS } from '@flatkit/engine/timeline'\nimport { objectNames } from '@flatkit/engine/sceneRefs'\nimport { behaviorRegions } from '@flatkit/engine/flatFormat'\nimport { itemBBox, dropZoneBounds, transformBBox } from '@flatkit/engine/groups'\nimport { IDENTITY, apply, compose } from '@flatkit/engine/transform'\nimport { makePathSampler } from '@flatkit/engine/path'\nimport { bboxIntersects } from '@flatkit/engine/bbox'\nimport { lint, localVariables, type LintContext } from './lint'\nimport { forEachAction, forEachExpression } from './docWalk'\nimport { parseUnits } from '@flatkit/engine/dsl'\nimport type { Image, Item, Layer, ParamDef, SymbolDef, Text, Transform } from '@flatkit/types'\n\n/** Rebuilds the \"program\" text of a scope (imports + variables + functions + scene cycle\n * + one `object \"Name\" { … }` block per scripted container with a unique name). Pure, round-trip via printUnits. */\nexport function scopeProgram(doc: Doc, editPath: EditFrame[] = []): string {\n const atRoot = editPath.length === 0\n const items = contextLayers(doc, editPath).flatMap((l) => l.items)\n const nameCount = new Map<string, number>()\n for (const it of items) if (isContainer(it) && it.name) nameCount.set(it.name, (nameCount.get(it.name) ?? 0) + 1)\n const scripted = (it: Group | Instance) => !!doc.interactions?.some((i) => i.targetId === it.id) || !!doc.interactors?.some((i) => i.targetId === it.id) || !!(it.expressions && Object.keys(it.expressions).length)\n const objects = items\n .filter((it): it is Group | Instance => isContainer(it) && !!it.name && nameCount.get(it.name) === 1 && scripted(it))\n .map((it) => ({ name: it.name, body: printUnits(objectToUnits(it.id, doc.interactions, it.expressions, doc.interactors)) }))\n const imports = atRoot ? printUnits(importsToUnits(doc.imports)) : ''\n // A local package (`use \"physics\"` -> physics.flatink) is inlined TWICE into doc.functions: `tick` and the\n // qualified alias `physics.tick` (so both call forms resolve). The alias is not authored source and has NO\n // syntax (`fn physics.tick(s) = …` does not parse) -> never re-emit it, or the reconstructed program fails\n // to parse and `--check` reports phantom errors. The alias stays in `doc.functions`, so `docLintContext`\n // still knows `physics.tick` as a callable name.\n const funcs = atRoot ? printUnits(functionsToUnits((doc.functions ?? []).filter((f) => !f.name.includes('.')))) : ''\n const globals = atRoot ? printUnits(variablesToUnits(doc.variables)) : ''\n const scene = printUnits(timelineToUnits(getScopeTimeline(doc, editPath)))\n const head = [imports, globals, funcs, scene].filter((t) => t.trim()).join('\\n\\n')\n return joinScopeProgram(head, objects)\n}\n\n/** Lint context of a scope: known names (variables, labels, functions, objects) drawn from the Doc.\n * `extraVars` = variables assigned in the OTHER scopes (FlatInk variables are GLOBAL -> known\n * everywhere; avoids a false \"unknown variable\" on a var written elsewhere, e.g. generated by `match`). */\nexport function docLintContext(doc: Doc, editPath: EditFrame[] = [], extraVars?: Iterable<string>): LintContext {\n return {\n // A symbol's own `params` (and state params) are injected into its instance scope at runtime, so they are\n // valid variables in ITS `expr` -- add them, but ONLY to that symbol's scope (resolved from `editPath`),\n // never globally, so a param named in symbol A can't mask a real typo of the same name in symbol B.\n variables: [...Object.keys(doc.variables ?? {}), ...(extraVars ?? []), ...scopeSymbolParams(doc, editPath)],\n labels: (getScopeTimeline(doc, editPath)?.labels ?? []).map((l) => l.name),\n functions: [...(doc.functions ?? []).map((f) => f.name), ...importedFunctions(doc.imports).map((f) => f.name)],\n objects: objectNames(contextLayers(doc, editPath)),\n }\n}\n\n/** Names exposed as variables by the symbol of `editPath` (its `params` + state params), or `[]` for the\n * scene scope / an unresolved path. Mirrors the per-instance scope the runtime and `--preview` build. */\nfunction scopeSymbolParams(doc: Doc, editPath: EditFrame[]): string[] {\n const frame = [...editPath].reverse().find((f): f is Extract<EditFrame, { kind: 'symbol' }> => f.kind === 'symbol')\n const sym = frame ? doc.symbols.find((s) => s.id === frame.symbolId) : undefined\n if (!sym) return []\n return [...(sym.params ?? []).map((p) => p.name), ...(sym.states ?? []).map((s) => s.param)]\n}\n\n/** All the scopes of a Doc: the scene (root) + each symbol of the library. */\nfunction scopes(doc: Doc): { label: string; editPath: EditFrame[] }[] {\n return [{ label: 'scene', editPath: [] }, ...doc.symbols.map((s) => ({ label: s.name, editPath: [{ kind: 'symbol' as const, symbolId: s.id, name: s.name }] }))]\n}\n\n/** Union of the variables ASSIGNED in ALL the scopes (scene + symbols). FlatInk variables are\n * global -> a var written in one scope is legitimate in the others (cross-scope reads). */\nexport function allScopeVariables(doc: Doc): string[] {\n const set = new Set<string>()\n for (const { editPath } of scopes(doc))\n for (const r of scopeRegions(scopeProgram(doc, editPath)))\n for (const v of localVariables(parseUnits(r.body).units)) set.add(v)\n return [...set]\n}\n\n/** Readable name of an item by id (for the messages). */\nfunction itemNameById(doc: Doc, id: string): string | null {\n let found: string | null = null\n const walk = (layers: { items: import('@flatkit/types').Item[] }[]) => {\n for (const l of layers) for (const it of l.items) {\n if (found) return\n if ('name' in it && it.id === id) { found = it.name ?? null; return }\n if (isGroup(it)) walk(it.layers)\n }\n }\n walk(doc.layers)\n return found\n}\n\nconst escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\-]/g, '\\\\$&')\n\n/** Value functions reachable in this Doc whose body reads the LOOP-WRAPPED `time`, directly or through\n * another such function. Only `kind: 'value'` matters — a `proc` is not callable from a channel expression.\n * Fixpoint, so `fn a(t) = b(t)` inherits from `b`. Qualified aliases (`feedback.pulse`) are kept: an author\n * may call either form. */\nfunction functionsReadingTime(doc: Doc): string[] {\n const defs = [...(doc.functions ?? []), ...importedFunctions(doc.imports)].filter((f) => f.kind === 'value')\n const reads = new Set<string>()\n for (;;) {\n const before = reads.size\n for (const f of defs) {\n if (reads.has(f.name)) continue\n const body = f.expr\n // A parameter NAMED `time` would shadow the runtime scalar — then the body's `time` is just an argument.\n if (!f.params.includes('time') && /\\btime\\b/.test(body)) { reads.add(f.name); continue }\n for (const other of reads) if (new RegExp(`\\\\b${escapeRe(other)}\\\\s*\\\\(`).test(body)) { reads.add(f.name); break }\n }\n if (reads.size === before) return [...reads]\n }\n}\n\n/** Top-level arguments of every `name(…)` call in `src`. Depth-aware, so a nested call keeps its own\n * commas: `pulse(max(a, 0), 1)` reads as two arguments, not three. */\nfunction callArguments(src: string, name: string): string[][] {\n const out: string[][] = []\n const re = new RegExp(`(?<![\\\\w.])${escapeRe(name)}\\\\s*\\\\(`, 'g')\n let m: RegExpExecArray | null\n while ((m = re.exec(src))) {\n const args: string[] = []\n let depth = 1\n let start = m.index + m[0].length\n let i = start\n for (; i < src.length; i++) {\n const c = src[i]\n if (c === '(') depth++\n else if (c === ')') { depth--; if (depth === 0) break }\n else if (c === ',' && depth === 1) { args.push(src.slice(start, i)); start = i + 1 }\n }\n args.push(src.slice(start, i))\n out.push(args.map((a) => a.trim()))\n }\n return out\n}\n\n/** Which argument of a feedback function is an INSTANT (a timestamp compared against `clock`):\n * `pulse(since, dur)` → the 1st; `shake(bad, t)` → the 2nd (the 1st is the flag). */\nconst INSTANT_ARG: Record<string, number> = { pulse: 0, shake: 1 }\n\n/** Variables whose value is captured from `time` (`doneAt = time`) — the pre-0.23 timestamp idiom, back\n * when `pulse`/`shake` rode `time` too. A capture that already mentions `clock` is the correct one. */\nfunction timeCapturedVars(doc: Doc): Set<string> {\n const out = new Set<string>()\n forEachAction(doc, (a) => {\n if (a.do === 'setVar' && /\\btime\\b/.test(a.value) && !/\\bclock\\b/.test(a.value)) out.add(a.name)\n })\n return out\n}\n\n/** STRUCTURAL warnings (non-blocking) of a Doc: phantom drop zones, dead variables. */\nexport function docStructureWarnings(doc: Doc): { scope: string; diag: Diagnostic }[] {\n const out: { scope: string; diag: Diagnostic }[] = []\n // (a) `when dropped on X` whose zone X does not exist (no item named so) -> the handler can never fire.\n const names = new Set(objectNames(doc.layers))\n for (const it of doc.interactions ?? []) {\n if (it.event === 'drop' && it.over && !names.has(it.over)) {\n const who = itemNameById(doc, it.targetId) ?? it.targetId\n out.push({ scope: 'scene', diag: { line: 1, col: 1, severity: 'warning', message: `unknown drop zone \"${it.over}\" (object \"${who}\") — no item of the scene carries this name` } })\n }\n }\n // (b) global variable never referenced (declared but not found in any scope) -> probably dead.\n const names0 = Object.keys(doc.variables ?? {})\n if (names0.length) {\n // Modifier targets are NOT emitted into scopeProgram (to avoid double-linting them, cf. docModifierWarnings),\n // so collect them here too — else a global used ONLY by a `spring`/`smooth` target reads as \"never used\".\n const modTargets: string[] = []\n const collectMods = (items: Item[]): void => {\n for (const it of items) {\n if (isPoseable(it) && it.modifiers) for (const ch of EXPR_CHANNELS) { const mm = it.modifiers[ch]; if (mm) modTargets.push(mm.target) }\n if (isGroup(it)) for (const l of it.layers) collectMods(l.items)\n }\n }\n for (const l of doc.layers) collectMods(l.items)\n for (const s of doc.symbols ?? []) for (const l of s.layers) collectMods(l.items)\n const allText = [...scopes(doc).map(({ editPath }) => scopeProgram(doc, editPath)), ...modTargets].join('\\n')\n for (const name of names0) {\n const m = allText.match(new RegExp(`(?<![\\\\w-])${escapeRe(name)}(?![\\\\w-])`, 'g'))\n if ((m?.length ?? 0) <= 1) out.push({ scope: 'scene', diag: { line: 1, col: 1, severity: 'warning', message: `global variable \"${name}\" never used (declared, but neither read nor written)` } })\n }\n }\n // (b bis) An `instance \"X\"` that resolved to NO symbol. Compilation keeps the unresolved `@X` marker all\n // the way into the .flatpack, the instance draws nothing, and everything else about the file looks\n // fine — the same family as the phantom drop zone above: a name that points at nothing. Only a\n // warning: compiling a program on its own and supplying its libraries later is a legitimate\n // workflow, and it must keep exiting 0.\n const missing = new Set<string>()\n const scanRefs = (items: Item[]): void => {\n for (const it of items) {\n if (isInstance(it) && it.symbolId.startsWith('@')) missing.add(it.symbolId.slice(1))\n if (isGroup(it)) for (const l of it.layers) scanRefs(l.items)\n }\n }\n for (const l of doc.layers) scanRefs(l.items)\n for (const s of doc.symbols ?? []) for (const l of s.layers) scanRefs(l.items)\n if (missing.size) {\n const names = [...missing].map((n) => `\"${n}\"`).join(', ')\n out.push(warn(`instance resolves to no symbol: ${names} — nothing is drawn where it stands. Pass the .flat library that defines it (\\`flatc <program> <lib.flat>\\`, or place it beside the program), or fix the name.`))\n }\n\n // (c) `time` reaching a channel expression + a SHORT looping timeline → the motion JUMPS on each loop\n // (`time = frame/fps` resets to 0 every `durationFrames`). The monotone `clock` never wraps.\n // A `time` reached THROUGH A FUNCTION counts: the costliest version of this trap was\n // `opacity = pulse(doneAt, 0.9)`, where the wrapping clock sat in the callee's body and the channel\n // text held no `time` at all — so a warning that only grepped the channel said nothing whatsoever.\n const dur = doc.timeline?.durationFrames ?? 60\n const fps = doc.timeline?.fps ?? 24\n if (dur <= 120) { // ≤ 5 s @24fps: short enough that the reset is visible as a periodic jump\n const viaFn = functionsReadingTime(doc)\n const culprits = new Set<string>()\n const scan = (layers: Layer[]) => {\n for (const l of layers) for (const it of l.items) {\n if (isPoseable(it) && it.expressions) for (const k in it.expressions) {\n const expr = it.expressions[k as keyof typeof it.expressions] ?? ''\n if (/\\btime\\b/.test(expr)) culprits.add('`time`')\n for (const fn of viaFn) if (new RegExp(`\\\\b${escapeRe(fn)}\\\\s*\\\\(`).test(expr)) culprits.add(`\\`${fn}()\\`` + ' (which reads `time`)')\n }\n if (isGroup(it)) scan(it.layers)\n }\n }\n scan(doc.layers)\n if (culprits.size) {\n const secs = Math.round((dur / fps) * 10) / 10\n out.push(warn(`a channel expression uses ${[...culprits].join(' and ')}, but the timeline loops every ${secs}s (durationFrames ${dur}) — \\`time\\` resets each loop, so the motion jumps (a one-shot ramp REPLAYS forever). Use the monotone \\`clock\\` (never wraps), drive it by \\`frame/${dur}\\`, or set a longer \\`timeline\\`.`))\n }\n }\n // (d) An instant captured on `time` and handed to `pulse`/`shake`. Both ride the MONOTONE `clock`, so\n // `clock - since` grows without bound and the ramp NEVER fires. Unlike (c) this is loop-LENGTH\n // independent — the two axes never meet however long the timeline is — and it is entirely SILENT:\n // nothing jumps, nothing blinks, and (c) itself stays quiet because the channel text holds no `time`.\n // The 0.21 → 0.23 migration trap: back then `pulse` rode `time` and the mismatch at least showed.\n const captured = timeCapturedVars(doc)\n if (captured.size) {\n const readers = new Map<string, Set<string>>() // variable → the feedback functions reading it as an instant\n forEachExpression(doc, (expr) => {\n for (const fnName of Object.keys(INSTANT_ARG)) {\n for (const args of callArguments(expr, fnName)) {\n const arg = args[INSTANT_ARG[fnName]]\n if (!arg) continue\n // The name may sit INSIDE the argument (`pulse(max(doneAt, 0), 1)`) — match it as a whole word.\n for (const v of captured) {\n if (!new RegExp(`(?<![\\\\w-])${escapeRe(v)}(?![\\\\w-])`).test(arg)) continue\n const seen = readers.get(v) ?? new Set<string>()\n seen.add(fnName)\n readers.set(v, seen)\n }\n }\n }\n })\n for (const [v, fns] of readers) {\n const who = [...fns].map((f) => `\\`${f}()\\``).join(' and ')\n out.push(warn(`\"${v}\" is captured on \\`time\\` but read as an INSTANT by ${who}, which compares it against the monotone \\`clock\\` — the two axes never meet, so the ramp never fires and nothing on screen says so. Capture it with \\`clock\\` instead: \\`${v} = clock\\`.`))\n }\n }\n out.push(...docPaintParamWarnings(doc))\n out.push(...docCelLayerWarnings(doc))\n out.push(...docLayoutWarnings(doc))\n return out\n}\n\n/**\n * SILENT DROPS in a cel layer. `resolveLayerAt` draws exactly two things: the current cel's `matter`, and\n * the containers that cel POSES — never the layer's other roster items. Three ways to author a layer that\n * compiles, renders an empty frame, and says nothing:\n * (a) a bare shape (`path`/`circle`/`rect`/…) sitting in a layer that has cels — it belongs inside a\n * cel's `matter { … }` (frame-by-frame drawing) or on a cel-less layer (static decor);\n * (b) a `pose \"X\"` naming no roster item (the parser leaves such a pose id as `@X`) — poses nothing;\n * (c) a roster container/text/image that no cel ever poses — never drawn.\n * All three are the classic \"my drawing vanished\" bug. Warnings (non-blocking).\n */\nexport function docCelLayerWarnings(doc: Doc): { scope: string; diag: Diagnostic }[] {\n const out: { scope: string; diag: Diagnostic }[] = []\n const checkScope = (scope: string, layers: Layer[]) => {\n const push = (message: string) => out.push({ scope, diag: { line: 1, col: 1, severity: 'warning', message } })\n const visit = (ls: Layer[]): void => {\n for (const l of ls) {\n if (l.cels?.length) {\n const bare = l.items.filter(isRegion)\n if (bare.length) push(`layer \"${l.name}\" has cels, so its ${bare.length} bare shape(s) are NEVER drawn — a cel layer draws only the cel's \\`matter { … }\\` and the containers it poses. Move them into \\`cel N { matter { … } }\\`, or onto a cel-less layer if they are static.`)\n const posed = new Set<string>()\n for (const c of l.cels) for (const p of c.poses) posed.add(p.id)\n for (const id of posed) if (id.startsWith('@')) push(`layer \"${l.name}\": \\`pose \"${id.slice(1)}\"\\` matches no item of this layer — the pose draws nothing (roster: ${l.items.map(itemLabel).join(', ') || 'empty'}).`)\n for (const it of l.items) if (isPoseable(it) && !posed.has(it.id)) push(`layer \"${l.name}\": \"${itemLabel(it)}\" is never posed by a cel — it is never drawn. Add \\`pose \"${itemLabel(it)}\"\\` to the cels where it should show.`)\n }\n for (const it of l.items) if (isGroup(it)) visit(it.layers)\n }\n }\n visit(layers)\n }\n checkScope('scene', doc.layers)\n for (const s of doc.symbols) checkScope(s.name, s.layers)\n return out\n}\n\n/** A scope's declared `color` param names (the only params usable as a paint color). */\nconst colorParamNames = (params?: ParamDef[]): Set<string> => new Set((params ?? []).filter((p) => p.type === 'color').map((p) => p.name))\n\n/**\n * A `color` param used as a paint (`fill`/`stroke`, a gradient **stop** `0:teinte@…`, or a `tint`) that the\n * owning symbol does NOT declare → a SILENT \"dead recolor\" (it falls back to the literal hex, the picker does\n * nothing). Warns per scope, with the param scoped to ITS symbol (the scene declares none). Mirrors how the\n * lint now knows a symbol's params in its `expr`. Non-blocking (a warning), so it never breaks a build.\n */\nexport function docPaintParamWarnings(doc: Doc): { scope: string; diag: Diagnostic }[] {\n const out: { scope: string; diag: Diagnostic }[] = []\n const checkScope = (scope: string, layers: Layer[], known: Set<string>) => {\n const flag = (param: string, where: string, who?: string) => {\n if (known.has(param)) return\n const hint = known.size ? `declared color params: ${[...known].join(', ')}` : 'this scope declares no color params'\n out.push({ scope, diag: { line: 1, col: 1, severity: 'warning', message: `unknown color param \"${param}\" in ${where}${who ? ` (${who})` : ''} — ${hint}` } })\n }\n const visit = (it: Item) => {\n const who = 'name' in it ? it.name : undefined\n if (isRegion(it)) {\n if (it.fillParam) flag(it.fillParam, 'a fill', who)\n if (it.strokeParam) flag(it.strokeParam, 'a stroke', who)\n const stops = it.paint && it.paint.type !== 'solid' ? it.paint.stops : []\n for (const s of stops) if (s.param) flag(s.param, 'a gradient stop', who)\n }\n if (isPoseable(it) && it.tint?.param) flag(it.tint.param, 'a tint', who)\n if (isGroup(it)) for (const l of it.layers) for (const c of l.items) visit(c) // a group is the same scope; an instance is a separate symbol (linted on its own pass)\n }\n for (const l of layers) for (const it of l.items) visit(it)\n }\n checkScope('scene', doc.layers, new Set())\n for (const s of doc.symbols) checkScope(s.name, s.layers, colorParamNames((s as SymbolDef).params))\n return out\n}\n\nconst warn = (message: string): { scope: string; diag: Diagnostic } => ({ scope: 'scene', diag: { line: 1, col: 1, severity: 'warning', message } })\nconst itemLabel = (it: Item): string => ('name' in it && it.name ? it.name : 'kind' in it ? it.kind : 'path')\n/** Mean advance of a proportional glyph at this text's size — the metric both estimators share. */\nconst glyphAdvance = (t: Text): number => (t.weight && t.weight >= 700 ? 0.56 : 0.52) * t.size\n\n/** Estimated width of the longest line (approximate metric, without canvas). */\nfunction estTextWidth(t: Text): number {\n const advance = glyphAdvance(t)\n let longest = 0\n for (const line of t.content.split('\\n')) longest = Math.max(longest, line.length)\n return longest * advance\n}\n\n/** Greedy word-wrap of a `wrap` text at its box width — the renderer's own rule (break at spaces, and\n * only at spaces). Returns the resulting line count and the widest single word, which is what decides\n * whether anything spills sideways: a word wider than the box has no break point and runs past it. */\nfunction wrapMetrics(t: Text): { lines: number; widestWord: number } {\n const advance = glyphAdvance(t)\n const width = (s: string) => s.length * advance\n let lines = 0\n let widestWord = 0\n for (const paragraph of t.content.split('\\n')) {\n lines++ // an explicit newline always starts a line, empty ones included (they still take height)\n let current = ''\n for (const word of paragraph.split(/\\s+/).filter(Boolean)) {\n widestWord = Math.max(widestWord, width(word))\n const joined = current ? `${current} ${word}` : word\n if (current && width(joined) > t.box.w) { lines++; current = word } else current = joined\n }\n }\n return { lines, widestWord }\n}\n\n/** LAYOUT warnings (non-blocking): canvas overflow, text too wide, overlapping hitboxes.\n * Approximate but without canvas — catches \"blind\" positioning bugs before the render. */\nexport function docLayoutWarnings(doc: Doc): { scope: string; diag: Diagnostic }[] {\n const out: { scope: string; diag: Diagnostic }[] = []\n const W = doc.width, H = doc.height\n const TOL = 8 // overflow tolerance (px) before alerting\n const r0 = (n: number) => Math.round(n)\n\n // An item whose position is written at RUNTIME — bound to an expression, or dragged directly — has a\n // meaningless static bbox. That rule already existed; what it missed is that it is INHERITED: a label\n // sitting inside a dragged tile is authored around the tile's origin and only lands somewhere real once\n // the tile moves. Measuring it where the file happens to leave it (0,0) reports the whole scene as\n // clipped — the exact noise that would make the pass unusable.\n const dynamicPos = (it: Item) =>\n ('expressions' in it && it.expressions && (it.expressions.x != null || it.expressions.y != null || it.expressions.dx != null || it.expressions.dy != null)) ||\n !!doc.interactors?.some((i) => i.targetId === it.id) // `drag`/`turn`: the pointer decides where it is\n\n // Every text/image of the scene, paired with the WORLD matrix of its PARENT (the space `itemBBox`\n // reports in) and whether any ancestor is runtime-positioned. Both passes below used to read\n // `doc.layers` top level only — and a group is the only thing a behavior block can animate, so most of\n // a real scene lives inside one and was never measured at all.\n const placed: { it: Text | Image; parent: Transform; dynamic: boolean }[] = []\n const collect = (layers: Layer[], matrix: Transform, dynamic: boolean): void => {\n for (const l of layers) for (const it of l.items) {\n if (isText(it) || isImage(it)) placed.push({ it, parent: matrix, dynamic: dynamic || dynamicPos(it) })\n if (isGroup(it)) collect(it.layers, compose(matrix, it.transform), dynamic || dynamicPos(it))\n }\n }\n collect(doc.layers, IDENTITY, false)\n\n // (c) TEXT/IMAGE clipped at the canvas edge. Precise to avoid noise: we ignore `path`/groups\n // (decor that \"bleeds\" on purpose), anything positioned at runtime (see above), and items PARKED\n // entirely off-screen (\"hidden\" pattern).\n for (const { it, parent, dynamic } of placed) {\n if (dynamic || (isText(it) && it.textPath)) continue // path-laid text → §(f), not a box\n const local = itemBBox(doc, it)\n if (!local) continue\n const b = transformBBox(local, parent) // itemBBox is PARENT-space → compose with the ancestors\n const visible = b.maxX > 0 && b.minX < W && b.maxY > 0 && b.minY < H // overlaps the canvas (not parked off-screen)\n const clipped = b.minX < -TOL || b.minY < -TOL || b.maxX > W + TOL || b.maxY > H + TOL\n if (visible && clipped) {\n out.push(warn(`\"${itemLabel(it)}\" clipped at the canvas edge: bbox [${r0(b.minX)},${r0(b.minY)} -> ${r0(b.maxX)},${r0(b.maxY)}] outside 0,0 -> ${W},${H}`))\n }\n }\n\n // (d) Text (without `wrap`) whose estimated width makes it OVERFLOW THE CANVAS -> instruction clipped.\n // (We do NOT compare against `box.w`: without wrap, a text overflows its box harmlessly as long as it\n // fits in the canvas — it is the canvas edge that clips.)\n for (const { it, parent, dynamic } of placed) {\n if (!isText(it) || it.wrap || it.textPath || dynamic) continue\n const estW = estTextWidth(it)\n const left = it.align === 'left' ? 0 : it.align === 'right' ? it.box.w - estW : it.box.w / 2 - estW / 2\n const m = compose(parent, it.transform) // the text's own world matrix: its baseline runs in ITS space\n const p0 = apply(m, { x: left, y: 0 }), p1 = apply(m, { x: left + estW, y: 0 })\n const wl = Math.min(p0.x, p1.x), wr = Math.max(p0.x, p1.x) // left/right edge of the content in world coords\n if (wr > W + TOL || wl < -TOL) {\n out.push(warn(`text \"${it.content.slice(0, 24)}${it.content.length > 24 ? '…' : ''}\" overflows the canvas (estimated ~${r0(estW)} px, edge ${r0(wl)}->${r0(wr)} outside 0->${W}) — add \"wrap\"`))\n }\n }\n\n // (g) WRAPPED text taller than its box. Pass (d) skips `wrap` — rightly, since wrapping cannot overflow\n // the box horizontally — but that left wrapped text unmeasured entirely, and what it DOES overflow is\n // the box's height. Same visible defect: an instruction spilling out of its frame is the first thing\n // an eye catches, and the reason a whole activity had to be re-read by a multimodal model.\n for (const { it } of placed) {\n if (!isText(it) || !it.wrap || it.textPath) continue\n const { lines, widestWord } = wrapMetrics(it)\n const label = `${it.content.slice(0, 24).replace(/\\n/g, ' ')}${it.content.length > 24 ? '…' : ''}`\n const tall = lines * it.size * it.lineHeight\n if (tall > it.box.h + TOL) {\n out.push(warn(`text \"${label}\" overflows its box: ~${lines} wrapped line(s) ≈ ${r0(tall)} px for a box ${r0(it.box.h)} px tall — raise the box height, shorten the text, or reduce the size`))\n }\n if (widestWord > it.box.w + TOL) {\n out.push(warn(`text \"${label}\" has a word too wide for its box (~${r0(widestWord)} px > ${r0(it.box.w)} px) — a single word cannot be broken, so it spills past the frame whatever \"wrap\" says`))\n }\n }\n\n // (f) Text laid ALONG a path whose estimated run is longer than the path -> trailing glyphs dropped at\n // render (mirrors the canvas-overflow warning, but for the curve). Position-independent -> recurse.\n const pathTexts: Text[] = []\n const walkPathTexts = (layers: Layer[]) => { for (const l of layers) for (const it of l.items) { if (isText(it) && it.textPath) pathTexts.push(it); else if (isGroup(it)) walkPathTexts(it.layers) } }\n walkPathTexts(doc.layers)\n for (const it of pathTexts) {\n const L = makePathSampler(it.textPath!.path).length\n if (L <= 0) continue\n const advance = (it.weight && it.weight >= 700 ? 0.56 : 0.52) * it.size // same metric as estTextWidth\n const glyphs = [...it.content].length\n const eff = Math.max(advance + (it.textPath!.spacing ?? 0), 1) // mirror the renderer's per-glyph floor\n const estW = glyphs * eff\n if (estW > L + TOL) out.push(warn(`text \"${it.content.slice(0, 24)}${it.content.length > 24 ? '…' : ''}\" overflows its path (~${r0(estW)}px > ${r0(L)}px) — trailing glyphs are dropped`))\n }\n\n // (e) Drop zones (`hitbox`) that overlap -> ambiguous drop.\n const zones: { name: string; b: NonNullable<ReturnType<typeof dropZoneBounds>> }[] = []\n const collectZones = (layers: Layer[]) => {\n for (const l of layers) for (const it of l.items) {\n if (isGroup(it) && it.hitbox && it.name) { const b = dropZoneBounds(doc, it.name); if (b) zones.push({ name: it.name, b }) }\n if (isGroup(it)) collectZones(it.layers)\n }\n }\n collectZones(doc.layers)\n for (let i = 0; i < zones.length; i++) for (let j = i + 1; j < zones.length; j++) {\n if (bboxIntersects(zones[i].b, zones[j].b)) out.push(warn(`overlapping hitboxes: \"${zones[i].name}\" and \"${zones[j].name}\" -> ambiguous drop`))\n }\n return out\n}\n\n/** Semantic lint of a whole Doc (all the scopes) -> diagnostics labeled by scope.\n * A scope's text contains `object` blocks (outside DSL) -> we lint each region separately and re-project\n * the lines. `src` = the program SOURCE, when the caller has it (the CLI): the scene scope is then read\n * from it, so positions and scopes describe the author's file. Without it the scene scope falls back to\n * the text rebuilt from the Doc — right for the editor, where that rebuilt text IS the file being edited\n * (cf. CodeEditor.lintFile). Adds the structural warnings (phantom drop zones, dead variables). */\nexport function lintDoc(doc: Doc, src?: string): { scope: string; diag: Diagnostic }[] {\n const out: { scope: string; diag: Diagnostic }[] = []\n const allVars = allScopeVariables(doc) // computed ONCE (global vars -> known in all the scopes)\n for (const { label, editPath } of scopes(doc)) {\n const ctx = docLintContext(doc, editPath, allVars)\n // The SCENE scope is linted from the SOURCE whenever the caller has it (the CLI does). `scopeProgram`\n // rebuilds a text the author never wrote -- no `scene { … }` block -- so its line numbers are offset by\n // that block's height and its scope is always `scene`, never the `object \"X\"` the error is really in.\n // Symbols keep the reconstruction: they come from `.flat` libs, whose source is not this program's.\n const regions = src && editPath.length === 0\n ? behaviorRegions(src)\n : scopeRegions(scopeProgram(doc, editPath)).map((r) => ({ scope: label, body: r.body, line: r.line }))\n for (const r of regions) for (const d of lint(r.body, ctx)) out.push({ scope: r.scope, diag: { ...d, line: d.line + r.line - 1 } })\n }\n out.push(...docStructureWarnings(doc))\n out.push(...docModifierWarnings(doc))\n return out\n}\n\n/** Lint each stateful channel modifier's TARGET expression with its scope's known names (a symbol's params\n * included) — so a typo'd `spring rotation \"crochetXX\"` surfaces as \"unknown variable\" at `--check` time,\n * not a silent NaN at runtime. Also flags out-of-range spring damping (clamped at runtime, but worth noting). */\nexport function docModifierWarnings(doc: Doc): { scope: string; diag: Diagnostic }[] {\n const out: { scope: string; diag: Diagnostic }[] = []\n const allVars = allScopeVariables(doc)\n for (const { label, editPath } of scopes(doc)) {\n const ctx = docLintContext(doc, editPath, allVars)\n const visit = (items: Item[]): void => {\n for (const it of items) {\n if (isPoseable(it) && it.modifiers) for (const ch of EXPR_CHANNELS) {\n const m = it.modifiers[ch]\n if (!m) continue\n for (const d of lint(`${ch} = ${m.target}`, ctx)) out.push({ scope: label, diag: { ...d, line: 1, col: 1, message: `${m.kind} ${ch}: ${d.message}` } })\n if (m.kind === 'spring' && !(m.damping > 0 && m.damping < 1)) out.push({ scope: label, diag: { line: 1, col: 1, severity: 'warning', message: `spring ${ch}: damping ${m.damping} should be in (0,1) — clamped at runtime` } })\n }\n if (isGroup(it)) for (const l of it.layers) visit(l.items) // a group shares the scope; instances are linted on their own symbol pass\n }\n }\n visit(contextLayers(doc, editPath).flatMap((l) => l.items))\n // `velocity()` is valid ONLY in a modifier target (resolved by the player's stateful advance). scopeProgram\n // emits the scope's REGULAR expressions but NOT modifier targets, so any velocity() here is a misuse.\n if (/\\bvelocity\\s*\\(/.test(scopeProgram(doc, editPath)))\n out.push({ scope: label, diag: { line: 1, col: 1, message: 'velocity() is only valid inside a spring/smooth target, not a plain expression' } })\n }\n return out\n}\n\nconst sev = (d: Diagnostic) => (d.severity === 'warning' ? 'warning' : 'error')\n\n/** Lint report of a Doc ready to display: `''` = no issue, otherwise `[scope] line:col: level: message`.\n * Pass `src` (the program source) to get positions that point INTO IT rather than into a rebuilt program. */\nexport function lintDocReport(doc: Doc, src?: string): string {\n return lintDoc(doc, src).map(({ scope, diag }) => `[${scope}] ${diag.line}:${diag.col}: ${sev(diag)}: ${diag.message}`).join('\\n')\n}\n\n/** `true` if the Doc has at least one ERROR (warnings alone do not block). */\nexport function docHasErrors(doc: Doc, src?: string): boolean {\n return lintDoc(doc, src).some(({ diag }) => sev(diag) === 'error')\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// docWalk.ts — walking the behavior of a Doc: every action, every expression.\n//\n// A Doc scatters its logic across five places (item interactions, direct-manipulation interactors,\n// procedure bodies, the scene timeline's hooks, each symbol's timeline hooks) and its expressions\n// across two more (channel bindings and stateful modifier targets, at any depth in the group tree).\n// Anything that reasons about \"what does this program read / write / emit\" has to visit all of them,\n// and missing one is a silent hole. Collected here once so the linter and the manifest agree.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Action, Doc, Item, Layer } from '@flatkit/types'\nimport { isGroup, isPoseable } from '@flatkit/engine/layers'\nimport { EXPR_CHANNELS } from '@flatkit/engine/timeline'\n\n/** Every action of a body, nested `if` / `repeat` bodies included. */\nexport function forEachActionIn(actions: Action[] | undefined, fn: (a: Action) => void): void {\n for (const a of actions ?? []) {\n fn(a)\n if (a.do === 'if') { forEachActionIn(a.then, fn); forEachActionIn(a.else, fn) }\n else if (a.do === 'repeat' || a.do === 'repeatRange') forEachActionIn(a.body, fn)\n }\n}\n\n/** Every ACTION of the Doc: item events, procedure bodies, and the load / enter-frame / at-frame hooks\n * of the scene timeline and of every symbol's. */\nexport function forEachAction(doc: Doc, fn: (a: Action) => void): void {\n for (const i of doc.interactions ?? []) forEachActionIn(i.actions, fn)\n for (const f of doc.functions ?? []) if (f.kind === 'proc') forEachActionIn(f.body, fn)\n for (const t of [doc.timeline, ...(doc.symbols ?? []).map((s) => s.timeline)]) {\n if (!t) continue\n forEachActionIn(t.onLoad, fn)\n forEachActionIn(t.onEnterFrame, fn)\n for (const fa of t.frameActions ?? []) forEachActionIn(fa.actions, fn)\n }\n}\n\n/** Every expression an action carries: assigned values, conditions, loop bounds, call arguments, payloads. */\nexport function forEachActionExpression(actions: Action[] | undefined, fn: (expr: string) => void): void {\n forEachActionIn(actions, (a) => {\n if (a.do === 'setVar' || a.do === 'setParam') fn(a.value)\n else if (a.do === 'setIndex') { fn(a.value); fn(a.index) }\n else if (a.do === 'if') fn(a.cond)\n else if (a.do === 'repeat') fn(a.count)\n else if (a.do === 'repeatRange') { fn(a.from); fn(a.to) }\n else if (a.do === 'call') for (const arg of a.args) fn(arg)\n else if (a.do === 'send' && a.payload?.kind === 'expr') fn(a.payload.expr)\n else if (a.do === 'send' && a.payload?.kind === 'record') for (const f of a.payload.fields) fn(f.expr)\n })\n}\n\n/** The channel bindings and modifier targets carried by one item. */\nexport function forEachItemExpression(it: Item, fn: (expr: string, channel: string) => void): void {\n if (!isPoseable(it)) return\n if (it.expressions) for (const k of Object.keys(it.expressions)) { const e = it.expressions[k as keyof typeof it.expressions]; if (e) fn(e, k) }\n if (it.modifiers) for (const ch of EXPR_CHANNELS) { const m = it.modifiers[ch]; if (m) fn(m.target, ch) }\n}\n\n/** Every EXPRESSION text of the Doc: channel bindings and modifier targets (scene + symbols, nested\n * groups included), plus everything the actions carry. */\nexport function forEachExpression(doc: Doc, fn: (expr: string) => void): void {\n const visit = (items: Item[]): void => {\n for (const it of items) {\n forEachItemExpression(it, (e) => fn(e))\n if (isGroup(it)) for (const l of it.layers) visit(l.items)\n }\n }\n for (const l of doc.layers) visit(l.items)\n for (const s of doc.symbols ?? []) for (const l of s.layers) visit(l.items)\n for (const i of doc.interactions ?? []) forEachActionExpression(i.actions, fn)\n for (const f of doc.functions ?? []) if (f.kind === 'proc') forEachActionExpression(f.body, fn)\n for (const t of [doc.timeline, ...(doc.symbols ?? []).map((s) => s.timeline)]) {\n if (!t) continue\n forEachActionExpression(t.onLoad, fn)\n forEachActionExpression(t.onEnterFrame, fn)\n for (const fa of t.frameActions ?? []) forEachActionExpression(fa.actions, fn)\n }\n}\n\n/** Every named item of the scene tree, with the layers it owns. Positions are deliberately NOT part of\n * this: what a host needs to honour is the set of NAMES, never where the file happens to place them. */\nexport function forEachNamedItem(layers: Layer[], fn: (it: Item) => void): void {\n for (const l of layers) for (const it of l.items) {\n fn(it)\n if (isGroup(it)) forEachNamedItem(it.layers, fn)\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// check.ts — `flatc --check`, as a function.\n//\n// The compiled `Doc` is NOT enough to validate a program. Two whole classes of error live in the\n// SOURCE and are gone by the time the Doc exists:\n// • statements the parser DROPS (an unknown top-level statement, a malformed line inside a block) —\n// a text that is not FlatInk at all compiles to an empty Doc that nothing distinguishes from a\n// valid one;\n// • an `object \"X\" { … }` block that binds to NOTHING (X is a shape, a layer, or absent) — the\n// binding simply never lands, and the Doc holds no trace of the attempt.\n// Both are source-level passes (`behaviorDiagnostics`, `objectTargetDiagnostics`) that the CLI ran and\n// no public call could reach: an integrator validating in a service got a verdict weaker than the same\n// file checked at the prompt, and had to shell out to `flatc` to get the real one.\n//\n// `checkProgram(src)` runs the WHOLE pass — source in, diagnostics out, no filesystem, no subprocess.\n// The CLI calls the same function, so the two cannot drift.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Doc } from '@flatkit/types'\nimport { behaviorDiagnostics, objectTargetDiagnostics } from '@flatkit/engine/flatFormat'\nimport { compileFlatpack, type MediaMap } from './compile'\nimport { lintDoc } from './programDoc'\n\n/** One diagnostic, positioned in the AUTHOR'S source. `scope` is `scene` or `object \"Name\"`. */\nexport type CheckDiagnostic = { scope: string; line: number; col: number; severity: 'error' | 'warning'; message: string }\n\nexport type CheckOptions = {\n /** Text of each `.flat` symbol library the program draws on — the CLI auto-discovers them beside the file. */\n assetSrcs?: string[]\n /** Declared media, by path (only affects the returned Doc; diagnostics do not depend on it). */\n media?: MediaMap\n}\n\nexport type CheckResult = {\n /** `false` as soon as one diagnostic is an error — the exit code of `flatc --check`. Warnings do not block. */\n ok: boolean\n errors: number\n warnings: number\n diagnostics: CheckDiagnostic[]\n /** The diagnostics as `flatc` prints them (`''` when there is nothing to say). */\n report: string\n /** The compiled program, or `null` if the parser rejected the source outright. */\n doc: Doc | null\n}\n\nconst line1 = (d: CheckDiagnostic): string => `[${d.scope}] ${d.line}:${d.col}: ${d.severity}: ${d.message}`\n\n/** The diagnostics as `flatc` prints them: one `[scope] line:col: level: message` per line. */\nexport const formatDiagnostics = (diagnostics: CheckDiagnostic[]): string => diagnostics.map(line1).join('\\n')\n\n/** The two SOURCE-level passes, which read the author's text rather than the compiled Doc. Always errors. */\nfunction sourceDiagnostics(src: string): CheckDiagnostic[] {\n return [...behaviorDiagnostics(src), ...objectTargetDiagnostics(src)]\n .map(({ scope, diag }) => ({ scope, line: diag.line, col: diag.col, severity: 'error' as const, message: diag.message }))\n}\n\n/**\n * Every diagnostic of a program that HAS compiled: the source-level passes, then the semantic lint of the\n * whole Doc (read against `src`, so positions point into the author's file). Exact-duplicate lines are\n * dropped — a scene parse error is legitimately seen by both paths, which now both read the source.\n */\nexport function programDiagnostics(doc: Doc, src: string): CheckDiagnostic[] {\n const out: CheckDiagnostic[] = []\n const seen = new Set<string>()\n const push = (d: CheckDiagnostic) => { const k = line1(d); if (!seen.has(k)) { seen.add(k); out.push(d) } }\n for (const d of sourceDiagnostics(src)) push(d)\n for (const { scope, diag } of lintDoc(doc, src)) push({ scope, line: diag.line, col: diag.col, severity: diag.severity === 'warning' ? 'warning' : 'error', message: diag.message })\n return out\n}\n\nconst tally = (diagnostics: CheckDiagnostic[], doc: Doc | null): CheckResult => {\n const errors = diagnostics.filter((d) => d.severity === 'error').length\n return { ok: errors === 0, errors, warnings: diagnostics.length - errors, diagnostics, report: formatDiagnostics(diagnostics), doc }\n}\n\n/**\n * Check a `.flatink` program — the same pass as `flatc --check`, on a string. Never throws: a source the\n * parser rejects outright comes back as a diagnostic with `doc: null`, not an exception.\n *\n * ```ts\n * const { ok, report } = checkProgram(srcFromAnLLM)\n * if (!ok) regenerate(report) // the report is the repair prompt\n * ```\n */\nexport function checkProgram(src: string, opts: CheckOptions = {}): CheckResult {\n let doc: Doc\n try {\n doc = compileFlatpack(src, opts.assetSrcs ?? [], opts.media ?? {})\n } catch (e) {\n // The parser gives up without a position (it stops at the offending token, not at a line) — report it\n // at the top of the file rather than inventing coordinates.\n return tally([{ scope: 'scene', line: 1, col: 1, severity: 'error', message: `compile error: ${(e as Error).message}` }], null)\n }\n return tally(programDiagnostics(doc, src), doc)\n}\n"],"mappings":"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","names":["isGroup","isPoseable","EXPR_CHANNELS","parseUnits","isGroup","isPoseable","EXPR_CHANNELS","names"]}
package/dist/cli/flatc.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  run
3
- } from "../chunk-AVUH5CVJ.js";
4
- import "../chunk-VB3ZWCNP.js";
3
+ } from "../chunk-5WQFNGZQ.js";
4
+ import "../chunk-7KBWT2PA.js";
5
5
  import "../chunk-KXKX2RMX.js";
6
6
  export {
7
7
  run
package/dist/index.d.ts CHANGED
@@ -1,25 +1,59 @@
1
1
  export { MediaMap, compileFlatpack, packToJSON } from './compile.js';
2
- export { LintContext, allScopeVariables, docHasErrors, docLayoutWarnings, docLintContext, docStructureWarnings, formatObjectBlock, joinScopeProgram, lint, lintDoc, lintDocReport, lintReport, localVariables, scopeProgram, scopeRegions, splitScopeProgram } from './analysis.js';
3
- import { Doc } from '@flatkit/types';
2
+ export { CheckDiagnostic, CheckOptions, CheckResult, LintContext, allScopeVariables, checkProgram, docHasErrors, docLayoutWarnings, docLintContext, docStructureWarnings, formatDiagnostics, formatObjectBlock, joinScopeProgram, lint, lintDoc, lintDocReport, lintReport, localVariables, programDiagnostics, scopeProgram, scopeRegions, splitScopeProgram } from './analysis.js';
3
+ import { ItemEvent, Doc } from '@flatkit/types';
4
4
  export { run } from './cli/flatc.js';
5
5
  export { parseUnits, printUnits } from '@flatkit/engine/dsl';
6
6
  export { exportFlatProject, parseFlat, parseFlatLib, parseProgram, parseProgramFull, printProgram, printProgramFull } from '@flatkit/engine/flatFormat';
7
7
 
8
+ /**
9
+ * A named scene object and the CONTRACT the logic places on it — what a host must honour to reskin the
10
+ * scene without touching its behavior. Deliberately free of coordinates: the names and the roles are the
11
+ * contract, the composition stays entirely the skin's business. (A contract that carried positions would
12
+ * hand every reskin the same layout, which is exactly the failure it exists to prevent.)
13
+ */
8
14
  type ManifestObject = {
9
15
  name: string;
10
16
  kind: string;
17
+ /** Item events the logic handles on it (`click`, `drop`…) — the skin must keep them reachable. */
18
+ events: ItemEvent[];
19
+ /** A `drag`/`turn` interactor targets it: the pointer writes its position, so the skin must not pin it. */
20
+ dragged: boolean;
21
+ /** A placement target: something drops on it, or it carries a `hitbox`. Keep the name and a generous box. */
22
+ zone: boolean;
23
+ /** Channels the logic drives (bindings and modifier targets) — a skin that sets these fights the logic. */
24
+ channels: string[];
25
+ /** Document variables it reads: the game state a skin can hang its own visuals on. */
26
+ reads: string[];
11
27
  };
12
- /** Named scene objects (groups included, library symbols excluded), first name wins. */
28
+ /** Named scene objects (groups included, library symbols excluded) with their binding contract; first
29
+ * name wins, and nested groups are walked. */
13
30
  declare function manifestObjects(doc: Doc): ManifestObject[];
31
+ /** The events the program EMITS to its host (`send "…"`), in order of first appearance, deduped. The
32
+ * other half of the contract: the moments a host — or a skin's motion pass — can react to. */
33
+ declare function manifestEvents(doc: Doc): string[];
14
34
  /**
15
35
  * Compact map of the scene (objects/assets/variables/functions/packages) — injectable in a prompt.
16
36
  * Only non-empty sections appear. The names are the ones the code can reference.
17
37
  */
18
38
  declare function docToManifest(doc: Doc): string;
19
- /** Full LLM context: language reference (static) + scene map (derived from the Doc). */
20
- declare function llmContext(doc: Doc): string;
39
+ /**
40
+ * EVERYTHING a model needs to write a whole program: both language references (static) + the scene map
41
+ * derived from the Doc. It is the bundle, not the map — if you only want the names this scene can
42
+ * reference (because you already inject the references yourself), that is **`docToManifest(doc)`**, and
43
+ * calling this instead ships the cards a second time.
44
+ *
45
+ * The DRAWING card is included by default. Handed the behavior card alone, a model asked for decor
46
+ * invents a shapes grammar — and what it invents does not compile. Pass `{ drawing: false }` when the
47
+ * model only edits behavior and the prompt budget is tight.
48
+ */
49
+ declare function llmContext(doc: Doc, opts?: {
50
+ drawing?: boolean;
51
+ }): string;
21
52
 
22
53
  /** Language reference card (static, kept in sync with the engine via STD_*). */
23
54
  declare function languageCard(): string;
24
55
 
25
- export { type ManifestObject, docToManifest, languageCard, llmContext, manifestObjects };
56
+ /** Drawing reference card (composition: shapes, paints, filters, text, clipping). */
57
+ declare function drawingCard(): string;
58
+
59
+ export { type ManifestObject, docToManifest, drawingCard, languageCard, llmContext, manifestEvents, manifestObjects };
package/dist/index.js CHANGED
@@ -1,12 +1,17 @@
1
1
  import {
2
2
  run
3
- } from "./chunk-AVUH5CVJ.js";
3
+ } from "./chunk-5WQFNGZQ.js";
4
4
  import {
5
5
  allScopeVariables,
6
+ checkProgram,
6
7
  docHasErrors,
7
8
  docLayoutWarnings,
8
9
  docLintContext,
9
10
  docStructureWarnings,
11
+ forEachAction,
12
+ forEachActionExpression,
13
+ forEachItemExpression,
14
+ formatDiagnostics,
10
15
  formatObjectBlock,
11
16
  joinScopeProgram,
12
17
  lint,
@@ -14,10 +19,11 @@ import {
14
19
  lintDocReport,
15
20
  lintReport,
16
21
  localVariables,
22
+ programDiagnostics,
17
23
  scopeProgram,
18
24
  scopeRegions,
19
25
  splitScopeProgram
20
- } from "./chunk-VB3ZWCNP.js";
26
+ } from "./chunk-7KBWT2PA.js";
21
27
  import {
22
28
  compileFlatpack,
23
29
  packToJSON
@@ -26,6 +32,7 @@ import {
26
32
  // src/manifest.ts
27
33
  import { EXPR_CHANNELS as EXPR_CHANNELS2, OFFSET_CHANNELS } from "@flatkit/engine/timeline";
28
34
  import { getSymbol, isGroup, isImage, isInstance, isPoseable, isText } from "@flatkit/engine/layers";
35
+ import { analyzeExpr } from "@flatkit/engine/expr";
29
36
 
30
37
  // src/languageCard.ts
31
38
  import { STD_CONSTANTS, STD_FUNCTIONS } from "@flatkit/engine/expr";
@@ -36,6 +43,11 @@ function languageCard() {
36
43
  Numbers only (0 = false, anything else = true). No string type (only event/label/asset names in quotes). Comment: // to end of line.
37
44
  One statement per line \u2014 a newline ends the statement (write \`x = 1\` then \`y = 2\` on separate lines, not \`x = 1 y = 2\`).
38
45
 
46
+ ## Objects
47
+ object "Name" { \u2026 } attaches behavior BY NAME. The name must be a group / instance / text / image \u2014 the only things that carry a pose.
48
+ A SHAPE (\`rect \u2026 as "N"\`) or a LAYER cannot be animated: wrap the shape in \`group "N" { layer "art" { \u2026 } }\`. (Naming one is a compile error.)
49
+ Opacities MULTIPLY down the tree \u2014 do not set \`opacity 0\` on the shape to hide it at rest, or the group's fade-in stays invisible.
50
+
39
51
  ## Events (attach to an object or to the scene)
40
52
  when loaded { } // once, on start
41
53
  every frame { } // every frame
@@ -54,7 +66,9 @@ channel = expr channels: ${EXPR_CHANNELS.join(" ")} (expression wins o
54
66
  dx = expr \xB7 dy = expr // ADDITIVE offset: final pos = at + (dx, dy) \u2014 oscillate AROUND the anchor (dx = 30*sin(time)); absolute x/y REPLACE at
55
67
  rotationDeg = expr // sugar for rotation = rad(expr) \u2014 author angles in DEGREES (rotation & sin/cos/atan2 are RADIANS)
56
68
  operators: + - * / % < > <= >= == != && || ! cond ? a : b
57
- context: time frame value \xB7 mouse.x mouse.y mouse.dx mouse.dy \xB7 keys.Space keys.ArrowLeft \u2026 (keys are 1/0, use directly: keys.Space ? \u2026 : \u2026)
69
+ context: time frame clock value \xB7 mouse.x mouse.y mouse.dx mouse.dy \xB7 keys.Space keys.ArrowLeft \u2026 (keys are 1/0, use directly: keys.Space ? \u2026 : \u2026)
70
+ time WRAPS every durationFrames (2.5 s by default) \u2014 clock is MONOTONE. Timestamps you capture and compare later MUST use clock
71
+ (\`when wrong { shown = clock }\` + \`opacity = pulse(shown, 4)\`), or the ramp replays on every loop.
58
72
  Name.x Name.y Name.rotation Name.scaleX Name.scaleY Name.opacity // any named object (identifier name), live on-screen value (read-only)
59
73
  self.x self.y self.rotation self.scaleX self.scaleY self.opacity // the object's own channels, in its bindings (no mirror variable)
60
74
 
@@ -67,6 +81,7 @@ functions: ${STD_FUNCTIONS.join(" ")}
67
81
 
68
82
  ## Direct manipulation (interactors)
69
83
  drag x, y // object follows the pointer while held \u2192 writes vars x, y (bind them: x = vx, y = vy)
84
+ { enabled <expr> } gates the GESTURE only \u2014 when/pressed/released/clicked STILL fire. Guard the body: when released { if done == 0 { \u2026 } }
70
85
  dragX vx \xB7 dragY vy // single-axis
71
86
  drag x, y { confine to Zone snap 10 } // bound to a named object's box \xB7 grid snap
72
87
  turn a around cx,cy { snap 15 } // dial/knob \u2192 a = pivot\u2192cursor angle in RADIANS (pair: rotation = a)
@@ -78,7 +93,7 @@ let name = 0 \xB7 let arr = fill(n, v) \xB7 let arr = [a, b, c]
78
93
  fn name(a, b) = expr // value function (use in expressions)
79
94
  fn name() { \u2026 } // procedure (block of actions)
80
95
  each "Symbol" as i { opacity = data[i] } // bind every instance of a symbol (i = index)
81
- use "package" packages: ${PACKAGES.join(" ")}
96
+ use "package" packages: ${PACKAGES.join(" ")} // OPTIONAL: calling a package function imports it automatically
82
97
 
83
98
  ## Example
84
99
  let score = 0
@@ -89,6 +104,119 @@ object "Ball" {
89
104
  }`;
90
105
  }
91
106
 
107
+ // src/drawingCard.ts
108
+ function drawingCard() {
109
+ return `# FlatInk \u2014 drawing (the \`scene { \u2026 }\` half)
110
+
111
+ The unit is the LAYER: \`layer "name" { \u2026 }\`, stacked bottom to top. A layer holds shapes, text, images
112
+ and groups (which nest their own layers). Coordinates are PIXELS in the parent's frame \u2014 never percentages.
113
+
114
+ ## Shapes
115
+ circle <cx> <cy> <r>
116
+ ellipse <cx> <cy> <rx> <ry>
117
+ rect <x> <y> <w> <h> [<r>] // r = rounded corners (or <rx> <ry> for distinct)
118
+ path "M0 0 C40 -20 80 20 120 0 Z" // raw SVG path data \u2014 total freedom
119
+ circle 100 100 40 as "Ring" // name it (right after the geometry) \u2192 addressable
120
+
121
+ ## Paint
122
+ fill #rrggbb | #rrggbbaa \xB7 nofill
123
+ fill linear(<angle>, 0:#\u2026, 1:#\u2026) // angle: 0 = \u2192, 90 = \u2193 ; stops are offset:color
124
+ fill radial(<cx>, <cy>, <r>, 0:#\u2026, 1:#\u2026) // cx/cy/r as 0..1 of the box
125
+ stroke <paint> <width> [cap butt|round|square] [join miter|round|bevel] [miter <n>] [dash a,b]
126
+ opacity <0..1> // opacities MULTIPLY down the tree
127
+
128
+ ## Filters \u2014 on any item, no wrapper group needed
129
+ filter glow <blur> <color> \xB7 filter shadow <dx> <dy> <blur> <color>
130
+ filter blur <radius> \xB7 filter adjust <brightness> <contrast> <saturate> <hue>
131
+
132
+ ## Text
133
+ text "\u2026" at <x>,<y> box <w> <h> font "sans-serif" size <n> align center line 1.2 color #\u2026 [bold] [italic] [wrap]
134
+ text "\u2026" along "<shapeId>" align center start 0.5 // laid along a named shape's curve
135
+ Word-wrap is OPT-IN: without \`wrap\`, only an explicit \\n breaks a line.
136
+
137
+ ## Images
138
+ image "<assetId>" <w> <h> at <x>,<y> // the asset is declared at the top: asset "logo" "logo.svg" image
139
+
140
+ ## Clipping
141
+ group "Name" at x,y clip <x> <y> <w> <h> { \u2026 } // rectangular cut, in the group's LOCAL coordinates
142
+ mask layer "Name" { <shapes> layer "c" { \u2026 } } // arbitrary shape: the mask's matter clips its child layers
143
+
144
+ ## The five rules that decide whether it compiles
145
+ 1. WORD ORDER IS FIXED: content \u2192 \`as "\u2026"\` \u2192 \`at \u2026\` \u2192 style. So \`text "Hi" at 10,10 box 200 40\`,
146
+ NEVER \`text "Hi" box 200 40 at 10,10\`. A shape names itself right after its geometry.
147
+ Stroke options (\`cap\`/\`join\`/\`miter\`/\`dash\`) belong to the STROKE and follow it directly:
148
+ \`nofill stroke #888 2 dash 6,5\`, not \`stroke #888 2 nofill dash 6,5\`.
149
+ 2. A COMMENT STARTS WITH \`//\`. A \`#\` opens a COLOR.
150
+ 3. ONE STATEMENT PER LINE.
151
+ 4. Anything you intend to ANIMATE must be a \`group "Name" at x,y { layer "c" { \u2026 } }\`. An \`object\`
152
+ block aimed at a shape or a layer is a compile ERROR \u2014 shapes are baked material and carry no pose.
153
+ 5. A group's children are positioned RELATIVE to it. Draw them around 0,0 and place the group.
154
+
155
+ ## Examples
156
+
157
+ A sky and a ground, in two gradients:
158
+
159
+ \`\`\`flatink
160
+ size 960 540
161
+ scene {
162
+ layer "sky" {
163
+ rect 0 0 960 540 fill linear(90, 0:#1b2a4a, 1:#4a6fa5)
164
+ ellipse 780 90 70 70 fill radial(0.5, 0.5, 0.5, 0:#ffe9a8, 1:#ffe9a800)
165
+ }
166
+ layer "ground" {
167
+ path "M0 430 C240 400 420 460 960 415 L960 540 L0 540 Z" fill #2e4b34 opacity 0.85
168
+ }
169
+ }
170
+ \`\`\`
171
+
172
+ A line-art silhouette and a drop shadow:
173
+
174
+ \`\`\`flatink
175
+ size 200 400
176
+ scene {
177
+ layer "tree" {
178
+ path "M60 300 L60 180 M60 220 L20 170 M60 230 L100 180" nofill stroke #23301f 8 cap round
179
+ circle 60 150 60 fill #3f6b3a filter shadow 0 6 12 #00000055
180
+ }
181
+ }
182
+ \`\`\`
183
+
184
+ A group \u2014 the only shape a behavior block can animate:
185
+
186
+ \`\`\`flatink
187
+ size 480 320
188
+ scene {
189
+ layer "life" {
190
+ group "Cloud" at 240,120 {
191
+ layer "c" {
192
+ ellipse 0 0 70 26 fill #ffffff opacity 0.22
193
+ ellipse 46 -10 44 22 fill #ffffff opacity 0.18
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ object "Cloud" {
200
+ dx = 30 * sin(clock)
201
+ }
202
+ \`\`\`
203
+
204
+ A panel, text, and a clipped porthole:
205
+
206
+ \`\`\`flatink
207
+ size 960 540
208
+ scene {
209
+ layer "panel" {
210
+ rect 40 40 300 90 24 fill #10141c99 stroke #ffffff33 2
211
+ text "Workshop" at 60,66 box 260 40 font "sans-serif" size 26 color #f2f6ff
212
+ group "Porthole" at 700,300 clip -60 -60 120 120 {
213
+ layer "c" { circle 0 0 90 fill linear(0, 0:#0b3d5c, 1:#0f6f92) }
214
+ }
215
+ }
216
+ }
217
+ \`\`\``;
218
+ }
219
+
92
220
  // src/manifest.ts
93
221
  function kindLabel(doc, it) {
94
222
  if (isInstance(it)) {
@@ -99,14 +227,46 @@ function kindLabel(doc, it) {
99
227
  if (isImage(it)) return "Image";
100
228
  return "Symbol";
101
229
  }
230
+ function stateVariables(doc) {
231
+ const out = new Set(Object.keys(doc.variables ?? {}));
232
+ forEachAction(doc, (a) => {
233
+ if (a.do === "setVar" || a.do === "setIndex") out.add(a.name);
234
+ else if (a.do === "repeatRange") out.add(a.var);
235
+ });
236
+ for (const i of doc.interactors ?? []) for (const v of [i.varX, i.varY, i.varT]) if (v) out.add(v);
237
+ return out;
238
+ }
239
+ function readsOf(expr, state, into) {
240
+ const a = analyzeExpr(expr);
241
+ if (!a.ok) return;
242
+ for (const id of a.refs.ids) if (state.has(id)) into.add(id);
243
+ }
102
244
  function manifestObjects(doc) {
103
245
  const out = [];
104
246
  const seen = /* @__PURE__ */ new Set();
247
+ const state = stateVariables(doc);
248
+ const dropZones = new Set((doc.interactions ?? []).filter((i) => i.event === "drop" && i.over).map((i) => i.over));
105
249
  const walk = (layers) => {
106
250
  for (const l of layers) for (const it of l.items) {
107
251
  if (isPoseable(it) && it.name && !seen.has(it.name)) {
108
252
  seen.add(it.name);
109
- out.push({ name: it.name, kind: kindLabel(doc, it) });
253
+ const mine = (doc.interactions ?? []).filter((i) => i.targetId === it.id);
254
+ const channels = [];
255
+ const reads = /* @__PURE__ */ new Set();
256
+ forEachItemExpression(it, (expr, channel) => {
257
+ channels.push(channel);
258
+ readsOf(expr, state, reads);
259
+ });
260
+ for (const i of mine) forEachActionExpression(i.actions, (expr) => readsOf(expr, state, reads));
261
+ out.push({
262
+ name: it.name,
263
+ kind: kindLabel(doc, it),
264
+ events: [...new Set(mine.map((i) => i.event))],
265
+ dragged: !!doc.interactors?.some((i) => i.targetId === it.id),
266
+ zone: dropZones.has(it.name) || isGroup(it) && !!it.hitbox,
267
+ channels,
268
+ reads: [...reads]
269
+ });
110
270
  }
111
271
  if (isGroup(it)) walk(it.layers);
112
272
  }
@@ -114,6 +274,22 @@ function manifestObjects(doc) {
114
274
  walk(doc.layers);
115
275
  return out;
116
276
  }
277
+ function manifestEvents(doc) {
278
+ const events = /* @__PURE__ */ new Set();
279
+ forEachAction(doc, (a) => {
280
+ if (a.do === "send") events.add(a.event);
281
+ });
282
+ return [...events];
283
+ }
284
+ function contractParts(o) {
285
+ const parts = [];
286
+ if (o.dragged) parts.push("drag");
287
+ if (o.zone) parts.push("zone");
288
+ if (o.events.length) parts.push(`on ${o.events.join("/")}`);
289
+ if (o.channels.length) parts.push(`driven: ${o.channels.join(" ")}`);
290
+ if (o.reads.length) parts.push(`reads: ${o.reads.join(" ")}`);
291
+ return parts;
292
+ }
117
293
  function manifestVars(doc) {
118
294
  return Object.entries(doc.variables ?? {}).map(([k, v]) => Array.isArray(v) ? `${k}[${v.length}]` : `${k}=${v}`);
119
295
  }
@@ -128,11 +304,21 @@ function docToManifest(doc) {
128
304
  if (vars.length) lines.push(`vars: ${vars.join(", ")}`);
129
305
  if (funcs.length) lines.push(`funcs: ${funcs.join(", ")}`);
130
306
  if (doc.imports?.length) lines.push(`packages: ${doc.imports.join(", ")}`);
307
+ const contract = objs.map((o) => ({ o, parts: contractParts(o) })).filter(({ parts }) => parts.length);
308
+ if (contract.length) {
309
+ lines.push("contract (honour these; the layout is yours):");
310
+ for (const { o, parts } of contract) lines.push(` ${o.name} - ${parts.join(", ")}`);
311
+ }
312
+ const events = manifestEvents(doc);
313
+ if (events.length) lines.push(`events: ${events.join(", ")}`);
131
314
  lines.push(`channels: ${EXPR_CHANNELS2.join(" ")} (additive offsets: ${OFFSET_CHANNELS.join(" ")} -> pos = at + d)`);
132
315
  return lines.join("\n");
133
316
  }
134
- function llmContext(doc) {
135
- return `${languageCard()}
317
+ function llmContext(doc, opts = {}) {
318
+ const cards = opts.drawing === false ? languageCard() : `${languageCard()}
319
+
320
+ ${drawingCard()}`;
321
+ return `${cards}
136
322
 
137
323
  ${docToManifest(doc)}`;
138
324
  }
@@ -150,13 +336,16 @@ import {
150
336
  } from "@flatkit/engine/flatFormat";
151
337
  export {
152
338
  allScopeVariables,
339
+ checkProgram,
153
340
  compileFlatpack,
154
341
  docHasErrors,
155
342
  docLayoutWarnings,
156
343
  docLintContext,
157
344
  docStructureWarnings,
158
345
  docToManifest,
346
+ drawingCard,
159
347
  exportFlatProject,
348
+ formatDiagnostics,
160
349
  formatObjectBlock,
161
350
  joinScopeProgram,
162
351
  languageCard,
@@ -166,6 +355,7 @@ export {
166
355
  lintReport,
167
356
  llmContext,
168
357
  localVariables,
358
+ manifestEvents,
169
359
  manifestObjects,
170
360
  packToJSON,
171
361
  parseFlat,
@@ -176,6 +366,7 @@ export {
176
366
  printProgram,
177
367
  printProgramFull,
178
368
  printUnits,
369
+ programDiagnostics,
179
370
  run,
180
371
  scopeProgram,
181
372
  scopeRegions,