@flatkit/compiler 0.25.0 → 0.27.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flatkit/compiler",
3
- "version": "0.25.0",
3
+ "version": "0.27.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",
@@ -38,7 +38,14 @@
38
38
  "types": "./dist/analysis.d.ts",
39
39
  "import": "./dist/analysis.js",
40
40
  "require": "./dist/analysis.js"
41
- }
41
+ },
42
+ "./render": {
43
+ "types": "./dist/cli/render.d.ts",
44
+ "import": "./dist/cli/render.js",
45
+ "require": "./dist/cli/render.js"
46
+ },
47
+ "./prompts/*": "./prompts/*",
48
+ "./docs/*": "./docs/*"
42
49
  },
43
50
  "bin": {
44
51
  "flatc": "./bin/flatc.mjs"
@@ -46,12 +53,13 @@
46
53
  "files": [
47
54
  "dist",
48
55
  "bin",
49
- "prompts"
56
+ "prompts",
57
+ "docs"
50
58
  ],
51
59
  "dependencies": {
52
- "@flatkit/engine": "0.25.0",
53
- "@flatkit/types": "0.25.0",
54
- "@flatkit/player": "0.25.0"
60
+ "@flatkit/engine": "0.27.0",
61
+ "@flatkit/types": "0.27.0",
62
+ "@flatkit/player": "0.27.0"
55
63
  },
56
64
  "peerDependencies": {
57
65
  "skia-canvas": "^3.0.8"
@@ -65,7 +73,7 @@
65
73
  "access": "public"
66
74
  },
67
75
  "scripts": {
68
- "build": "tsup",
76
+ "build": "tsup && node ../../scripts/sync-docs.mjs",
69
77
  "typecheck": "tsc -p tsconfig.json"
70
78
  },
71
79
  "module": "./dist/index.js"
@@ -18,25 +18,25 @@ A `.flatink` is split in **two halves with different grammars**:
18
18
  ## `.flatink` skeleton
19
19
 
20
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
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
27
 
28
28
  scene {
29
29
  layer "bg" { rect 0 0 480 320 fill #0a0e1c }
30
30
  layer "game" {
31
- circle 240 160 40 fill #ffcc00 as "Sun"
31
+ group "Sun" at 240,160 { layer "art" { circle 0 0 40 fill #ffcc00 } }
32
32
  }
33
33
  }
34
34
 
35
- object "Sun" {
35
+ object "Sun" { // behavior attaches BY NAME, to a group -- never to a bare shape
36
36
  when clicked { score = score + 1 }
37
- rotation = time * 30
37
+ rotationDeg = clock * 30 // degrees; `clock` is monotone, `time` restarts every loop
38
38
  }
39
- every frame { if (score >= 10) { send "win" } }
39
+ every frame { if score >= 10 { send "win" } }
40
40
  ```
41
41
 
42
42
  ## Drawing (inside `scene`/`symbol` layers)
@@ -46,24 +46,24 @@ Coordinates are plain numbers; canvas origin is **top-left**. Layers stack botto
46
46
  ```
47
47
  circle <cx> <cy> <r>
48
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
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
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
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
55
  ```
56
56
 
57
57
  Paint / style (work on shapes; most on text & groups too):
58
58
  ```
59
59
  fill #rrggbb | nofill
60
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
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
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
65
+ tint <color> <amount(0..1)> // Flash-style tint
66
+ nohit // drawn but ignored by hit-test
67
67
  ```
68
68
 
69
69
  ## Animation — timeline / cel / pose (in a `symbol`)
@@ -74,13 +74,13 @@ the **poses** of containers declared once in the layer roster, plus (optionally)
74
74
 
75
75
  ```
76
76
  symbol "Wheel" {
77
- timeline 24 24 # fps, durationFrames
77
+ timeline 24 24 // fps, durationFrames
78
78
  layer "spin" {
79
- group "Rim" at 100,100 pivot 0,0 { # roster: declared ONCE, posed by the cels below
79
+ group "Rim" at 100,100 pivot 0,0 { // roster: declared ONCE, posed by the cels below
80
80
  layer "art" { circle 0 0 40 nofill stroke #333 8 }
81
81
  }
82
82
  cel 0 tween { pose "Rim" rotate 0 }
83
- cel 24 { pose "Rim" rotate 360 } # one full turn around the pivot, in DEGREES
83
+ cel 24 { pose "Rim" rotate 360 } // one full turn around the pivot, in DEGREES
84
84
  }
85
85
  }
86
86
 
@@ -117,9 +117,9 @@ Additive position offsets: `dx dy` → **`pos = at + (dx, dy)`** (binding-only;
117
117
 
118
118
  ```
119
119
  object "Needle" {
120
- rotation = atan2(mouse.y - 160, mouse.x - 240) # RADIANS
120
+ rotation = atan2(mouse.y - 160, mouse.x - 240) // RADIANS
121
121
  opacity = lit ? 1 : 0.3
122
- dx = 30 * sin(clock) # sways AROUND its declared at — no base to re-inject
122
+ dx = 30 * sin(clock) // sways AROUND its declared at — no base to re-inject
123
123
  }
124
124
  ```
125
125
  Or bind a channel on a symbol container directly: `group "Fan" pivot 0,0 expr rotation "turns(time)" { … }`.
@@ -128,9 +128,9 @@ Or bind a channel on a symbol container directly: `group "Fan" pivot 0,0 expr ro
128
128
  snapping to it. Per instance, zero cost when unused, and it snaps to the target on a seek (so tune it by
129
129
  PLAYING the preview, not by scrubbing).
130
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
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
134
  ```
135
135
  The quoted target's names must EXIST in that scope (a symbol `param`, a scene `var`) or `--check` errors.
136
136
 
@@ -145,37 +145,37 @@ Actions (one per line): `<var> = <expr>` · `arr[i] = <expr>` · `if/else if/els
145
145
 
146
146
  Drag & interactors (each writes into your vars; all accept `{ enabled <expr> }`):
147
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)
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
154
  ```
155
155
 
156
156
  State & helpers:
157
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")
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
163
  ```
164
164
 
165
165
  ## Factoring (compile-time, zero runtime cost)
166
166
 
167
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
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
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
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
174
  correct Word1 -> Good, Word2 -> Bad
175
175
  on done { send "win" }
176
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
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
179
  ```
180
180
 
181
181
  ## Expressions & stdlib
@@ -195,6 +195,9 @@ ramp over `dur` s for a readable timed feedback — capture the instant with **`
195
195
 
196
196
  ## CRITICAL GOTCHAS — do not get these wrong
197
197
 
198
+ 0. **A comment is `//`, everywhere.** `#` opens a COLOUR (`#ffcc00`). Used as a comment it survives in
199
+ the header half and is a parse ERROR inside `scene { … }` — reported as `"layer" expected, "#" found`,
200
+ which points nowhere near the real cause.
198
201
  1. **`size W H` is required and MUST be the first line** of a `.flatink`. A `.flat` has no `size`.
199
202
  2. **Two grammars.** Drawing keywords live in `scene`/`symbol` layers; logic keywords live in
200
203
  `object`/`every frame`/`fn`. Don't mix (no `var`/`when` inside `scene`; no `circle` inside `object`).
@@ -2,6 +2,7 @@
2
2
 
3
3
  You write **FlatInk**, a text language for animations & interactive scenes. Output **only valid source**
4
4
  in one fenced block. You won't have a compiler — get it right in one pass. Keep DSL keywords in English.
5
+ **Comments are `//`, everywhere. `#` opens a COLOUR, and breaks inside `scene { … }`.**
5
6
 
6
7
  ## Two file types
7
8
  - **`.flat`** = a symbol library: one or more `symbol "Name" { … }`. **No `size` line.** Not playable alone.
@@ -12,27 +13,29 @@ group/instance); everything after = behavior (`object`, `every frame`, `var`, `f
12
13
 
13
14
  ## `.flatink` skeleton
14
15
  ```
15
- size 480 320 # REQUIRED first line (canvas units; origin = top-left)
16
- background #0a0e1c # optional
17
- var score = 0 # optional global runtime state
16
+ size 480 320 // REQUIRED first line (canvas units; origin = top-left)
17
+ background #0a0e1c // optional
18
+ var score = 0 // optional global runtime state
18
19
  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
20
+ layer "bg" { rect 0 0 480 320 fill #0a0e1c } // layers stack bottom -> top
21
+ layer "game" {
22
+ group "Sun" at 240,160 { layer "art" { circle 0 0 40 fill #ffcc00 } }
23
+ }
21
24
  }
22
- object "Sun" { # behavior attaches by name
25
+ object "Sun" { // behavior attaches by name, to a GROUP (a bare shape is an error)
23
26
  when clicked { score = score + 1 }
24
- rotation = time * 30 # channel binding, every frame (RADIANS)
27
+ rotationDeg = clock * 30 // channel binding, every frame (degrees; `clock` never wraps)
25
28
  }
26
- every frame { if (score >= 10) { send "win" } }
29
+ every frame { if score >= 10 { send "win" } }
27
30
  ```
28
31
 
29
32
  ## Drawing (in scene/symbol layers)
30
33
  ```
31
34
  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
35
  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
+ image "id" w h at -w/2,-h/2 // origin top-left → center yourself ; needs: asset "id" "f.png" image
37
+ group "Name" at x,y pivot px,py { layer "c" { … } } // nests its own layers
38
+ instance "Symbol" as "Name" at x,y // place a symbol from a .flat
36
39
  ```
37
40
  Style: `fill #rrggbb | nofill` · `stroke #rgb <w> [cap round][join round][dash a,b]` · `opacity 0..1` ·
38
41
  `fill linear(90, 0:#a, 1:#b)` (0=→,90=↓) · `fill radial(0.5,0.5,0.5, 0:#fff,1:#000)` ·
@@ -41,13 +44,13 @@ Style: `fill #rrggbb | nofill` · `stroke #rgb <w> [cap round][join round][dash
41
44
  ## Animation (in a symbol): timeline / cel / pose
42
45
  ```
43
46
  symbol "Wheel" {
44
- timeline 24 24 # fps, durationFrames (loops [0,dur))
47
+ timeline 24 24 // fps, durationFrames (loops [0,dur))
45
48
  layer "spin" {
46
- group "Rim" at 100,100 pivot 0,0 { # roster: declared ONCE, posed by cels below
49
+ group "Rim" at 100,100 pivot 0,0 { // roster: declared ONCE, posed by cels below
47
50
  layer "art" { circle 0 0 40 nofill stroke #333 8 }
48
51
  }
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
52
+ cel 0 tween { pose "Rim" rotate 0 } // tween = interpolate to next cel; no tween = hold
53
+ cel 24 { pose "Rim" rotate 360 } // DEGREES, around the pivot
51
54
  }
52
55
  }
53
56
  pose "Name" [at x,y] [rotate deg] [scale s | scaleX sx scaleY sy] [opacity o] [spin cw|ccw] [turns n]
@@ -64,9 +67,9 @@ Actions (one per line): `<var> = <expr>` · `arr[i] = <expr>` · `if/else if/els
64
67
  `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
68
  Drag/interactors (write into your vars; all take `{ enabled <expr> }`):
66
69
  ```
67
- drag x, y [{ confine to <Zone> · snap <grid> }] # then USE them: x = px y = py
70
+ drag x, y [{ confine to <Zone> · snap <grid> }] // then USE them: x = px y = py
68
71
  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
72
+ link endX,endY,target to <Group> // target = hit index 1..n (0=none), WORLD coords
70
73
  ```
71
74
  Self-state & feedback: `self.hovered self.grabbed self.pressed` (0/1) ·
72
75
  `feedback lift tilt dim shake(<expr>)`.
@@ -11,10 +11,10 @@ Author verifies with `flatc --preview Asset.flat --render -o out.png`.
11
11
 
12
12
  ```
13
13
  symbol "Mug" {
14
- layer "back" { ellipse 0 30 26 8 fill #00000022 } # layers stack bottom → top
14
+ layer "back" { ellipse 0 30 26 8 fill #00000022 } // layers stack bottom → top
15
15
  layer "body" {
16
16
  path "M-22 -28 L22 -28 L18 28 L-18 28 Z" fill #e8553a stroke #b23a24 3 join round
17
- path "M22 -18 a14 14 0 0 1 0 28" nofill stroke #b23a24 5 cap round # handle
17
+ path "M22 -18 a14 14 0 0 1 0 28" nofill stroke #b23a24 5 cap round // handle
18
18
  }
19
19
  }
20
20
  ```
@@ -26,20 +26,20 @@ symbol "Mug" {
26
26
 
27
27
  ```
28
28
  circle cx cy r ellipse cx cy rx ry
29
- rect x y w h [r | rx ry] path "M… L… C… Z" # raw SVG path data
29
+ rect x y w h [r | rx ry] path "M… L… C… Z" // raw SVG path data
30
30
  text "Hi" font "sans-serif" size 24 align center line 1.2 color #fff box 200 40 [bold] [italic]
31
- image "id" w h at -w/2,-h/2 # needs: asset "id" "file.png" image (declared in the .flatink that uses it)
31
+ image "id" w h at -w/2,-h/2 // needs: asset "id" "file.png" image (declared in the .flatink that uses it)
32
32
  ```
33
33
 
34
34
  Paint & finish:
35
35
  ```
36
36
  fill #rrggbb | nofill
37
37
  stroke #rrggbb <width> [cap butt|round|square] [join round|bevel|miter] [miter n] [dash a,b]
38
- opacity 0..1 # or 8-digit hex alpha #rrggbbaa
39
- fill linear(90, 0:#bdecff, 1:#2f8fe0) # angle 0 = →, 90 = ↓
40
- fill radial(0.5, 0.5, 0.5, 0:#fff, 1:#000) # cx, cy, r in 0..1, then stops
38
+ opacity 0..1 // or 8-digit hex alpha #rrggbbaa
39
+ fill linear(90, 0:#bdecff, 1:#2f8fe0) // angle 0 = →, 90 = ↓
40
+ fill radial(0.5, 0.5, 0.5, 0:#fff, 1:#000) // cx, cy, r in 0..1, then stops
41
41
  filter glow <blur> <color> | shadow <dx> <dy> <blur> <color> | blur <r> | adjust <b> <c> <s> <h>
42
- tint <color> <amount(0..1)> nohit # nohit = drawn but click-through
42
+ tint <color> <amount(0..1)> nohit // nohit = drawn but click-through
43
43
  ```
44
44
 
45
45
  ## Repetition without copy-paste (compile-time)
@@ -47,7 +47,7 @@ tint <color> <amount(0..1)> nohit # nohit = drawn but click-
47
47
  ```
48
48
  def n = 6
49
49
  layer "rays" {
50
- repeat i from 0 to n { rect -2 -40 4 16 fill #ffd24d } # $(…) interpolates compile-time math
50
+ repeat i from 0 to n { rect -2 -40 4 16 fill #ffd24d } // $(…) interpolates compile-time math
51
51
  }
52
52
  ```
53
53
  `$(expr)` injects arithmetic into any coordinate: `circle $(60 + i*40) 80 6 …`. Nested loops = grids.
@@ -64,8 +64,8 @@ symbol "Boat" {
64
64
  bool flag = true "Show the pennant"
65
65
  }
66
66
  layer "body" {
67
- path "M-40 0 L40 0 L24 20 L-24 20 Z" fill hull # a color param used as a fill…
68
- path "M0 -50 L0 0 L26 -10 Z" fill sail # …or stroke <param> <width>
67
+ path "M-40 0 L40 0 L24 20 L-24 20 Z" fill hull // a color param used as a fill…
68
+ path "M0 -50 L0 0 L26 -10 Z" fill sail // …or stroke <param> <width>
69
69
  }
70
70
  }
71
71
  ```
@@ -6,30 +6,29 @@ and the declarative factoring sugar. You wire behavior to named scene items.
6
6
  **Output contract:** a `.flatink` program. Emit **only the code** in a fenced block. Verify and test
7
7
  headlessly (no browser):
8
8
  ```
9
- flatc game.flatink --check # semantic + layout lint (exit ≠0 on ERROR)
10
- flatc game.flatink --play --script gestures.json --trace # replay gestures, see sends + var diffs
9
+ flatc game.flatink --check // semantic + layout lint (exit ≠0 on ERROR)
10
+ flatc game.flatink --play --script gestures.json --trace // replay gestures, see sends + var diffs
11
11
  ```
12
12
 
13
13
  ## File shape — TWO grammars, one file
14
14
 
15
15
  ```
16
- size 480 320 # REQUIRED, first line
16
+ size 480 320 // REQUIRED, first line
17
17
  background #0a0e1c
18
- use "collision" # stdlib: collision | easing | gesture | feedback
19
- var score = 0 # global runtime state (top of file)
18
+ use "collision" // stdlib: collision | easing | gesture | feedback
19
+ var score = 0 // global runtime state (top of file)
20
20
 
21
- scene { # ── HALF 1: composition (what you see) ──
21
+ scene { // -- HALF 1: composition (what you see) --
22
22
  layer "game" {
23
- circle 240 160 40 fill #ffcc00 as "Sun" # `as` names an item for behavior
24
- instance "Card" as "C1" at 100,100
23
+ group "Sun" at 240,160 { layer "art" { circle 0 0 40 fill #ffcc00 } }
25
24
  }
26
25
  }
27
26
 
28
- object "Sun" { # ── HALF 2: behavior, attaches by name ──
27
+ object "Sun" { // -- HALF 2: behavior, attaches by name --
29
28
  when clicked { score = score + 1 }
30
- rotation = time * 90 # channel binding, evaluated every frame
29
+ rotationDeg = clock * 90 // channel binding, evaluated every frame
31
30
  }
32
- every frame { if (score >= 10) { send "win" } }
31
+ every frame { if score >= 10 { send "win" } }
33
32
  ```
34
33
  **Drawing keywords (`circle`, `path`, `group`…) live ONLY in `scene`. Logic keywords (`var`, `when`,
35
34
  `object`, `fn`, `if`) live ONLY after it.** They don't share a grammar.
@@ -37,16 +36,16 @@ every frame { if (score >= 10) { send "win" } }
37
36
  ## State, events, actions
38
37
 
39
38
  ```
40
- var score = 0 var slots = [0,0,0] var seen = fill(8, 0) # arrays via literal or fill(n,v)
39
+ var score = 0 var slots = [0,0,0] var seen = fill(8, 0) // arrays via literal or fill(n,v)
41
40
  ```
42
41
  Events (in `object "Name"`): `when clicked | hovered | unhovered | pressed | released | dragged | held
43
42
  | dropped on <Zone> [at pointer]`. Scene-wide: `when loaded`, `every frame`, `at frame <n>`.
44
43
 
45
44
  Actions — **one per line**:
46
45
  ```
47
- <var> = <expr> arr[<expr>] = <expr> # `set` keyword optional; nested indices ok
46
+ <var> = <expr> arr[<expr>] = <expr> // `set` keyword optional; nested indices ok
48
47
  if <c> { … } [else if <c> { … }] [else { … }]
49
- repeat <n> times { … } repeat i from a to b { … } # RUNTIME loops (bounded)
48
+ repeat <n> times { … } repeat i from a to b { … } // RUNTIME loops (bounded)
50
49
  play pause go to frame <n> [and play|and pause] go to "<label>" [and play]
51
50
  send "<evt>" [, <expr> | , text("<id>") | , { a = <expr>, b }] sound "<assetId>" <fn>(<args>)
52
51
  ```
@@ -57,14 +56,14 @@ Drive `x y scaleX scaleY rotation opacity` (absolute) every frame, plus `dx dy`
57
56
  offsets, `pos = at + (dx, dy)`:
58
57
  ```
59
58
  object "Needle" {
60
- rotation = atan2(mouse.y - 160, mouse.x - 240) # RADIANS
59
+ rotation = atan2(mouse.y - 160, mouse.x - 240) // RADIANS
61
60
  opacity = lit ? 1 : 0.3
62
- dx = 30 * sin(clock) # sways AROUND its declared at — no base to re-inject
61
+ dx = 30 * sin(clock) // sways AROUND its declared at — no base to re-inject
63
62
  }
64
63
  ```
65
64
  Chase a target with inertia instead of snapping to it:
66
65
  ```
67
- object "Dial" { spring rotation = aim { stiffness 0.08 damping 0.86 } } # smooth y = target { k 0.15 }
66
+ object "Dial" { spring rotation = aim { stiffness 0.08 damping 0.86 } } // smooth y = target { k 0.15 }
68
67
  ```
69
68
  Pure numeric expressions (no booleans — logic/compares yield `1`/`0`). Operators `?: || && == != < >
70
69
  <= >= + - * / % - ! . [] fn()`. Built-ins: `sin cos tan atan2 abs sqrt pow exp log floor ceil round
@@ -77,15 +76,15 @@ Functions: `fn dist(ax,ay,bx,by) = hypot(ax-bx, ay-by)` (value) · `fn reset() {
77
76
 
78
77
  ```
79
78
  object "Piece" {
80
- drag px, py { confine to Board · snap 20 } # dragX / dragY for one axis
81
- x = px y = py # USE the vars it writes
79
+ drag px, py { confine to Board · snap 20 } // dragX / dragY for one axis
80
+ x = px y = py // USE the vars it writes
82
81
  when dropped on Slot at pointer { placed = 1 }
83
82
  }
84
- turn <angle> around x,y [{ snap <deg> }] # → RADIANS → rotation = <angle> directly
85
- turnDeg <angle> around x,y [{ snap <deg> }] # → DEGREES → pair with rotationDeg = <angle>
86
- trace <progress> along <Group> [{ tolerance <px> }] # follow path → 0..1 monotone
87
- reveal <progress> [{ brush <px> }] # scratch/wipe grabbed area → 0..1 cumulative
88
- link endX,endY,target to <Group> # elastic thread; target = hit index 1..n (0=none), WORLD coords
83
+ turn <angle> around x,y [{ snap <deg> }] // → RADIANS → rotation = <angle> directly
84
+ turnDeg <angle> around x,y [{ snap <deg> }] // → DEGREES → pair with rotationDeg = <angle>
85
+ trace <progress> along <Group> [{ tolerance <px> }] // follow path → 0..1 monotone
86
+ reveal <progress> [{ brush <px> }] // scratch/wipe grabbed area → 0..1 cumulative
87
+ link endX,endY,target to <Group> // elastic thread; target = hit index 1..n (0=none), WORLD coords
89
88
  ```
90
89
 
91
90
  ## Feedback (reactions without handlers)
@@ -93,7 +92,7 @@ link endX,endY,target to <Group> # elastic thread; target = hit
93
92
  ```
94
93
  object "Tile" {
95
94
  x = tx y = ty
96
- feedback lift tilt dim shake(wrongZone) # lift=hover grow · tilt=grab squash · dim=hover dim · shake=wobble
95
+ feedback lift tilt dim shake(wrongZone) // lift=hover grow · tilt=grab squash · dim=hover dim · shake=wobble
97
96
  }
98
97
  ```
99
98
  Or read state directly: `scaleX = self.hovered ? 1.06 : 1`, `scaleY = self.grabbed ? 0.94 : 1`.
@@ -102,19 +101,19 @@ Or read state directly: `scaleX = self.hovered ? 1.06 : 1`, `scaleY = self.grabb
102
101
 
103
102
  ```
104
103
  def gap = 70
105
- repeat i from 0 to 4 { circle $(40 + i*gap) 80 6 fill #ffd98a } # $(…) = compile-time math
104
+ repeat i from 0 to 4 { circle $(40 + i*gap) 80 6 fill #ffd98a } // $(…) = compile-time math
106
105
  symbol "Key"(label) { layer "c" { rect -28 -28 56 56 fill #e8e8e8
107
106
  text "$(label)" font "sans-serif" size 24 align center line 1.2 color #111 box 56 56 } }
108
107
  scene { layer "Pad" {
109
108
  repeat i from 0 to 8 { instance "Key"($(i+1)) as "K$(i)" at $(70 + (i%3)*80),$(80 + floor(i/3)*80) }
110
109
  } }
111
- each "Key" as i { when clicked { input = input*10 + (i+1) } } # one handler per generated instance
110
+ each "Key" as i { when clicked { input = input*10 + (i+1) } } // one handler per generated instance
112
111
  ```
113
112
  `match` factors a whole drag+drop matching activity:
114
113
  ```
115
114
  match Word1, Word2 onto Good, Bad {
116
115
  correct Word1 -> Good, Word2 -> Bad
117
- lock on wrong # optional; absent = retryable
116
+ lock on wrong // optional; absent = retryable
118
117
  on correct as it { send "found", text(it) }
119
118
  on done { send "win" }
120
119
  }
@@ -6,9 +6,9 @@ pivots, expression-driven channels, and named states. You think in keyframes, ar
6
6
  **Output contract:** usually a `.flat` (animated `symbol`s). Emit **only the code** in a fenced block.
7
7
  A `.flat` has **no `size`**. Author previews with:
8
8
  ```
9
- flatc --preview Wheel.flat --render -o wheel.png # PNG of one frame (default --bbox all, union)
10
- flatc --preview Wheel.flat -o wheel.flatpack # playable file for the browser
11
- flatc --preview Door.flat --render --set door=0.5 -o half.png # a state / param value
9
+ flatc --preview Wheel.flat --render -o wheel.png // PNG of one frame (default --bbox all, union)
10
+ flatc --preview Wheel.flat -o wheel.flatpack // playable file for the browser
11
+ flatc --preview Door.flat --render --set door=0.5 -o half.png // a state / param value
12
12
  ```
13
13
 
14
14
  ## The model in one paragraph
@@ -22,11 +22,11 @@ containers in the layer's **roster** (declared ONCE above the cels). Between two
22
22
  symbol "Wheel" {
23
23
  timeline 24 24
24
24
  layer "spin" {
25
- group "Rim" at 100,100 pivot 0,0 { # roster: declared once, posed below
25
+ group "Rim" at 100,100 pivot 0,0 { // roster: declared once, posed below
26
26
  layer "art" { circle 0 0 40 nofill stroke #333 8 }
27
27
  }
28
28
  cel 0 tween { pose "Rim" rotate 0 }
29
- cel 24 { pose "Rim" rotate 360 } # one full turn around the pivot, in DEGREES
29
+ cel 24 { pose "Rim" rotate 360 } // one full turn around the pivot, in DEGREES
30
30
  }
31
31
  }
32
32
  ```
@@ -75,7 +75,7 @@ A cel is a **full snapshot**: a container is shown only on cels that `pose` it;
75
75
  **disappears** there (that's how a symbol exits). To avoid re-typing unchanged containers:
76
76
  ```
77
77
  cel 0 tween { pose "Base" at 0,0 pose "Ring" scale 1 }
78
- cel 30 hold tween { pose "Ring" scale 4 } # Base carried forward automatically
78
+ cel 30 hold tween { pose "Ring" scale 4 } // Base carried forward automatically
79
79
  cel 60 hold { pose "Ring" scale 1 }
80
80
  ```
81
81
  Or keep a truly static element on its **own cel-less layer** (rendered every frame, declared once).
@@ -84,7 +84,7 @@ Or keep a truly static element on its **own cel-less layer** (rendered every fra
84
84
 
85
85
  Bind a channel on a container to a formula:
86
86
  ```
87
- group "Fan" pivot 0,0 expr rotation "turns(time)" { … } # one turn per second
87
+ group "Fan" pivot 0,0 expr rotation "turns(time)" { … } // one turn per second
88
88
  ```
89
89
  - Channels: `x y scaleX scaleY rotation opacity` (absolute), plus **`dx` / `dy`** — additive position
90
90
  offsets, `pos = at + (dx, dy)`. Prefer them for motion AROUND a rest position: `expr dx "30*sin(clock)"`
@@ -101,15 +101,15 @@ unused, and **snaps to the target on a seek** — so tune it by PLAYING the prev
101
101
 
102
102
  ```
103
103
  symbol "Cable" {
104
- params { number hookX = 0 range -1 1 "Hook offset" } # the target must be a NAME this scope knows
104
+ params { number hookX = 0 range -1 1 "Hook offset" } // the target must be a NAME this scope knows
105
105
  timeline 24 24
106
106
  layer "a" {
107
- group "Swing" spring rotation "hookX" stiffness 0.08 damping 0.86 { # 2nd order: overshoots, settles
107
+ group "Swing" spring rotation "hookX" stiffness 0.08 damping 0.86 { // 2nd order: overshoots, settles
108
108
  layer "c" { rect -4 0 8 60 fill #333333 }
109
109
  }
110
110
  }
111
111
  }
112
- group "Leaf" smooth y "target" k 0.15 { … } # 1st order: no overshoot
112
+ group "Leaf" smooth y "target" k 0.15 { … } // 1st order: no overshoot
113
113
  ```
114
114
  - `stiffness` / `damping` / `k` are all `0..1`. Low stiffness = lazy; damping near 1 = few oscillations.
115
115
  - The target is a quoted expression, and its names must EXIST in that scope (a symbol `param`, a scene
@@ -126,8 +126,8 @@ symbol "Door" {
126
126
  states door { closed at 0 open at 24 initial closed transition 12 ease easeInOut }
127
127
  layer "panel" {
128
128
  group "Panel" at 60,10 pivot 0,0 { layer "art" { rect 0 0 40 80 fill #884422 } }
129
- cel 0 tween { pose "Panel" rotate 0 } # closed
130
- cel 24 { pose "Panel" rotate 80 } # open
129
+ cel 0 tween { pose "Panel" rotate 0 } // closed
130
+ cel 24 { pose "Panel" rotate 80 } // open
131
131
  }
132
132
  }
133
133
  ```