lecodes-cli 1.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +6211 -3649
- package/package.json +12 -8
- package/runtime/materials/lightmap.mat +115 -139
- package/runtime/materials/particles-quad.mat +6 -2
- package/runtime/materials/terrain-lightmap.mat +123 -0
- package/runtime/materials/terrain.mat +36 -55
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/VERSION.json +5 -0
- package/runtime/web/assets/camera-5XOM8eHl.js +1 -0
- package/runtime/web/assets/creator-2d-D9WKYM6h.js +1 -0
- package/runtime/web/assets/creator-2d-ukvEmNEp.wasm +0 -0
- package/runtime/web/assets/creator-full-BBXzQlhq.js +1 -0
- package/runtime/web/assets/creator-full-D2dSDdDj.wasm +0 -0
- package/runtime/web/assets/creator-full.js-BYswGsqR.symbols +8124 -0
- package/runtime/web/assets/creator-ui-DIc92ion.wasm +0 -0
- package/runtime/web/assets/creator-ui-_1Z_AX2M.js +1 -0
- package/runtime/web/assets/geolocation-BjTKN9_P.js +1 -0
- package/runtime/web/assets/inspect-C7x78PNs.js +2 -0
- package/runtime/web/assets/map-CyBxOSV0.js +1 -0
- package/runtime/web/assets/map-XzoOFt0E.js +2 -0
- package/runtime/web/assets/mapImpl-bxvQ2h1Z.js +823 -0
- package/runtime/web/assets/maplibre-gl-worker-B7LXXUkR.js +8 -0
- package/runtime/web/assets/materials-uri4FvPE.bin +0 -0
- package/runtime/web/assets/materials-world-CQWt8TAm.bin +0 -0
- package/runtime/web/assets/qr-scanner-CK_cYoJR.js +1 -0
- package/runtime/web/assets/uberarchive-DLH-kBhM.bin +0 -0
- package/runtime/web/index.html +36 -0
- package/runtime/web/player.js +21 -0
- package/runtime/web-page/index.html +41 -0
- package/runtime/web-page/page.css +1 -0
- package/runtime/web-page/page.js +31 -0
- package/src/cli/help.ts +25 -0
- package/src/cli/index.ts +7 -7
- package/src/commands/app/android.ts +3 -1
- package/src/commands/app/desktop.ts +1 -1
- package/src/commands/app/desktopMac.ts +10 -9
- package/src/commands/app/icon.ts +9 -6
- package/src/commands/app/index.ts +3 -1
- package/src/commands/app/shared.ts +13 -5
- package/src/commands/app/templates/android.ts +10 -11
- package/src/commands/app/templates/ios.ts +1 -1
- package/src/commands/assets.ts +1 -1
- package/src/commands/compile.ts +15 -2
- package/src/commands/design/index.ts +4 -4
- package/src/commands/design/snapshot.ts +3 -3
- package/src/commands/desktop.ts +5 -1
- package/src/commands/dev.ts +82 -29
- package/src/commands/index.ts +64 -60
- package/src/commands/init/index.ts +11 -15
- package/src/commands/init/templates.ts +64 -21
- package/src/commands/lightmap.ts +459 -200
- package/src/commands/navmesh.ts +2 -2
- package/src/commands/plugin.ts +46 -0
- package/src/commands/render.ts +22 -16
- package/src/commands/scene.ts +6 -1
- package/src/commands/serve.ts +58 -0
- package/src/commands/shaders/new.ts +1 -1
- package/src/commands/test.ts +402 -398
- package/src/commands/update/index.ts +3 -3
- package/src/compile/designCompile.ts +1 -1
- package/src/compile/headlessBundle.ts +133 -133
- package/src/compile/nativeStack.ts +1 -1
- package/src/compile/projectCompile.ts +10 -2
- package/src/compile/sceneCompile.ts +8 -22
- package/src/declarations/lecodes-3d-editor.d.ts +4 -0
- package/src/declarations/lecodes-assets.d.ts +1 -1
- package/src/declarations/{lecodes-renderer.d.ts → lecodes-headless.d.ts} +3 -3
- package/src/dev/clientTemplates.ts +18 -14
- package/src/dev/devServer.ts +75 -38
- package/src/dev/iosDev.ts +180 -0
- package/src/dev/webRunner.ts +82 -14
- package/src/hosts/cmgenTool.ts +1 -1
- package/src/hosts/desktopRenderer.ts +98 -27
- package/src/hosts/desktopScript.ts +150 -35
- package/src/hosts/distRoot.ts +1 -1
- package/src/hosts/matcTool.ts +1 -1
- package/src/hosts/peerInstall.ts +5 -5
- package/src/hosts/peers.ts +2 -2
- package/src/index.ts +4 -1
- package/src/plugin/contract.ts +438 -0
- package/src/plugin/emitKotlin.ts +357 -0
- package/src/plugin/emitSdk.ts +259 -0
- package/src/plugin/emitSwift.ts +336 -0
- package/src/plugin/emitWeb.ts +383 -0
- package/src/plugin/generate.ts +157 -0
- package/src/plugin/lines.ts +28 -0
- package/src/plugin/lower.ts +231 -0
- package/src/plugin/manifest.ts +192 -0
- package/src/project/ignore.ts +164 -163
- package/runtime/materials/lightmap-baked-lite.mat +0 -180
- package/runtime/materials/lightmap-baked.mat +0 -181
- package/runtime/scene-harness.json +0 -1
- package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +0 -1
- package/runtime/web/assets/basis-C64VHDVD.js +0 -1
- package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
- package/runtime/web/assets/createViewerLite-DHqTFhWB.js +0 -866
- package/runtime/web/assets/draco-BiISTFcR.js +0 -118
- package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
- package/runtime/web/assets/index-Q06c6oHx.js +0 -2
- package/runtime/web/assets/mapViewImpl-B2JcES8l.js +0 -810
- package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +0 -8
- package/runtime/web/assets/worker-Caf-yYEI.js +0 -2
- package/runtime/web/embed.html +0 -32
- package/runtime/web/embed.js +0 -171
package/src/commands/index.ts
CHANGED
|
@@ -1,60 +1,64 @@
|
|
|
1
|
-
import type { Group } from "../cli"
|
|
2
|
-
import app from "./app"
|
|
3
|
-
import assets from "./assets"
|
|
4
|
-
import clone from "./clone"
|
|
5
|
-
import compile from "./compile"
|
|
6
|
-
import create from "./create"
|
|
7
|
-
import design from "./design"
|
|
8
|
-
import desktop from "./desktop"
|
|
9
|
-
import dev from "./dev"
|
|
10
|
-
import diff from "./diff"
|
|
11
|
-
import init from "./init"
|
|
12
|
-
import install from "./install"
|
|
13
|
-
import lightmap from "./lightmap"
|
|
14
|
-
import link from "./link"
|
|
15
|
-
import login from "./login"
|
|
16
|
-
import navmesh from "./navmesh"
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
] },
|
|
60
|
-
|
|
1
|
+
import type { Group } from "../cli"
|
|
2
|
+
import app from "./app"
|
|
3
|
+
import assets from "./assets"
|
|
4
|
+
import clone from "./clone"
|
|
5
|
+
import compile from "./compile"
|
|
6
|
+
import create from "./create"
|
|
7
|
+
import design from "./design"
|
|
8
|
+
import desktop from "./desktop"
|
|
9
|
+
import dev from "./dev"
|
|
10
|
+
import diff from "./diff"
|
|
11
|
+
import init from "./init"
|
|
12
|
+
import install from "./install"
|
|
13
|
+
import lightmap from "./lightmap"
|
|
14
|
+
import link from "./link"
|
|
15
|
+
import login from "./login"
|
|
16
|
+
import navmesh from "./navmesh"
|
|
17
|
+
import plugin from "./plugin"
|
|
18
|
+
import pn from "./pn"
|
|
19
|
+
import pull from "./pull"
|
|
20
|
+
import push from "./push"
|
|
21
|
+
import render from "./render"
|
|
22
|
+
import scene from "./scene"
|
|
23
|
+
import serve from "./serve"
|
|
24
|
+
import shaders from "./shaders"
|
|
25
|
+
import status from "./status"
|
|
26
|
+
import test from "./test"
|
|
27
|
+
import thumbs from "./thumbs"
|
|
28
|
+
import update from "./update"
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* THE command list. Adding a command = one file with a `defineCommand` default export + one line
|
|
32
|
+
* here, in the group it belongs to (CONTRIBUTING.md). Order within a group is the help's order.
|
|
33
|
+
*/
|
|
34
|
+
export const commandGroups = (opts: { install: boolean }): Group[] => [
|
|
35
|
+
{ title: "Project", commands: [init, create, clone, link, status, diff, pull, push, login] },
|
|
36
|
+
{ title: "Run & test", commands: [
|
|
37
|
+
dev,
|
|
38
|
+
compile,
|
|
39
|
+
render,
|
|
40
|
+
test,
|
|
41
|
+
desktop,
|
|
42
|
+
serve,
|
|
43
|
+
] },
|
|
44
|
+
{ title: "Editors", commands: [
|
|
45
|
+
design,
|
|
46
|
+
scene,
|
|
47
|
+
thumbs,
|
|
48
|
+
] },
|
|
49
|
+
{ title: "Content", commands: [
|
|
50
|
+
assets,
|
|
51
|
+
shaders,
|
|
52
|
+
lightmap,
|
|
53
|
+
navmesh,
|
|
54
|
+
] },
|
|
55
|
+
{ title: "Native apps", commands: [
|
|
56
|
+
app,
|
|
57
|
+
plugin,
|
|
58
|
+
pn,
|
|
59
|
+
] },
|
|
60
|
+
{ title: "Maintenance", commands: [
|
|
61
|
+
update,
|
|
62
|
+
{ ...install, hidden: !opts.install },
|
|
63
|
+
] },
|
|
64
|
+
]
|
|
@@ -25,10 +25,10 @@ const STARTER = `// Welcome to LeCodes — the whole SDK is available as globals
|
|
|
25
25
|
|
|
26
26
|
let count = 0
|
|
27
27
|
|
|
28
|
-
const counter = UIText({ color: "#9fb0d0", fontSize: 22, marginTop: 18 }
|
|
28
|
+
const counter = UIText("Taps: 0").style({ color: "#9fb0d0", fontSize: 22, marginTop: 18 })
|
|
29
29
|
|
|
30
|
-
const button = UIButton(
|
|
31
|
-
{
|
|
30
|
+
const button = UIButton(UIText("Tap me").style({ color: "#ffffff", fontSize: 22 }))
|
|
31
|
+
.style({
|
|
32
32
|
backgroundColor: "#4f7cff",
|
|
33
33
|
borderRadius: 14,
|
|
34
34
|
paddingVertical: 16,
|
|
@@ -36,24 +36,20 @@ const button = UIButton(
|
|
|
36
36
|
marginTop: 24,
|
|
37
37
|
alignItems: "center",
|
|
38
38
|
justifyContent: "center",
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
)
|
|
42
|
-
.style({ name: "tap" }) // names make elements targetable in tests/*.flow.json (see CLAUDE.md)
|
|
39
|
+
$hovered: { backgroundColor: "#6b91ff" }, // the mouse over it (desktop); $pressed is the press
|
|
40
|
+
})
|
|
41
|
+
.named("tap") // names make elements targetable in tests/*.flow.json (see CLAUDE.md)
|
|
43
42
|
.onClick(() => {
|
|
44
43
|
count++
|
|
45
44
|
counter.text = "Taps: " + count
|
|
46
45
|
})
|
|
47
46
|
|
|
48
47
|
const screen = UIScreen(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
),
|
|
55
|
-
],
|
|
56
|
-
).style({ name: "home" }) // the screen root's name is what tests' \`expect: { screen: … }\` reads
|
|
48
|
+
UIColumn(UIText("Hello, LeCodes").style({ color: "#ffffff", fontSize: 32 }), button, counter)
|
|
49
|
+
.style({ backgroundColor: "#2b3550", borderRadius: 24, padding: 40, alignItems: "center" }),
|
|
50
|
+
)
|
|
51
|
+
.style({ backgroundColor: "#1e2230", alignItems: "center", justifyContent: "center", padding: 40 })
|
|
52
|
+
.named("home") // the screen root's name is what tests' \`expect: { screen: … }\` reads
|
|
57
53
|
|
|
58
54
|
screen.open()
|
|
59
55
|
`
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* has its own set — designTemplates.ts). CLAUDE.md is the AI-assistant counterpart of README.md:
|
|
3
3
|
* it teaches the headless feedback loop (render / --script / test) and the conventions that make
|
|
4
4
|
* an app testable, so an assistant opening the project drives the real app instead of guessing.
|
|
5
|
-
* Facts here mirror lecodes-
|
|
5
|
+
* Facts here mirror lecodes-headless's scenario engine — update both when the format changes. */
|
|
6
6
|
|
|
7
7
|
export const appClaudeMd = (name: string) => `# ${name} — LeCodes app
|
|
8
8
|
|
|
@@ -30,17 +30,22 @@ lecodes render --desktop --view iso --png iso.png # 3D: inspection camera fitt
|
|
|
30
30
|
# scene (iso|top|front|side|back [--fov N]) — or --camera x,y,z
|
|
31
31
|
# --look-at x,y,z; the app's camera is untouched. In scripts:
|
|
32
32
|
# { "view": "iso", "screenshot": "iso.png" } then { "view": "off" }
|
|
33
|
-
lecodes render --desktop --script <file> # the same scenario on the NATIVE host (
|
|
34
|
-
#
|
|
35
|
-
#
|
|
33
|
+
lecodes render --desktop --script <file> # the same scenario on the NATIVE host (real GPU, the
|
|
34
|
+
# real hit-test): the same steps, targets and expects as
|
|
35
|
+
# headless (only expect.audio / the json output are headless-only)
|
|
36
|
+
lecodes render --sim --script <file> # the WINDOWLESS native host (lecodes-server): the real
|
|
37
|
+
# engines, no GPU, no pixels, no UI hit-test — expect.log /
|
|
38
|
+
# expect.paint only; runs on CI and beside an open desktop window
|
|
36
39
|
lecodes test # all tests/*.flow.json + design-derived flows; run before "done"
|
|
37
|
-
lecodes test --desktop # the file flows on the NATIVE host (
|
|
38
|
-
|
|
40
|
+
lecodes test --desktop # the file flows on the NATIVE host (design flows stay headless)
|
|
41
|
+
lecodes test --sim # the windowless twin: coordinates + expect.log / expect.paint only
|
|
39
42
|
lecodes dev --android --time 12 --png phone.png # the REAL phone over adb (USB): launches the app on
|
|
40
43
|
# the dev url via adb reverse, streams device logs as [device:log],
|
|
41
44
|
# screenshots after 12 s, force-stops, exit 1 on a crash. --serial
|
|
42
45
|
# <id> with several devices, --apk <file> to install a build first.
|
|
43
46
|
# Without --time it runs until Ctrl+C (which stops the app).
|
|
47
|
+
lecodes dev --ios --time 12 --png sim.png # the same on a booted iOS simulator or a plugged-in iPhone
|
|
48
|
+
# (Mac + Xcode): --udid <id|name>, --app <LeCodesApp.app> to install.
|
|
44
49
|
\`\`\`
|
|
45
50
|
|
|
46
51
|
Iterate with \`--script\` scenarios and keep the useful ones as \`tests/<name>.flow.json\` — they
|
|
@@ -49,7 +54,8 @@ are the regression suite. A failing step dumps the failing frame (JSON + PNG) un
|
|
|
49
54
|
|
|
50
55
|
## Scenario format (tests/*.flow.json and --script files)
|
|
51
56
|
|
|
52
|
-
One action per step (\`tap\`, \`type\`+\`text\`, \`fill\`, \`scroll\` [+\`by\`], \`wait\`,
|
|
57
|
+
One action per step (\`tap\`, \`type\`+\`text\`, \`fill\`, \`scroll\` [+\`by\`], \`wait\`, \`hover\` (the
|
|
58
|
+
mouse over a node: \`$hovered\` + \`onMouseEnter\`; hovering elsewhere ends it), and for
|
|
53
59
|
games: \`frames\` (advance N fixed 1/60 steps, no sleeping), \`drag\`+\`to\` [+\`ms\`], \`hold\`+\`ms\`,
|
|
54
60
|
\`key\`+\`ms\` / \`keyDown\` / \`keyUp\` (KeyboardEvent.code: "ArrowRight", "KeyW", "Space" — mouse
|
|
55
61
|
buttons are keys too: "MouseLeft"/"MouseRight"), \`look: [dx, dy]\` [+\`ms\`] (mouse motion for
|
|
@@ -57,12 +63,29 @@ buttons are keys too: "MouseLeft"/"MouseRight"), \`look: [dx, dy]\` [+\`ms\`] (m
|
|
|
57
63
|
\`stick: { left: [x, y], right: [x, y], triggers: [l, r] }\` (values persist; \`index\` = pad),
|
|
58
64
|
\`gamepad: "connect" | "disconnect"\`, pad buttons as keys: \`keyDown: "GamepadSouth"\`), then an
|
|
59
65
|
optional \`expect\` (\`screen\`, \`node\`/\`notNode\`, \`text\`/\`notText\`,
|
|
60
|
-
\`value: { target, equals }\`, \`log\` — a \`console.log\` line contains it
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
\`value: { target, equals }\`, \`log\` — a \`console.log\` line contains it,
|
|
67
|
+
\`paint: { "<name>": { color: "#rrggbbaa", fontSize: 18, present: [...], absent: [...] } }\` — the
|
|
68
|
+
resolved style of a named node, \`audio: { played, playing }\`), then outputs
|
|
69
|
+
(\`screenshot\` [+\`clip\`: a node name or \`{ x, y, width, height }\`], \`json\`, \`dump\` — the
|
|
70
|
+
runtime's tree with every node's box + resolved paint, identical on every host). Targets resolve
|
|
71
|
+
by \`name\` → exact visible text → substring → input placeholder; a tap/drag on empty space reaches
|
|
63
72
|
the 2D scene (sprite \`click\`/\`touchstart\`, \`ev.track()\`). Envelope keys: \`name\`,
|
|
64
73
|
\`width\`/\`height\`, \`settle\`, \`fixtures\`, \`steps\`.
|
|
65
74
|
|
|
75
|
+
Three hosts run the same file, at a fixed 16 ms clock on all of them:
|
|
76
|
+
|
|
77
|
+
| host | flag | what it is | targets | expects |
|
|
78
|
+
|---|---|---|---|---|
|
|
79
|
+
| headless | (none) | Bun: the runtime + UI renderer + a recording 3D core (no GPU) | names or \`{ x, y }\` | all |
|
|
80
|
+
| sim | \`--sim\` | the windowless native server: real engines, no pixels, no UI | \`{ x, y }\`, straight to the scene | \`log\`, \`paint\` |
|
|
81
|
+
| desktop | \`--desktop\` | the hidden desktop window: pixels, GL materials, the real hit-test | names or \`{ x, y }\` | all but \`audio\` |
|
|
82
|
+
|
|
83
|
+
One flow file, the same verdict on headless and desktop: names resolve the same way (\`name\` →
|
|
84
|
+
exact text → substring → placeholder), every step runs (\`hover\`, \`type\`, \`fill\`, \`scroll\`,
|
|
85
|
+
\`clip\` included) and the semantic expects read the same tree. Only \`expect.audio\` and the
|
|
86
|
+
\`json\` output are the headless renderer's. On the desktop \`type\` taps the input and types into
|
|
87
|
+
it (appends); the sim has no UI, so it takes coordinates and \`expect.log\` / \`expect.paint\` only.
|
|
88
|
+
|
|
66
89
|
\`\`\`json
|
|
67
90
|
{ "steps": [
|
|
68
91
|
{ "expect": { "screen": "home", "text": "Taps: 0" } },
|
|
@@ -72,19 +95,21 @@ the 2D scene (sprite \`click\`/\`touchstart\`, \`ev.track()\`). Envelope keys: \
|
|
|
72
95
|
] }
|
|
73
96
|
\`\`\`
|
|
74
97
|
|
|
75
|
-
Game time is deterministic
|
|
76
|
-
advance in fixed 1/60 steps driven by \`wait\`/\`frames\`/\`drag\`/\`key\` —
|
|
77
|
-
so a flow that passes once passes every time. \`Input.key(code)\` reads the
|
|
78
|
-
\`Input.on('keydown' | 'keyup', e => e.code)\` fires for each
|
|
79
|
-
(events land before the next loop tick); \`stick\` values are what
|
|
80
|
-
Headless draws UI + the 2D scene (sprites, tilemaps, Box2D); 3D is
|
|
81
|
-
physics (bodies fall, characters collide, triggers fire, ragdolls
|
|
82
|
-
under it falls, like on device) — use \`--desktop\` for 3D pixels
|
|
83
|
-
|
|
98
|
+
Game time is deterministic on every host: the Aspect / System phases (\`updateBefore\` / \`update\`),
|
|
99
|
+
2D physics and animation advance in fixed 1/60 steps driven by \`wait\`/\`frames\`/\`drag\`/\`key\` —
|
|
100
|
+
never by a wall clock — so a flow that passes once passes every time. \`Input.key(code)\` reads the
|
|
101
|
+
keys a flow holds and \`Input.on('keydown' | 'keyup', e => e.code)\` fires for each
|
|
102
|
+
\`key\`/\`keyDown\`/\`keyUp\` step (events land before the next loop tick); \`stick\` values are what
|
|
103
|
+
\`Input.gamepad(0).axis()\` reads. Headless draws UI + the 2D scene (sprites, tilemaps, Box2D); 3D is
|
|
104
|
+
captured as JSON with REAL Jolt physics (bodies fall, characters collide, triggers fire, ragdolls
|
|
105
|
+
drop — a character with no ground under it falls, like on device) — use \`--desktop\` for 3D pixels,
|
|
106
|
+
\`--sim\` for engine behaviour on a box without a GPU. \`console.log\` + \`expect.log\` is the
|
|
107
|
+
assertion channel for game state that has no UI.
|
|
84
108
|
|
|
85
109
|
## Make the app testable
|
|
86
110
|
|
|
87
|
-
- Give interactive elements a \`name\`: \`UIButton(...).
|
|
111
|
+
- Give interactive elements a \`name\`: \`UIButton(...).named("login")\` (or \`el.name = "login"\`;
|
|
112
|
+
it is not a style key).
|
|
88
113
|
- Name every screen's ROOT node with the screen's id — \`expect.screen\` reads it.
|
|
89
114
|
- If the project has a \`design/\` folder, keep the design's screen ids and activator \`name\`s
|
|
90
115
|
in the app code: \`lecodes test\` then checks the built app against the designed navigation
|
|
@@ -94,6 +119,16 @@ channel for game state that has no UI.
|
|
|
94
119
|
match wins). \`lecodes test\` auto-loads it for design-derived flows; a scenario file opts in
|
|
95
120
|
with \`"fixtures": "fixtures.json"\` in its envelope.
|
|
96
121
|
- \`fill\` fills every empty input — use it before tapping a disabled-until-filled submit.
|
|
122
|
+
- Visual state is a \`$class\` in the style (\`$pressed\`, \`$hovered\`, \`$focused\`, your own
|
|
123
|
+
\`$selected\` set with \`el.class.selected = true\` or bound with \`.class({ selected: () => … })\`);
|
|
124
|
+
the interaction states read back as \`button.isPressed\` / \`button.isHovered\`.
|
|
125
|
+
A flow asserts the EFFECT through \`expect.paint\` on a named node (the resolved \`color\` /
|
|
126
|
+
\`backgroundColor\` from the dump), never the class name — that works headless and native alike.
|
|
127
|
+
- Hover is the mouse's: \`{ "hover": "login" }\`, a \`frames\` step, then \`expect.paint\` on the node's
|
|
128
|
+
resolved colour (\`$hovered\`) and \`expect.log\` from \`onMouseEnter\`; hover somewhere else to see
|
|
129
|
+
it end. Works headless and on \`--desktop\`; \`mouseTo: [x, y]\` is the same cursor by coordinates.
|
|
130
|
+
\`$focused\` is the desktop renderer's; a keyboard-driven highlight (your own \`$focus\` class moved
|
|
131
|
+
by arrow keys) is assertable everywhere: \`key\` steps, then \`expect.paint\` on the highlighted node.
|
|
97
132
|
|
|
98
133
|
## 3D scenes
|
|
99
134
|
|
|
@@ -108,7 +143,15 @@ channel for game state that has no UI.
|
|
|
108
143
|
\`lecodes update\` if it's missing, never commit \`.lecodes/\`). For semantics and examples,
|
|
109
144
|
fetch the docs — they are served as raw markdown for AI tools:
|
|
110
145
|
|
|
111
|
-
- https://le.codes/llms.txt — the docs map; fetch it first to find the right page
|
|
146
|
+
- https://le.codes/llms.txt — the docs map; fetch it first to find the right page. It opens with the
|
|
147
|
+
CLI: every \`lecodes\` command with what it is for, the agent page (running without a terminal,
|
|
148
|
+
\`--entry\`, login) and the editors that expose MCP tools — \`lecodes design\` (the screen board)
|
|
149
|
+
and \`lecodes scene\` (the 3D scene editor for \`.scene.ts\` files). \`lecodes <command> --help\`
|
|
150
|
+
is the authority on flags.
|
|
151
|
+
**Building a whole thing — a game, enemies, a crowd of characters, a native plugin? llms.txt opens
|
|
152
|
+
with a COOKBOOK: read the matching recipe BEFORE you design the structure.** It has the file
|
|
153
|
+
layout, a skeleton per part, how to verify, and the traps already paid for (start with
|
|
154
|
+
"Project architecture for a game" for any game bigger than one file).
|
|
112
155
|
- https://le.codes/en/docs/api/<area>/<page>.md — one reference page as raw markdown
|
|
113
156
|
(e.g. \`ui/styling.md\`, \`2d/scene2d.md\`, \`3d/scene.md\`, \`runtime/storage.md\`)
|
|
114
157
|
- https://le.codes/en/docs/guide/<page>.md — guide pages (project anatomy, CLI, tracks)
|