@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/dist/analysis.js +1 -1
- package/dist/{chunk-MRRRN2CS.js → chunk-IYPM3CU5.js} +37 -2
- package/dist/chunk-IYPM3CU5.js.map +1 -0
- package/dist/{chunk-GAS27LSJ.js → chunk-S63MOX6G.js} +18 -14
- package/dist/chunk-S63MOX6G.js.map +1 -0
- package/dist/cli/flatc.js +2 -2
- package/dist/cli/render.d.ts +36 -2
- package/dist/cli/render.js +76 -24
- package/dist/cli/render.js.map +1 -1
- package/dist/index.js +2 -2
- package/docs/README.md +55 -0
- package/docs/animating-symbols.md +357 -0
- package/docs/behavior-and-interactions.md +306 -0
- package/docs/dsl-gotchas.md +428 -0
- package/docs/embedding-fonts.md +95 -0
- package/docs/expressions-and-stdlib.md +107 -0
- package/docs/getting-started.md +107 -0
- package/docs/host-integration.md +162 -0
- package/docs/scene-and-drawing.md +168 -0
- package/docs/tooling.md +236 -0
- package/package.json +15 -7
- package/prompts/flatink-core.md +49 -46
- package/prompts/flatink-lite.md +20 -17
- package/prompts/role-asset-creator.md +11 -11
- package/prompts/role-coder.md +27 -28
- package/prompts/role-motion-designer.md +12 -12
- package/dist/chunk-GAS27LSJ.js.map +0 -1
- package/dist/chunk-MRRRN2CS.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flatkit/compiler",
|
|
3
|
-
"version": "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.
|
|
53
|
-
"@flatkit/types": "0.
|
|
54
|
-
"@flatkit/player": "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"
|
package/prompts/flatink-core.md
CHANGED
|
@@ -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
|
|
22
|
-
background #0a0e1c
|
|
23
|
-
timeline 30 300
|
|
24
|
-
use "collision"
|
|
25
|
-
asset "logo" "logo.svg" image
|
|
26
|
-
var score = 0
|
|
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
|
-
|
|
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
|
-
|
|
37
|
+
rotationDeg = clock * 30 // degrees; `clock` is monotone, `time` restarts every loop
|
|
38
38
|
}
|
|
39
|
-
every frame { if
|
|
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>]
|
|
50
|
-
path "M0 0 L10 0 L10 10 Z"
|
|
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
|
|
53
|
-
group "Name" at x,y pivot px,py { layer "c" { … } }
|
|
54
|
-
instance "Symbol" as "Name" at x,y
|
|
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
|
|
62
|
-
fill linear(90, 0:#bdecff, 1:#2f8fe0)
|
|
63
|
-
fill radial(0.5, 0.5, 0.5, 0:#fff, 1:#000)
|
|
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)>
|
|
66
|
-
nohit
|
|
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
|
|
77
|
+
timeline 24 24 // fps, durationFrames
|
|
78
78
|
layer "spin" {
|
|
79
|
-
group "Rim" at 100,100 pivot 0,0 {
|
|
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 }
|
|
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)
|
|
120
|
+
rotation = atan2(mouse.y - 160, mouse.x - 240) // RADIANS
|
|
121
121
|
opacity = lit ? 1 : 0.3
|
|
122
|
-
dx = 30 * sin(clock)
|
|
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 { … }
|
|
132
|
-
group "Panel" smooth y "target" k 0.15 { … }
|
|
133
|
-
object "Dial" { spring rotation = aim { stiffness 0.08 damping 0.86 } }
|
|
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> }]
|
|
149
|
-
turn <angle> around <x>,<y> [{ snap <deg> }]
|
|
150
|
-
turnDeg <angle> around <x>,<y> [{ snap <deg> }]
|
|
151
|
-
trace <progress> along <Group> [{ tolerance <px> }]
|
|
152
|
-
reveal <progress> [{ brush <px> }]
|
|
153
|
-
link <endX>,<endY>,<target> to <Group>
|
|
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)
|
|
159
|
-
fn dist(ax,ay,bx,by) = hypot(ax-bx, ay-by)
|
|
160
|
-
fn reset() { score = 0 go to frame 0 }
|
|
161
|
-
self.hovered self.grabbed self.pressed
|
|
162
|
-
feedback lift tilt dim shake(<expr>)
|
|
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
|
|
169
|
-
repeat i from 0 to 4 { circle $(40 + i*gap) 80 6 fill #ffd98a }
|
|
170
|
-
symbol "Card"(label, tint = "#fff") { … text "$(label)" … fill $(tint) … }
|
|
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) } }
|
|
173
|
-
match Word1, Word2 onto Good, Bad {
|
|
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
|
|
178
|
-
align top of "Bin" [offset dx,dy]
|
|
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`).
|
package/prompts/flatink-lite.md
CHANGED
|
@@ -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
|
|
16
|
-
background #0a0e1c
|
|
17
|
-
var score = 0
|
|
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 }
|
|
20
|
-
layer "game" {
|
|
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" {
|
|
25
|
+
object "Sun" { // behavior attaches by name, to a GROUP (a bare shape is an error)
|
|
23
26
|
when clicked { score = score + 1 }
|
|
24
|
-
|
|
27
|
+
rotationDeg = clock * 30 // channel binding, every frame (degrees; `clock` never wraps)
|
|
25
28
|
}
|
|
26
|
-
every frame { if
|
|
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
|
|
34
|
-
group "Name" at x,y pivot px,py { layer "c" { … } }
|
|
35
|
-
instance "Symbol" as "Name" at x,y
|
|
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
|
|
47
|
+
timeline 24 24 // fps, durationFrames (loops [0,dur))
|
|
45
48
|
layer "spin" {
|
|
46
|
-
group "Rim" at 100,100 pivot 0,0 {
|
|
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 }
|
|
50
|
-
cel 24 { pose "Rim" rotate 360 }
|
|
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> }]
|
|
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>
|
|
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 }
|
|
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
|
|
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"
|
|
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
|
|
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
|
|
39
|
-
fill linear(90, 0:#bdecff, 1:#2f8fe0)
|
|
40
|
-
fill radial(0.5, 0.5, 0.5, 0:#fff, 1:#000)
|
|
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
|
|
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 }
|
|
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
|
|
68
|
-
path "M0 -50 L0 0 L26 -10 Z" fill sail
|
|
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
|
```
|
package/prompts/role-coder.md
CHANGED
|
@@ -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
|
|
10
|
-
flatc game.flatink --play --script gestures.json --trace
|
|
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
|
|
16
|
+
size 480 320 // REQUIRED, first line
|
|
17
17
|
background #0a0e1c
|
|
18
|
-
use "collision"
|
|
19
|
-
var score = 0
|
|
18
|
+
use "collision" // stdlib: collision | easing | gesture | feedback
|
|
19
|
+
var score = 0 // global runtime state (top of file)
|
|
20
20
|
|
|
21
|
-
scene {
|
|
21
|
+
scene { // -- HALF 1: composition (what you see) --
|
|
22
22
|
layer "game" {
|
|
23
|
-
|
|
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" {
|
|
27
|
+
object "Sun" { // -- HALF 2: behavior, attaches by name --
|
|
29
28
|
when clicked { score = score + 1 }
|
|
30
|
-
|
|
29
|
+
rotationDeg = clock * 90 // channel binding, evaluated every frame
|
|
31
30
|
}
|
|
32
|
-
every frame { if
|
|
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)
|
|
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>
|
|
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 { … }
|
|
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)
|
|
59
|
+
rotation = atan2(mouse.y - 160, mouse.x - 240) // RADIANS
|
|
61
60
|
opacity = lit ? 1 : 0.3
|
|
62
|
-
dx = 30 * sin(clock)
|
|
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 } }
|
|
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 }
|
|
81
|
-
x = px y = py
|
|
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> }]
|
|
85
|
-
turnDeg <angle> around x,y [{ snap <deg> }]
|
|
86
|
-
trace <progress> along <Group> [{ tolerance <px> }]
|
|
87
|
-
reveal <progress> [{ brush <px> }]
|
|
88
|
-
link endX,endY,target to <Group>
|
|
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)
|
|
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 }
|
|
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) } }
|
|
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
|
|
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
|
|
10
|
-
flatc --preview Wheel.flat -o wheel.flatpack
|
|
11
|
-
flatc --preview Door.flat --render --set door=0.5 -o half.png
|
|
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 {
|
|
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 }
|
|
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 }
|
|
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)" { … }
|
|
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" }
|
|
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 {
|
|
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 { … }
|
|
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 }
|
|
130
|
-
cel 24 { pose "Panel" rotate 80 }
|
|
129
|
+
cel 0 tween { pose "Panel" rotate 0 } // closed
|
|
130
|
+
cel 24 { pose "Panel" rotate 80 } // open
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
```
|