@genex-ai/cli-demo 0.74.0 → 0.76.0-dev.195

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.
Files changed (33) hide show
  1. package/dist/index.js +13196 -12810
  2. package/package.json +1 -1
  3. package/templates/controllers/NOTICE.md +10 -0
  4. package/templates/controllers/assets/basis_transcoder.js +19 -0
  5. package/templates/controllers/assets/basis_transcoder.wasm +0 -0
  6. package/templates/controllers/character/meshy/meshy-loader.ts +29 -3
  7. package/templates/controllers/quality/gltf-loader.ts +67 -0
  8. package/templates/controllers/quality/governor.ts +21 -3
  9. package/templates/controllers/quality/pick-asset.ts +69 -0
  10. package/templates/controllers/quality/tier.ts +62 -6
  11. package/templates/skills/genex-ai-character/SKILL.md +4 -1
  12. package/templates/skills/genex-ai-hud/SKILL.md +191 -28
  13. package/templates/skills/genex-ai-hud/references/masked-fill.md +54 -0
  14. package/templates/skills/genex-ai-hud/references/stage1-prompt-template.md +28 -7
  15. package/templates/skills/genex-ai-hud/references/stage2-prompt-template.md +8 -4
  16. package/templates/skills/genex-ai-menu/SKILL.md +84 -18
  17. package/templates/skills/genex-ai-model/SKILL.md +20 -5
  18. package/templates/skills/genex-ai-skybox/SKILL.md +1 -1
  19. package/templates/skills/genex-ai-texture/SKILL.md +1 -1
  20. package/templates/skills/genex-ai-video/SKILL.md +1 -1
  21. package/templates/skills/genex-explore/SKILL.md +1 -1
  22. package/templates/skills/genex-getting-started/SKILL.md +2 -2
  23. package/templates/skills/genex-threejs-adaptive-quality/SKILL.md +49 -8
  24. package/templates/skills/genex-threejs-adaptive-quality/references/adaptive-quality.md +31 -16
  25. package/templates/skills/genex-threejs-character-controller/SKILL.md +4 -1
  26. package/templates/skills/genex-threejs-game-content/SKILL.md +3 -1
  27. package/templates/skills/genex-threejs-game-ui/SKILL.md +60 -35
  28. package/templates/skills/genex-threejs-game-ui/references/style-capsules.md +17 -0
  29. package/templates/skills/genex-threejs-multiplayer/SKILL.md +3 -0
  30. package/templates/skills/genex-threejs-skill-router/SKILL.md +4 -1
  31. package/templates/skills/genex-threejs-skill-router/references/routing-map.md +18 -6
  32. package/templates/skills/genex-threejs-visual-validation/SKILL.md +4 -1
  33. package/templates/skills/genex-updates/SKILL.md +1 -1
@@ -31,7 +31,7 @@ ring) that no hand-written CSS can fake.
31
31
  ## Three layers — plates are DOM, chrome is sprites, data is DOM
32
32
 
33
33
  - **Plate (DOM).** The backing surface behind a widget is built in CSS — in
34
- whatever register the brief and concept frame call for: smoked glass
34
+ whatever register the brief and concept mockup call for: smoked glass
35
35
  (`background: rgba(<darkest hue>, 0.35–0.6)` + `backdrop-filter: blur(6–12px)`
36
36
  and its `-webkit-` twin) when the style wants translucency, a solid painted
37
37
  plate, a subtle gradient — or no plate at all for an outline-led look.
@@ -62,6 +62,32 @@ ring) that no hand-written CSS can fake.
62
62
  identity-bearing reticle (engraved ring, sculpted brackets) is chrome —
63
63
  put it on the sheet like any other asset.
64
64
 
65
+ ### Sprite vs CSS — two decision rules
66
+
67
+ - **Rule A — container vs content (the FUSED-unit test).** When a widget's
68
+ frame and its interior detail are one crafted unit — an engraved border
69
+ flowing into an engraved interior, a medallion whose emblem grows out of
70
+ its ring — separating them and reassembling in CSS coordinates is fragile:
71
+ keep them FUSED in one sprite and treat the whole thing as chrome. When
72
+ the interior is runtime territory (a fill channel, a number, map blips),
73
+ the container is the sprite and the content is DOM — the default split.
74
+ - **Rule B — image vs code (the 5-minute test).** If CSS can build the
75
+ element in under ~5 minutes without losing fidelity — flat plates, simple
76
+ dots/pips, plain strokes, a chamfered corner — it is CSS. If it needs
77
+ craft (material, ornament, painterly texture), it is a sprite. One
78
+ exception cuts the other way: a static label engraved/embossed into a
79
+ frame's craft stays BAKED in the sprite (`baked_static` below) even
80
+ though DOM text is "code".
81
+ - **CSS-chrome recipes — the middle ground.** Between bare CSS and the full
82
+ sprite pipeline sits brief-styled CSS chrome, right for utilitarian panels
83
+ and secondary screens: a two-hue gradient frame (border + inset
84
+ box-shadow in brief hues), a clip-path chamfer (execution bar per
85
+ `$genex-threejs-game-ui`'s corner rules), a 9-slice-ish panel from nested
86
+ divs (outer div = border hue, inset div = plate hue, 2–3px reveal). What
87
+ makes these legitimate: **CSS elements are styled FROM THE BRIEF — hue,
88
+ weight, texture. Default-gray CSS anywhere on screen is a defect** (the
89
+ classic failure: score pips shipped as unstyled "○ ●" system glyphs).
90
+
65
91
  ## Framing rules — internalize before prompting
66
92
 
67
93
  The HUD overlays a **live 3D scene**. The pixels between widgets show the game.
@@ -118,7 +144,32 @@ buckets. The Stage-2 prompt KEEPS or REMOVES accordingly:
118
144
 
119
145
  The test: "is this fixed identity artwork, or should runtime code own it?"
120
146
  Baking a runtime value into a sprite is a failure; stripping a carved identity
121
- label flattens the art.
147
+ label flattens the art. **One label per widget:** whoever the table says owns
148
+ a word, owns it alone — if a sprite ships with a `baked_static` label, the
149
+ DOM must never render the same word again on that widget (the double-label
150
+ failure). The classification survives to wiring: carry it into the code
151
+ comments, not just this analysis.
152
+
153
+ ### Placing runtime text on frame sprites — measured, never eyeballed
154
+
155
+ - **Position from `innerBBox`.** The trim/extract sidecars (`.bbox.json`)
156
+ carry `innerBBox` — the frame's central transparent cavity. Box the DOM
157
+ text to `innerBBox` scaled to the widget's on-screen size; never hand-tune
158
+ magic paddings until it "looks right" (they break on the first resize or
159
+ art swap). If `innerBBox` is null on a genuine frame (no cavity), fall
160
+ back to the frame's visual center and say so in the wiring notes — solid
161
+ plates without a cavity are DOM plates anyway (three layers).
162
+ - **Occupancy bands.** Value text fills 65–85% of its zone's height; labels
163
+ 55–75%. Below the band the text rattles inside the frame; above it, it
164
+ crowds the art.
165
+ - **Width buffers for display fonts.** Display faces run wide: budget +25%
166
+ width over a naive estimate, +40% for heavy weights in uppercase, +50% for
167
+ heavy + uppercase + serif — then verify `scrollWidth <= clientWidth` at
168
+ the widest real value (the 888,888 test).
169
+ - **`genex ui text-color` is a MANDATORY step for every widget that carries
170
+ DOM text** — sample the mockup region and use the returned hex verbatim.
171
+ Free-picking a "close enough" palette color is how text drifts off the
172
+ art's actual plate.
122
173
 
123
174
  ## Icons & mechanics must match the game — diff before Stage 2
124
175
 
@@ -135,8 +186,10 @@ are committing to build):
135
186
  grenade slot on a rifle-only HUD, an armor bar with no armor, a minimap over
136
187
  no map — is a failure, and "the model drew it so I kept it" is how it happens.
137
188
 
138
- The mockup (and any concept frame) anchors STYLE; the game contract owns WHAT
139
- EXISTS. When the picture and the mechanics disagree, the mechanics win.
189
+ The mockup which IS the game concept anchors STYLE; the game contract owns
190
+ WHAT EXISTS. When the picture and the mechanics disagree, the mechanics win.
191
+ Cut widgets are recorded in the UI plan message as "deferred from mockup"
192
+ (`$genex-threejs-game-ui`) — never silently wired, never silently dropped.
140
193
 
141
194
  ## The pipeline
142
195
 
@@ -146,24 +199,30 @@ printed. Fill the two prompt templates —
146
199
  and [references/stage2-prompt-template.md](references/stage2-prompt-template.md)
147
200
  — before Stages 1 and 2.
148
201
 
149
- **Order of work: kick Stage 1 off as your FIRST action after the UI plan
150
- gate**, then write the widget layout and wiring code (placement, masked-fill
151
- scaffolding, plain-CSS placeholder bars) while it renders the CSS HUD keeps
152
- the game playable until the sprites land. When the gate produced a concept
153
- frame, anchor Stage 1 to it `--edit <concept-url>` holds the game's
154
- register far better than a text-only prompt; single-sprite regenerations
155
- anchor the same way. The concept is a STYLE anchor only: the widget set and
202
+ **Order of work: kick Stage 1 off as your FIRST action at the UI plan
203
+ gate the Stage-1 mockup IS the game concept.** There is no separate
204
+ UI-free concept image before it: this one generation carries scene + HUD,
205
+ serves as the user's style checkpoint, and anchors all later art
206
+ (`$genex-threejs-game-ui` owns the checkpoint choreography). The scene half
207
+ of the prompt is written in TEXT from the game plan — `[GAME_SCENE]` in the
208
+ Stage-1 template: setting, the moment, what the player is doing, lighting
209
+ never `--edit`-anchored to a prior image (there is none). Then write the
210
+ widget layout and wiring code (placement, masked-fill scaffolding,
211
+ plain-CSS placeholder bars) while it renders — the CSS HUD keeps
212
+ the game playable until the sprites land. The mockup is a STYLE anchor
213
+ only: the widget set and
156
214
  layout come from the element inventory and the game contract — a mechanic
157
- the concept invented (a lap counter, a stamina orb) does not enter the HUD,
215
+ the mockup invented (a lap counter, a stamina orb) does not enter the HUD,
158
216
  and a mechanic it failed to show still does. Generate 2 mockup candidates in one
159
217
  call (`--candidates 2`) and pick the better one: a re-roll costs the whole
160
218
  serial chain, a second candidate costs nothing extra in wall-clock. If the
161
- user's concept-loop feedback replaces the anchor frame after Stage 1 ran,
162
- re-run the pipeline from Stage 1 against the new frame there is no cheaper
163
- partial path, so budget the full chain and say so in one line.
219
+ user's concept-loop feedback rejects the style after Stage 2+ ran,
220
+ re-run from Stage 1 with their notescheaper than it sounds (one image
221
+ is the whole concept now), so say so in one line and do it.
164
222
 
165
223
  ```bash
166
- # Stage 1 — full-HUD mockup over a placeholder scene (the LAYOUT REFERENCE).
224
+ # Stage 1 — the game CONCEPT: full HUD composited over the game's own scene,
225
+ # described in TEXT (one generation covers concept + layout reference).
167
226
  # Two candidates in one call; pick the better, save its URL:
168
227
  npx genex image "<filled stage-1 prompt>" --size 2560x1440 --quality high --candidates 2
169
228
 
@@ -178,11 +237,15 @@ npx genex image "clean sheet" --clean <sheet-url>
178
237
  # Names in READING ORDER: top-to-bottom, then left-to-right within a band:
179
238
  npx genex ui extract --in <cleaned-url> --out-dir public/assets/hud --names hp-frame,ammo-frame,minimap-ring,weapon-icon
180
239
 
181
- # Progress masks — for every bar/meter with a generated frame. The Stage-2
182
- # sheet holds a clean frame cell PLUS a same-size annotated duplicate with the
183
- # fill zones painted pure green #00ff00; this converts the green into an alpha
184
- # mask + the fillBox (FB) the DOM fill reads:
185
- npx genex ui masks --in <cleaned-url> --out-dir public/assets/hud --pairs "hp:cx,cy,w,h:ax,ay,w,h:1"
240
+ # Progress masks — for every CONTINUOUS bar/meter with a generated frame. The
241
+ # Stage-2 sheet holds a clean frame cell PLUS a same-size annotated duplicate
242
+ # with the fill zones painted pure green #00ff00; this converts the green into
243
+ # an alpha mask + the fillBox (FB) the DOM fill reads (+ per-segment fillBoxes
244
+ # in `segments` for one-frame-N-slots meters). --auto finds the cell pairs
245
+ # itself — prefer it; fall back to hand-computed --pairs crop rects only when
246
+ # it reports ambiguity:
247
+ npx genex ui masks --in <cleaned-url> --out-dir public/assets/hud --auto --names hp
248
+ # explicit fallback: --pairs "hp:cx,cy,w,h:ax,ay,w,h:1"
186
249
  # pairs = name : clean-cell crop (px) : annotated-cell crop (px) : expected green components
187
250
 
188
251
  # Text colors — sample the mockup instead of free-picking from the palette:
@@ -208,6 +271,24 @@ To fix ONE sprite, don't re-run the pipeline:
208
271
  its alpha content and reports its real pixel dims — the source of truth for
209
272
  the box↔art aspect rule below.
210
273
 
274
+ ### Crop guards — catch a bad sheet before it ships
275
+
276
+ - **`extract --expect N`** (N = your `--names` count) asserts the component
277
+ count and hard-errors BEFORE writing any file, listing what it found. Use
278
+ it on every extraction — a miscount is otherwise silent and mis-names
279
+ everything after the gap.
280
+ - Extraction also **warns** when a component touches the sheet canvas edge
281
+ (the canvas sliced it — regenerate the sheet with more margin around every
282
+ element) or looks like a fragment (extreme aspect / near-empty box — try
283
+ `--dilate` to glue split pieces).
284
+ - **gpt-image-2 silently squashes past 3:1** — never request a canvas with
285
+ aspect beyond 3:1. For thin strips (a wide bar frame), generate inside a
286
+ ≤3:1 canvas with margins, then `genex ui trim` down to the art.
287
+ - **Fix-up recrop, never a Stage-2 re-run for one sprite.** One bad crop:
288
+ re-extract from the cleaned sheet with adjusted `--min-pixels`/`--dilate`,
289
+ or region-`--edit` the sheet and re-extract. Re-running Stage 2 re-rolls
290
+ every OTHER sprite too — the most expensive way to fix one.
291
+
211
292
  ## Composition rules (each one is a documented failure class)
212
293
 
213
294
  - **Continuous fills = masked reveal.** HP, mana, fuel, XP, stamina, charge —
@@ -236,6 +317,43 @@ To fix ONE sprite, don't re-run the pipeline:
236
317
  in a comment (`/* no plate — reads against <reason> */`). Bare digits look
237
318
  fine over the dev background and vanish over a bright scene.
238
319
 
320
+ ## Discrete vs continuous — classify every meter before Stage 2
321
+
322
+ **The rule: does the quantity change in whole units the player counts?**
323
+ Hearts, lives, ammo bullets, stars, mana pips, wave markers → DISCRETE.
324
+ HP %, fuel, XP, stamina, charge → CONTINUOUS. The two ship through different
325
+ recipes, and classifying late means re-doing the sheet:
326
+
327
+ - **Discrete = repeated sprites, NEVER a masked fill.** A masked reveal
328
+ swept across a row of icons slices an icon mid-body at most values — the
329
+ classic badly-masked hearts. Ship one `<img>` per unit
330
+ (`Array.from({ length: count })`), with partial states as sprite VARIANTS —
331
+ full/half/empty cells requested on the Stage-2 sheet as separate same-size
332
+ cells. Stacking (empty cell under a lit cell, dimmed vs lit) beats swapping
333
+ when the art allows it.
334
+ - **Overflow pre-flight** before placing:
335
+ `max_count × item_w + (max_count − 1) × gap ≤ widget_w` — at MAX count,
336
+ not the current one.
337
+ - **Anchor rule:** align the row to ONE end (start or end, matching the
338
+ widget's screen anchor); never center a count that changes — it reflows.
339
+ - Simple-shape counters CSS renders crisper than any sprite (plain
340
+ dots/pips) stay CSS by Rule B — but styled from the brief: hue, stroke,
341
+ glow, state change. Unstyled system glyphs are the defect.
342
+ - **Continuous = the masked-fill pattern below.** Frame sprite + green-key
343
+ mask + fillBox-relative clip. Never a `*-fill.png`, never a naive width%.
344
+ - **Segmented continuous meters** (ONE frame, N visible slots, fractional
345
+ value — a posture/heat bar with slot dividers): use the `segments` array
346
+ the masks JSON writes next to `fillBox` (per-slot fillBoxes, sorted
347
+ left-to-right). Fill `Math.floor(v)` segments fully (each clipped to 100%
348
+ of its own fillBox), clip segment `Math.floor(v) + 1` fractionally INSIDE
349
+ its own fillBox, leave the rest empty. Never sweep one clip across all
350
+ slots — the union `fillBox` is for single-channel fills only.
351
+ - **Never bake a label mid-channel.** A label or ornament painted in the
352
+ MIDDLE of a fill channel splits the mask into fragments and the fill into
353
+ pieces. Labels sit above/beside the channel or on the frame's end caps —
354
+ it's a Stage-2 prompt rule; if a sheet comes back violating it, region-edit
355
+ that cell before running masks.
356
+
239
357
  ## The masked-fill DOM pattern (the crown jewel)
240
358
 
241
359
  Full annotated version with all direction variants and a worked HP-bar
@@ -369,11 +487,42 @@ Two hard rules:
369
487
  `$genex-threejs-visual-validation`'s capture discipline. Judge the pixels,
370
488
  not your intent.
371
489
 
490
+ ## The closing wiring audit — the pipeline's final stage, not optional
491
+
492
+ The most expensive failure this pipeline has produced in the field is not
493
+ bad art — it is validated assets that never got WIRED: masks sitting on disk
494
+ while the shipped fill was a naive width%, frame sprites stretched into
495
+ boxes of the wrong aspect. Close every HUD build with this audit:
496
+
497
+ 1. **Wired-or-reasoned table.** Every PNG in `public/assets/hud/` is either
498
+ referenced in code/CSS or has a one-line written reason it was cut. No
499
+ third state.
500
+ 2. **No naive %-fills.** Grep your own code for dynamic width/height percent
501
+ writes — every continuous fill goes through the masked-fill clip with a
502
+ real `FB` from the masks JSON.
503
+ 3. **Box aspect = sprite aspect** for every placed widget, computed from the
504
+ trim/extract sidecar dims — not from the mockup, never by eye.
505
+ 4. **Every mask referenced.** Each `<name>-mask.png` appears in a CSS
506
+ `mask-image` (or its widget has a written reason).
507
+ 5. **Text placement from `innerBBox`, colors from `genex ui text-color`** —
508
+ verbatim hex, no free-picked palette colors.
509
+ 6. Run **`npx genex ui audit`** (it also runs automatically as a preflight in
510
+ `genex preview`/`publish`) and clear or explain every finding — its
511
+ findings are heuristics, so "checked, fine because X" is a valid close.
512
+
513
+ **The subagent handoff rule.** If stages 2–3 ran in a subagent or an earlier
514
+ session, the wiring agent MUST read the produced `*.annotated-progress.json`
515
+ and `.bbox.json` files from disk before wiring — a prose summary of them
516
+ does not count. fillBox numbers, `segments`, and real sprite dims do not
517
+ survive paraphrase; the canonical version of this failure is a subagent
518
+ reporting "masks validated" while the parent ships width% fills.
519
+
372
520
  ## Cost & latency honesty
373
521
 
374
- A full HUD is **~10 image generations** (mockup + deconstruct + clean + a few
375
- sprite re-rolls), a couple of minutes each at high quality budget an hour
376
- end to end, not five minutes. That fits comfortably inside the image rate
522
+ A full HUD is **~9 image generations** (mockup + deconstruct + clean + a few
523
+ sprite re-rolls the mockup doubles as the game concept, so there is no
524
+ separate concept spend), a couple of minutes each at high quality budget an
525
+ hour end to end, not five minutes. That fits comfortably inside the image rate
377
526
  limit; local `genex ui` steps are free and instant. Two rules keep the clock
378
527
  honest: the sprite pipeline runs WHILE you build (kick Stage 1 first, code
379
528
  against CSS placeholders, swap sprites in as stages land — never make the
@@ -406,15 +555,22 @@ after a bad single costs the whole chain).
406
555
  `--no-wait` enqueue and continue — `npx genex wait <id>` picks the result
407
556
  up later (safe to re-run; never creates a new generation).
408
557
  - `npx genex ui extract --in <png|url> --out-dir <dir> --names a,b,c` — also
409
- writes a `.bbox.json` sidecar per sprite; `--min-pixels <n>` lowers the
558
+ writes a `.bbox.json` sidecar per sprite (with `innerBBox` for text
559
+ placement); `--expect <n>` hard-errors on a component miscount before
560
+ writing anything; `--min-pixels <n>` lowers the
410
561
  component threshold when a small sprite is missed.
411
- - `npx genex ui masks --in <png|url> --out-dir <dir> --pairs "name:cx,cy,w,h:ax,ay,w,h:n"` —
412
- emits `<name>-frame.png`, `<name>-mask.png`, an overlay, and the JSON with
413
- `fillBox` (`FB`).
562
+ - `npx genex ui masks --in <png|url> --out-dir <dir> --auto [--names a,b]` —
563
+ detects the clean/annotated cell pairs itself; explicit fallback
564
+ `--pairs "name:cx,cy,w,h:ax,ay,w,h:n"`. Emits `<name>-frame.png`,
565
+ `<name>-mask.png`, an overlay, and the JSON with `fillBox` (`FB`) plus
566
+ per-slot `segments` for segmented meters.
414
567
  - `npx genex ui text-color --in <png|url> --box x,y,w,h` — deterministic
415
568
  eyedropper: background/plate color + dark/light/chromatic ink candidates.
416
569
  Use the returned hex verbatim for the DOM text over that region.
417
570
  - `npx genex ui trim --in <png>` — crop to alpha content + report real dims.
571
+ - `npx genex ui audit [--dir public/assets/hud] [--src src]` — the mechanical
572
+ wiring scan (unreferenced sprites/masks, naive %-fill patterns); warn-only,
573
+ `--strict` exits 1 on findings.
418
574
 
419
575
  ## Troubleshooting
420
576
 
@@ -443,6 +599,13 @@ after a bad single costs the whole chain).
443
599
  duplicate drifted from the clean cell (different scale/position) or the
444
600
  green isn't flat `#00ff00`. Region-edit the sheet (`--edit <sheet-url>`)
445
601
  asking for a same-size duplicate with flat green fill zones, then re-run.
602
+ - **`masks --auto` reports ambiguity or a missing twin** — it never guesses:
603
+ fall back to explicit `--pairs` with hand-read crop rects for just that
604
+ meter (the error lists the candidate cells it saw).
605
+ - **The green mask splits into unexpected components** — a label or ornament
606
+ is baked mid-channel. Region-edit that cell to move the label onto the
607
+ frame's end caps (see the mid-channel rule), or treat it as a segmented
608
+ meter and wire the `segments` array.
446
609
  - **Digits or `$`/`%` come back hollow after `--remove-bg`** — closed loops
447
610
  read as background to the default cutter. Regenerate with `--bg-mode glyph`.
448
611
  - **A widget looks squashed** — the placement box aspect doesn't match the
@@ -55,6 +55,60 @@ liquid rises). For an orb whose empty top would show the scene through the
55
55
  glass, add a dark cavity div (a circle at the `FB` box) BEHIND the fill so
56
56
  the empty portion reads as deep glass rather than a hole.
57
57
 
58
+ ## Segmented meters — the `segments` array
59
+
60
+ A meter whose frame has N visible slots but a CONTINUOUS value (a posture or
61
+ heat bar with dividers) never sweeps ONE clip across all slots — the union
62
+ `FB` would light half a slot's divider at most values. The masks JSON writes
63
+ `segments` next to `fillBox`: per-slot fillBoxes sorted left-to-right, each
64
+ `{ index, fillBox }` in the same 0..1 space. Wire it per slot:
65
+
66
+ ```ts
67
+ // v in [0, N] — e.g. 2.4 = two full slots + 40% of the third.
68
+ function setSegmented(fill: HTMLElement[], segments: { fillBox: FB }[], v: number): void {
69
+ segments.forEach((seg, i) => {
70
+ const local = Math.max(0, Math.min(1, v - i)); // this slot's own 0..1
71
+ const b = seg.fillBox;
72
+ const cx = (b.x + local * b.w) * 100;
73
+ // One fill div per slot, all inside the same mask div:
74
+ fill[i]!.style.clipPath = `polygon(${b.x * 100}% 0%, ${cx}% 0%, ${cx}% 100%, ${b.x * 100}% 100%)`;
75
+ });
76
+ }
77
+ ```
78
+
79
+ Full slots clip to 100% of their own box, the fractional slot clips inside
80
+ its own box, empty slots clip to zero width. One fill div per slot (same
81
+ gradient, same mask parent) keeps each slot's edge inside its own channel.
82
+
83
+ ## Discrete counters are NOT masked fills
84
+
85
+ Hearts, lives, ammo pips, stars — anything counted in whole units — never go
86
+ through this pattern at all. A mask swept across a row of hearts slices a
87
+ heart mid-body at most values:
88
+
89
+ ```ts
90
+ // DON'T: one mask + width clip across a row of heart icons.
91
+ // DO: repeated <img> per unit, variants for partial states.
92
+ const hearts = document.getElementById("hud-hearts")!;
93
+ function setHearts(hp: number, max: number): void { // hp may be 2.5
94
+ hearts.replaceChildren(
95
+ ...Array.from({ length: max }, (_, i) => {
96
+ const img = document.createElement("img");
97
+ const v = Math.max(0, Math.min(1, hp - i));
98
+ img.src = v === 1 ? "/assets/hud/heart-full.png"
99
+ : v >= 0.5 ? "/assets/hud/heart-half.png"
100
+ : "/assets/hud/heart-empty.png";
101
+ return img;
102
+ }),
103
+ );
104
+ }
105
+ ```
106
+
107
+ The full/half/empty cells come from the Stage-2 sheet as separate same-size
108
+ cells (see the stage-2 template). Pre-flight the row at MAX count:
109
+ `max × item_w + (max − 1) × gap ≤ widget_w`, and anchor the row to one end —
110
+ never center a count that changes.
111
+
58
112
  ## The `data-fill-*` contract
59
113
 
60
114
  Stamp these on the inner fill div, derived from the SAME variables that
@@ -1,15 +1,29 @@
1
- # Stage 1 prompt template — full-HUD mockup
1
+ # Stage 1 prompt template — the game concept: full-HUD mockup
2
2
 
3
3
  Fill the four placeholders, then pass the whole text as the prompt to
4
- `npx genex image "<filled prompt>" --size 2560x1440 --quality high`.
4
+ `npx genex image "<filled prompt>" --size 2560x1440 --quality high --candidates 2`.
5
+ This image IS the game concept — the user's style checkpoint and the anchor
6
+ for all later art — so the scene half deserves the same care as the HUD half.
5
7
 
6
8
  Placeholders:
7
9
 
8
10
  - `[GENRE_BRIEF]` — one-line genre description, anchored in a recognizable
9
11
  register ("a dark gothic action RPG", "a sun-bleached rally racing game",
10
12
  "a hand-painted storybook platformer").
11
- - `[GAME_SCENE]` — what's behind the HUD ("a dark medieval cathedral interior
12
- with candlelight"). Only the scene may show 3D depth never the HUD.
13
+ - `[GAME_SCENE]` — what's behind the HUD, written RICH and from the game
14
+ plan (this is the concept's scene a thin phrase wastes the checkpoint):
15
+ the setting, the moment, what the player is doing right now (the verb),
16
+ what threatens them, what they're chasing, and the lighting. Only the
17
+ scene may show 3D depth — never the HUD. Examples:
18
+ - "a rain-slick neon alley mid-chase, the player character vaulting a
19
+ barrier toward a glowing extraction gate, two drones closing in from
20
+ behind, magenta signage cutting through blue dusk"
21
+ - "a torchlit dungeon arena mid-duel, the player's knight raising a
22
+ shield against a lunging skeletal champion, a health shrine glowing in
23
+ a far alcove, embers drifting in the dark"
24
+ - "a sun-bleached desert rally stage mid-drift, the player's buggy
25
+ kicking up dust through a canyon gate marked with checkpoint flags, a
26
+ rival truck closing on the left, late-afternoon glare"
13
27
  - `[STYLE_BRIEF]` — your full 3–5 sentence style brief with the named 4–5 hue
14
28
  palette. Do NOT append the per-sprite cutout sentence here — this is a full
15
29
  frame, not a cutout.
@@ -39,6 +53,13 @@ not deconstruct cleanly (angled panel edges have no clean silhouette, so
39
53
  Stage 2 produces sliced, skewed cutouts). If the mockup comes back tilted,
40
54
  regenerate it before proceeding; do not try to salvage it downstream.
41
55
 
42
- The mockup is the **layout reference**: save its URL, compare the finished
43
- HUD against it, and sample runtime text colors from it with
44
- `npx genex ui text-color`.
56
+ One more Stage-1 rule that pays off at mask time: **meter channels must be
57
+ continuous** never place a label or ornament in the MIDDLE of a fill
58
+ channel (it splits the mask and the fill into fragments); labels sit above or
59
+ beside the channel, or on the frame's end caps. If the mockup comes back
60
+ with a mid-channel label, keep it only if you'll wire that meter as
61
+ segmented (`segments` in the masks JSON) — otherwise regenerate.
62
+
63
+ The mockup is the **game concept AND the layout reference**: save its URL,
64
+ compare the finished HUD against it, and sample runtime text colors from it
65
+ with `npx genex ui text-color`.
@@ -19,9 +19,13 @@ Fill `[ASSET_LIST]`, then pass the whole text as the prompt to
19
19
  translucent or solid: plates are rebuilt at runtime in CSS (rgba,
20
20
  backdrop-filter, or a solid fill — whatever matches the mockup), so ask
21
21
  for the panel's frame/brackets/ornament, not the panel surface itself;
22
- - **requests the annotated duplicate** for every bar/meter frame (the
23
- template's progress block below covers the mechanics — your item just says
24
- "output the clean frame + annotated duplicate pair").
22
+ - **requests the annotated duplicate** for every CONTINUOUS bar/meter frame
23
+ (the template's progress block below covers the mechanics — your item just
24
+ says "output the clean frame + annotated duplicate pair");
25
+ - **requests variant cells for discrete icon counters** — hearts, ammo
26
+ pips, stars ship as repeated sprites, not masks, so their item asks for
27
+ the states as SEPARATE same-size cells ("the heart icon: one full, one
28
+ half, one empty cell") instead of an annotated pair.
25
29
 
26
30
  ## The template
27
31
 
@@ -34,7 +38,7 @@ Lay these assets on a pure flat white #ffffff background, separated by at least
34
38
 
35
39
  If an asset is a generated progress/bar/meter frame, output TWO CELLS immediately beside each other:
36
40
  1. LEFT CELL — the clean production asset the game will use. Preserve exact style, baked_static labels, frame metal, bevels, dividers, scratches, proportions, and texture. Remove runtime dynamic fill and values. Do not paint technical colors on this clean cell.
37
- 2. RIGHT CELL — an annotated duplicate of the same production asset. It must be the same asset, same scale, same canvas bounds, same position, and same outer shape as the left cell. The only difference: paint runtime-fill zones with pure green key color #00ff00. Green marks only the places where runtime bar/progress fill should appear. Paint the green as a FLAT, UNIFORM, fully-saturated #00ff00 fill — no shading, no gradient, no inner shadow, no vignette, NO DARKENING TOWARD THE EDGES, no lighting, no highlights, no texture; constant color edge-to-edge with a HARD, CRISP boundary against the frame. The green is a chroma-key, not a lit surface — any edge shading makes the extracted alpha mask ragged. For discrete segment meters, paint each segment slot as a separate green shape; do not merge slots into one continuous strip and do not paint half-segments.
41
+ 2. RIGHT CELL — an annotated duplicate of the same production asset. It must be the same asset, same scale, same canvas bounds, same position, and same outer shape as the left cell. The only difference: paint runtime-fill zones with pure green key color #00ff00. Green marks only the places where runtime bar/progress fill should appear. Paint the green as a FLAT, UNIFORM, fully-saturated #00ff00 fill — no shading, no gradient, no inner shadow, no vignette, NO DARKENING TOWARD THE EDGES, no lighting, no highlights, no texture; constant color edge-to-edge with a HARD, CRISP boundary against the frame. The green is a chroma-key, not a lit surface — any edge shading makes the extracted alpha mask ragged. For segmented meters, paint each segment slot as a separate green shape; do not merge slots into one continuous strip and do not paint half-segments. Never place a label or ornament in the middle of a fill channel — labels belong above/beside the channel or on the frame's end caps.
38
42
 
39
43
  Do NOT create a separate black-background mask cell. Do NOT create a standalone simplified silhouette. The right cell is a registration-safe annotation map; local tooling derives the alpha mask from its green pixels and uses the left cell as the frame.
40
44
 
@@ -38,6 +38,53 @@ might suggest. Two hard rules for the frame prompt:
38
38
  menus baked into the image. All of that is DOM on top. If the image model
39
39
  draws UI into the frame, the prompt was wrong — rewrite and regenerate.
40
40
 
41
+ This dedicated UI-free **menu still is the ONLY valid `--frame` input** for
42
+ the menu video. The game concept is the HUD mockup now (`$genex-ai-hud`
43
+ Stage 1) — it has a HUD baked into it, so feeding it to `genex video
44
+ --frame` ships a menu with a HUD floating in the backdrop: an instant
45
+ defect, not a shortcut. Anchor the still to the mockup with
46
+ `--edit <mockup-url>` so menu and world share one palette and light — then
47
+ animate the STILL.
48
+
49
+ ## Choose the composition — one archetype per game, justified
50
+
51
+ The single most common menu failure is every game shipping the same
52
+ left-rail layout regardless of genre. Before writing menu markup, pick ONE
53
+ archetype and justify it from the style brief in the UI plan message
54
+ (`$genex-threejs-game-ui`'s gate) — **defaulting to left-rail without a
55
+ reason is the failure mode this section exists to kill:**
56
+
57
+ - **Left rail** — title upper-left or top, buttons stacked left of center
58
+ (~38%), vista breathing on the right. Fits: exploration, RPG, anything
59
+ whose still has a strong right-side subject. (The worked example below
60
+ implements this one — it is ONE archetype, not the default.)
61
+ - **Centered stack** — logotype dominant top-center, buttons in a tight
62
+ column directly under it. Fits: title-driven games, arcade, sports,
63
+ anything with a strong wordmark. Delta: center the title block, buttons
64
+ `left: 50%; transform: translateX(-50%)`.
65
+ - **Bottom command bar** — buttons in one horizontal row along the bottom
66
+ edge, title above the fold. Fits: fighting, racing, arcade cabinets.
67
+ Delta: a flex row pinned to `bottom: 8%`, generous letter-spacing.
68
+ - **Boxed plate buttons** — buttons carry visible plates (brief-styled CSS
69
+ plates, or sprite chrome from the HUD sheet). Fits: RPG/fantasy,
70
+ crafted/ornate briefs. Delta: `.menu-btn` gets a background plate,
71
+ border, and hover lift instead of bare text.
72
+ - **Diegetic corner** — buttons anchored into a calm region OF THE SCENE
73
+ (a doorway, a dashboard, dead sky). Fits: horror, immersive sims; pairs
74
+ with a still prompted to leave negative space ("the lower right third is
75
+ calm open water").
76
+ - **Minimal fullbleed** — the vista is the hero; tiny corner links, no
77
+ stack at all. Fits: art games, ambient toys. Delta: 2–3 small text links
78
+ bottom-left, heavy letter-spacing, nothing over the subject.
79
+
80
+ **Second choice — button treatment,** stated in the same line: bare text
81
+ (cinematic, the example below) / CSS plate (styled FROM the brief —
82
+ gradients, borders, chamfers per the game-ui corner rules; default-gray
83
+ plates are a defect) / sprite chrome (from the HUD sheet or a dedicated
84
+ generation — Tier 2b, when the brief is ornate). The per-genre lean lives
85
+ in the style capsules (`$genex-threejs-game-ui`'s
86
+ references/style-capsules.md) — a lean, not a rule.
87
+
41
88
  ## Single-frame vs two-frame
42
89
 
43
90
  - **Single-frame (default — use this).** Generate ONE image and pass it as
@@ -65,9 +112,10 @@ might suggest. Two hard rules for the frame prompt:
65
112
 
66
113
  ```bash
67
114
  # 1. The frame — a still of the scene the loop returns to (this example is a
68
- # painterly meadow; YOUR prompt comes from YOUR game's style brief). When the
69
- # game-ui gate produced a concept frame, anchor the still to it with
70
- # --edit <concept-url> so menu and world share one palette and light:
115
+ # painterly meadow; YOUR prompt comes from YOUR game's style brief). Anchor
116
+ # the still to the concept mockup with --edit <mockup-url> so menu and world
117
+ # share one palette and light (the still stays UI-free; the mockup itself is
118
+ # NEVER the video frame — it has a HUD in it):
71
119
  npx genex image "windswept alpine meadow at golden hour, wildflowers leaning in the gusts, painterly light, cinematic wide shot. Edge-to-edge cinematic composition. Full-bleed 16:9 frame. No UI elements, no text, no buttons in the frame." --aspect 16:9 --quality high
72
120
  # -> https://assets.genex.technology/generations/<id>/image-main
73
121
 
@@ -150,8 +198,10 @@ fallback, not a downgrade to apologize for.
150
198
  ## Wire it as a phase screen
151
199
 
152
200
  The menu is one `data-phase` screen in the `$genex-threejs-game-ui`
153
- architecture (the `#ui` overlay + `setPhase()` state machine). Complete,
154
- copy-pasteable example:
201
+ architecture (the `#ui` overlay + `setPhase()` state machine). Complete
202
+ worked example — this implements the **left rail** archetype with **bare
203
+ text** buttons; adapt the positions and treatment to YOUR chosen archetype
204
+ (the wiring — stagger, selection, intents — carries to all of them):
155
205
 
156
206
  ```html
157
207
  <div id="ui">
@@ -288,15 +338,23 @@ One display font + one body font per menu. Give text its own contrast against
288
338
  the moving video — layered text-shadows or a subtle dark plate (avoid heavy
289
339
  text strokes — they read as a browser game, not a title screen).
290
340
 
291
- **Title treatment.** A bare display font is the floor, not the ceiling. Two
292
- upgrades, in order of effort:
293
-
294
- - **Layered CSS (free):** gradient ink via `background-clip: text` +
295
- stacked shadows (a tight dark one for contrast, a wide soft one for glow).
296
- - **A generated logotype (Tier 3 — offer it, don't pre-build):**
297
- `npx genex image "the word 'EMBERFALL' as an ornate engraved game logo, <style brief>" --transparent`,
298
- cleaned with `--bg-mode glyph` if letter counters get eaten, trimmed with
299
- `npx genex ui trim`. Short names (one or two words) come out best.
341
+ **Title treatment the generated logotype is the DEFAULT, not an upsell.**
342
+ Every game with a menu gets a generated wordmark (Tier 2; skipping it needs
343
+ a one-line stated reason in the UI plan message — "the brief is minimal
344
+ fullbleed and a set wordmark would fight the vista" is a reason; silence is
345
+ not):
346
+
347
+ - **Logotype (default yes):**
348
+ `npx genex image "the word 'EMBERFALL' as an ornate engraved game logo, <style brief>" --transparent`
349
+ the wordmark alone on a transparent background, in the brief's display
350
+ register, no extra text or scenery. Clean with `--bg-mode glyph` if letter
351
+ counters get eaten, trim with `npx genex ui trim`. Short names (one or two
352
+ words) come out best. Wire it as the menu title AND the loader mark; the
353
+ DOM keeps an accessible text fallback (`aria-label` or visually-hidden
354
+ text).
355
+ - **Layered CSS (the fallback while it renders, or the stated-reason
356
+ skip):** gradient ink via `background-clip: text` + stacked shadows (a
357
+ tight dark one for contrast, a wide soft one for glow).
300
358
 
301
359
  ## Tips
302
360
 
@@ -308,10 +366,15 @@ upgrades, in order of effort:
308
366
  still frame right first (images are cheap to redo), then animate it once.
309
367
  The one exception: a user-driven style change (the game-ui concept loop
310
368
  re-anchored the look) re-opens this rule once — re-edit the still
311
- (`--edit` against the new concept frame) and re-run the video from the new
312
- still. Agent-initiated polish never does.
313
- - **720p is right.** It's a background under UI; higher resolutions cost more,
314
- take longer, and change nothing visible.
369
+ (`--edit` against the new concept mockup) and re-run the video from the
370
+ new still. Agent-initiated polish never does.
371
+ - **1080p is the default leave it.** The menu clip is full-screen key art,
372
+ the first moving thing the player sees, and 720p visibly softens it on any
373
+ modern display. `genex video` renders 1080p by default; pass
374
+ `--resolution 720p` only as an explicit cost fallback (about half the
375
+ credits) when the user asks to economize. The flip side of 1080p: getting
376
+ the phone rule above wrong now costs twice as much bandwidth — poster on
377
+ phone tiers, always.
315
378
  - **Pause/victory/defeat variants reuse the same video — as GRADES.** Same
316
379
  `<video>` element or URL, different emotion via CSS `filter` on the
317
380
  background: pause = a plain dark overlay (`rgba(0,0,0,0.55)`); defeat =
@@ -341,6 +404,9 @@ upgrades, in order of effort:
341
404
  genuine state change; expect a loop seam.
342
405
  - `--duration <sec>` (video) — 4, 6, or 8 for frame-conditioned clips;
343
406
  default 8.
407
+ - `--resolution <720p|1080p>` (video) — default 1080p; `720p` is the
408
+ explicit cost fallback (~half the credits). Loop clips (`--loop`) ignore
409
+ it (that model has no resolution parameter).
344
410
  - `--aspect 16:9 --quality high` (image) — the right settings for a menu frame.
345
411
  - `--no-wait` — enqueue and return immediately with the generation id; pick
346
412
  the result up later with `npx genex wait <id>` (safe to re-run — it attaches
@@ -65,22 +65,37 @@ side — reads as broken at a glance.
65
65
 
66
66
  ## Load it into the scene
67
67
 
68
- Use Three.js `GLTFLoader` with the URL the command printed (R2 sends the right
69
- CORS headers, so cross-origin loading just works):
68
+ Load through the quality kit's decoder-wired loader and the model rung ladder
69
+ (`$genex-threejs-adaptive-quality`; `npx genex controller quality` installs
70
+ it). The bare URL is the provider-raw original — multiple 1–4K PBR textures +
71
+ dense geometry; phones load the `@1024` rung instead (textures budgeted to
72
+ 1024, meshopt-compressed, simplified) and KTX2-capable games get the
73
+ GPU-compressed sibling. Desktop always loads the original. R2 sends the right
74
+ CORS headers, so cross-origin loading just works:
70
75
 
71
76
  ```ts
72
- import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
77
+ import { createGltfLoader } from "./controllers/quality/gltf-loader.ts";
78
+ import { loadModelWithFallback } from "./controllers/quality/pick-asset.ts";
79
+ import { detectTier } from "./controllers/quality/tier.ts";
73
80
 
74
- const loader = new GLTFLoader();
81
+ const tier = detectTier(); // reuse the boot tier if you already have it
82
+ const gltfLoader = createGltfLoader(renderer); // once at boot — meshopt + KTX2
75
83
  // the URL `npx genex model` printed:
76
84
  const MODEL_URL = "https://assets.genex.technology/generations/<id>/model-glb";
77
- const gltf = await loader.loadAsync(MODEL_URL);
85
+ const gltf = await loadModelWithFallback(
86
+ MODEL_URL, tier, (u) => gltfLoader.loader.loadAsync(u), { ktx2: gltfLoader.ktx2 },
87
+ );
78
88
  const model = gltf.scene;
79
89
  model.scale.setScalar(1); // tune to taste
80
90
  model.position.set(0, 0, 0);
81
91
  scene.add(model);
82
92
  ```
83
93
 
94
+ A missing rung falls back to the original automatically (each fallback warns —
95
+ never silent). Without the quality kit a plain
96
+ `new GLTFLoader().loadAsync(MODEL_URL)` still works, but every device pays for
97
+ the full original — phones included.
98
+
84
99
  Never swallow a failed load silently: if you wrap a generated-asset load in a
85
100
  `catch`, `console.warn` the asset name in it — your own self-check reads the
86
101
  console, and a bare `.catch(() => null)` hides a missing model from you too.