@flatkit/compiler 0.23.0 → 0.25.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/analysis.d.ts +47 -2
- package/dist/analysis.js +8 -1
- package/dist/{chunk-5PA2W4V3.js → chunk-GAS27LSJ.js} +11 -9
- package/dist/chunk-GAS27LSJ.js.map +1 -0
- package/dist/{chunk-HRWULWIL.js → chunk-MRRRN2CS.js} +356 -118
- package/dist/chunk-MRRRN2CS.js.map +1 -0
- package/dist/cli/flatc.js +2 -2
- package/dist/index.d.ts +40 -6
- package/dist/index.js +188 -5
- package/dist/index.js.map +1 -1
- package/package.json +12 -8
- package/prompts/README.md +45 -0
- package/prompts/flatink-core.md +273 -0
- package/prompts/flatink-lite.md +132 -0
- package/prompts/role-asset-creator.md +97 -0
- package/prompts/role-coder.md +180 -0
- package/prompts/role-motion-designer.md +171 -0
- package/dist/chunk-5PA2W4V3.js.map +0 -1
- package/dist/chunk-HRWULWIL.js.map +0 -1
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":";;;;;;;;;;;;;;;;;;;;;;;;;;AAWA,SAAS,iBAAAA,gBAAe,uBAAuB;AAC/C,SAAS,WAAW,SAAS,SAAS,YAAY,YAAY,cAAc;;;ACJ5E,SAAS,eAAe,qBAAqB;AAC7C,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AAGlB,SAAS,eAAuB;AACrC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAuB0B,cAAc,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAc7C,cAAc,KAAK,GAAG,CAAC;AAAA,aACvB,cAAc,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAgBD,SAAS,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASrD;;;AD7DA,SAAS,UAAU,KAAU,IAA6C;AACxE,MAAI,WAAW,EAAE,GAAG;AAAE,UAAM,IAAI,UAAU,KAAK,GAAG,QAAQ;AAAG,WAAO,IAAI,YAAY,EAAE,IAAI,KAAK;AAAA,EAAW;AAC1G,MAAI,OAAO,EAAE,EAAG,QAAO;AACvB,MAAI,QAAQ,EAAE,EAAG,QAAO;AACxB,SAAO;AACT;AAKO,SAAS,gBAAgB,KAA4B;AAC1D,QAAM,MAAwB,CAAC;AAC/B,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,OAAO,CAAC,WAAoB;AAChC,eAAW,KAAK,OAAQ,YAAW,MAAM,EAAE,OAAO;AAChD,UAAI,WAAW,EAAE,KAAK,GAAG,QAAQ,CAAC,KAAK,IAAI,GAAG,IAAI,GAAG;AAAE,aAAK,IAAI,GAAG,IAAI;AAAG,YAAI,KAAK,EAAE,MAAM,GAAG,MAAM,MAAM,UAAU,KAAK,EAAE,EAAE,CAAC;AAAA,MAAE;AAChI,UAAI,QAAQ,EAAE,EAAG,MAAK,GAAG,MAAM;AAAA,IACjC;AAAA,EACF;AACA,OAAK,IAAI,MAAM;AACf,SAAO;AACT;AAGA,SAAS,aAAa,KAAoB;AACxC,SAAO,OAAO,QAAQ,IAAI,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAO,MAAM,QAAQ,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,EAAG;AACnH;AAMO,SAAS,cAAc,KAAkB;AAC9C,QAAM,OAAO,gBAAgB,GAAG;AAChC,QAAM,UAAU,IAAI,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,EAAE,EAAE;AAChE,QAAM,OAAO,aAAa,GAAG;AAC7B,QAAM,SAAS,IAAI,aAAa,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG;AAClF,QAAM,QAAQ,CAAC,WAAW,SAAS,IAAI,KAAK,IAAI,IAAI,MAAM,EAAE;AAC5D,MAAI,KAAK,OAAQ,OAAM,KAAK,YAAY,KAAK,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,IAAI,GAAG,EAAE,KAAK,IAAI,CAAC,EAAE;AAC5F,MAAI,OAAO,OAAQ,OAAM,KAAK,WAAW,OAAO,KAAK,IAAI,CAAC,EAAE;AAC5D,MAAI,KAAK,OAAQ,OAAM,KAAK,SAAS,KAAK,KAAK,IAAI,CAAC,EAAE;AACtD,MAAI,MAAM,OAAQ,OAAM,KAAK,UAAU,MAAM,KAAK,IAAI,CAAC,EAAE;AACzD,MAAI,IAAI,SAAS,OAAQ,OAAM,KAAK,aAAa,IAAI,QAAQ,KAAK,IAAI,CAAC,EAAE;AACzE,QAAM,KAAK,aAAaC,eAAc,KAAK,GAAG,CAAC,wBAAwB,gBAAgB,KAAK,GAAG,CAAC,mBAAmB;AACnH,SAAO,MAAM,KAAK,IAAI;AACxB;AAGO,SAAS,WAAW,KAAkB;AAC3C,SAAO,GAAG,aAAa,CAAC;AAAA;AAAA,EAAO,cAAc,GAAG,CAAC;AACnD;;;AEnCA,SAAS,YAAY,kBAAkB;AACvC;AAAA,EACE;AAAA,EAAc;AAAA,EAAc;AAAA,EAAkB;AAAA,EAC9C;AAAA,EAAW;AAAA,EAAc;AAAA,OACpB;","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.
|
|
3
|
+
"version": "0.25.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.
|
|
49
|
-
"@flatkit/
|
|
50
|
-
"@flatkit/
|
|
52
|
+
"@flatkit/engine": "0.25.0",
|
|
53
|
+
"@flatkit/types": "0.25.0",
|
|
54
|
+
"@flatkit/player": "0.25.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.
|