@genex-ai/cli-demo 0.84.0-dev.215 → 0.86.0-dev.217

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "0.84.0-dev.215",
3
+ "version": "0.86.0-dev.217",
4
4
  "description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -324,6 +324,17 @@ export interface PlayOneShotOptions {
324
324
  clamp?: boolean;
325
325
  /** Fired when the clip finishes (skipped if a newer one-shot interrupts it). */
326
326
  onDone?: () => void;
327
+ /**
328
+ * Movement cancels this one-shot. Every one-shot is FULL-BODY (there is no
329
+ * upper-body layering), so it freezes the legs — a moving character SLIDES
330
+ * across the ground in a static pose for the clip's whole length (a 2.9s hit
331
+ * reaction, a 2.7s cast). With `interruptible: true`, grounded movement
332
+ * intent releases the clip via {@link CharacterAnimations.clearOneShot} and
333
+ * locomotion takes over immediately. Use it for reactions and gestures a
334
+ * player can walk out of; leave it off for poses that must hold to their end
335
+ * (a death/knockdown). Default false.
336
+ */
337
+ interruptible?: boolean;
327
338
  }
328
339
 
329
340
  type MutableAnimationStateContext = {
@@ -358,6 +369,9 @@ export class CharacterAnimations {
358
369
  // When true, the active one-shot holds its final pose on finish (clamp:true —
359
370
  // e.g. Death01) instead of crossfading back to locomotion.
360
371
  #oneShotHoldPose = false;
372
+ // When true, grounded movement intent cancels the active one-shot (see
373
+ // PlayOneShotOptions.interruptible).
374
+ #oneShotInterruptible = false;
361
375
 
362
376
  #state: CharacterAnimationState = "IDLE";
363
377
  #prevActionName: string | null;
@@ -398,7 +412,27 @@ export class CharacterAnimations {
398
412
  options: CharacterAnimationsOptions = {}
399
413
  ) {
400
414
  this.#model = model;
401
- this.#clipMap = buildClipMap(clips, options.clipMap);
415
+ // Seed the fuzzy name-matched map from the locomotion profile's own slots
416
+ // (explicit options.clipMap still wins). Simple boolean snapshots (remote
417
+ // players, bots) never reach the profile's directional resolver and fall
418
+ // through to this map — without the seed they bind DIFFERENT clips than the
419
+ // local player ("Running" vs the profile's run.forward), so the same
420
+ // movement renders as two different gaits on two screens. Jump states are
421
+ // deliberately NOT seeded: jump.full would one-shot-clamp as a loop.
422
+ const slots = options.locomotionProfile?.slots ?? {};
423
+ const profileSeed: Partial<Record<CharacterAnimationState, string>> = {};
424
+ const seedPairs: ReadonlyArray<[CharacterAnimationState, string]> = [
425
+ ["IDLE", "idle.default"],
426
+ ["WALK", "walk.forward"],
427
+ ["RUN", "run.forward"],
428
+ ["CROUCH_IDLE", "crouch.idle"],
429
+ ["CROUCH_MOVE", "crouch.forward"],
430
+ ];
431
+ for (const [state, slot] of seedPairs) {
432
+ const clip = slots[slot];
433
+ if (clip) profileSeed[state] = clip;
434
+ }
435
+ this.#clipMap = buildClipMap(clips, { ...profileSeed, ...options.clipMap });
402
436
  this.#resolver = options.resolver ?? resolveAnimationState;
403
437
  this.#onChange = options.onChange;
404
438
  this.#locomotionProfile = options.locomotionProfile;
@@ -542,6 +576,13 @@ export class CharacterAnimations {
542
576
  // Optional in the snapshot (older five-boolean remote-player objects).
543
577
  ctx.crouchActive = snapshot.crouchActive ?? false;
544
578
 
579
+ // Grounded movement cancels an interruptible one-shot — otherwise the
580
+ // frozen full-body pose slides across the ground for the clip's whole
581
+ // length. clearOneShot crossfades locomotion back in with no T-pose frame.
582
+ if (this.#oneShotAction && this.#oneShotInterruptible && ctx.isMoving && ctx.isOnGround) {
583
+ this.clearOneShot();
584
+ }
585
+
545
586
  const next = this.#resolver(ctx);
546
587
  const stateChanged = next !== this.#state;
547
588
  if (stateChanged) this.#state = next;
@@ -550,6 +591,19 @@ export class CharacterAnimations {
550
591
  // (Jump_Start/Jump_Land) finishes and unlocks, the pending loop clip must still fade in
551
592
  // even though the state did not change again. Internal guards make this a no-op otherwise.
552
593
  this.#desiredMotionName = this.#resolveLocomotionClip(snapshot, next) ?? this.#clipMap[next];
594
+ // Rigs whose airborne slots fall back to the SAME clip as jump.start (every
595
+ // Meshy rig: jump.rise/jump.fall → jump.full) never re-enter #applyTransition
596
+ // while airborne — the name never changes — so once the one-shot finishes the
597
+ // character hangs FROZEN in the jump's final frame for the rest of the fall
598
+ // (long drops, knockback launches). Once the held action has finished
599
+ // (paused+clamped), retarget sustained airtime at the idle loop so the limbs
600
+ // keep animating while descending.
601
+ if ((next === "JUMP_IDLE" || next === "JUMP_FALL") && this.#desiredMotionName !== null) {
602
+ const held = this.#actions.get(this.#desiredMotionName);
603
+ if (held && held.paused) {
604
+ this.#desiredMotionName = this.#resolveProfileSlot("idle.default") ?? this.#clipMap.IDLE;
605
+ }
606
+ }
553
607
  // A full-jump profile clip may outlast a very short physics hop. Release
554
608
  // its one-shot lock as soon as the controller lands so idle/walk can
555
609
  // crossfade immediately instead of waiting for the authored clip to end.
@@ -643,6 +697,7 @@ export class CharacterAnimations {
643
697
  // finish and the rig snaps to bind pose for a frame — the T-pose flash.
644
698
  action.clampWhenFinished = true;
645
699
  this.#oneShotHoldPose = options.clamp ?? false;
700
+ this.#oneShotInterruptible = options.interruptible ?? false;
646
701
  action.timeScale = options.timeScale ?? 1;
647
702
  action.reset();
648
703
  if (current && current !== action) {
@@ -660,6 +715,42 @@ export class CharacterAnimations {
660
715
  return true;
661
716
  }
662
717
 
718
+ /**
719
+ * Cancel a one-shot and hand control back to locomotion NOW. The case that
720
+ * REQUIRES it: a `clamp:true` one-shot (death/knockdown, a held wind-up)
721
+ * holds its final frame and keeps the locomotion lock closed FOREVER by
722
+ * design — so a respawn or an aborted action must explicitly release it, or
723
+ * the rig keeps the clamped pose while the body moves around. Safe to call
724
+ * at any time; a no-op when nothing is held.
725
+ *
726
+ * Call it on TRANSITIONS (respawn, revive, action aborted) — never per
727
+ * frame, or it cancels every in-flight reaction clip.
728
+ *
729
+ * @example
730
+ * // on respawn, before teleporting the body:
731
+ * anims.clearOneShot();
732
+ */
733
+ clearOneShot(): void {
734
+ if (this.#disposed) return;
735
+ const finished = this.#oneShotAction;
736
+ this.#oneShotAction = null;
737
+ this.#oneShotOnDone = undefined;
738
+ this.#oneShotHoldPose = false;
739
+ this.#oneShotInterruptible = false;
740
+ this.#canPlayNext = true;
741
+ // Crossfade the current state's loop in FROM the held pose — same path the
742
+ // "finished" handler uses, so there is no unposed/T-pose frame.
743
+ if (finished) this.#recoverFromOneShot(finished);
744
+ }
745
+
746
+ /** Clip name of the currently-playing (or clamped-held) one-shot, or null.
747
+ * Lets a caller cancel a SPECIFIC one-shot (e.g. a held charge wind-up)
748
+ * without clobbering whatever replaced it:
749
+ * `if (anims.currentOneShotName === WINDUP_CLIP) anims.clearOneShot();` */
750
+ get currentOneShotName(): string | null {
751
+ return this.#oneShotAction?.getClip().name ?? null;
752
+ }
753
+
663
754
  /** Stop all actions, uncache clips, remove the mixer's 'finished' listener. */
664
755
  dispose(): void {
665
756
  if (this.#disposed) return;
@@ -766,9 +857,23 @@ export class CharacterAnimations {
766
857
  #releaseStuckLocks(): void {
767
858
  const prevActionName = this.#prevActionName;
768
859
  if (prevActionName === null) return;
860
+ // Match BOTH bindings a jump one-shot can have: the fuzzy clipMap names and
861
+ // the profile slots. On profile rigs the clipMap entries are usually null
862
+ // (no clip is literally named "jump_start"), which made this escape hatch
863
+ // dead code — a stuck jump lock could never self-release. The jump.land
864
+ // slot is ignored when it falls back to idle.default (same guard
865
+ // #applyTransition uses), or an idle prevAction would spuriously match.
866
+ const startName = this.#clipMap.JUMP_START ?? this.#resolveProfileSlot("jump.start");
867
+ const profileLand = this.#resolveProfileSlot("jump.land");
868
+ const landName =
869
+ this.#clipMap.JUMP_LAND ??
870
+ (profileLand !== this.#resolveProfileSlot("idle.default") ? profileLand : null);
871
+ // A manual playOneShot() lock is NOT ours to release — even when its clip
872
+ // happens to share a name with the jump binding (clearOneShot owns that).
873
+ if (this.#oneShotAction) return;
769
874
  if (
770
875
  !this.#canPlayNext &&
771
- prevActionName === this.#clipMap.JUMP_START &&
876
+ prevActionName === startName &&
772
877
  this.#state !== "JUMP_IDLE" &&
773
878
  this.#state !== "JUMP_START"
774
879
  ) {
@@ -776,7 +881,7 @@ export class CharacterAnimations {
776
881
  }
777
882
  if (
778
883
  !this.#canPlayNext &&
779
- prevActionName === this.#clipMap.JUMP_LAND &&
884
+ prevActionName === landName &&
780
885
  this.#state !== "IDLE" &&
781
886
  this.#state !== "JUMP_LAND"
782
887
  ) {
@@ -878,13 +983,19 @@ export class CharacterAnimations {
878
983
  snapshot: CharacterStateSnapshot,
879
984
  state: CharacterAnimationState,
880
985
  ): void {
881
- if (!this.#locomotionProfile || !isAdvancedSnapshot(snapshot) || this.oneShotActive) return;
986
+ // Gate on a numeric moveSpeed rather than the full advanced snapshot:
987
+ // remote players and bots drive with simple boolean snapshots plus an
988
+ // estimated speed (position deltas / their own velocity), and without this
989
+ // they play locomotion at a flat 1.0 while the local player's clips
990
+ // speed-match — visibly different gaits for the same movement.
991
+ const speed = (snapshot as Partial<AdvancedCharacterStateSnapshot>).moveSpeed;
992
+ if (!this.#locomotionProfile || typeof speed !== "number" || this.oneShotActive) return;
882
993
  const band = state === "RUN" ? "run" : state === "CROUCH_MOVE" ? "crouch" : state === "WALK" ? "walk" : null;
883
994
  if (!band || !this.#prevActionName) return;
884
995
  const nominal = this.#locomotionProfile.nominalSpeed?.[band];
885
996
  if (!nominal || nominal <= 0) return;
886
997
  const limits = this.#locomotionProfile.playbackRate ?? { min: 0.75, max: 1.35 };
887
- const rate = THREE.MathUtils.clamp(snapshot.moveSpeed / nominal, limits.min, limits.max);
998
+ const rate = THREE.MathUtils.clamp(speed / nominal, limits.min, limits.max);
888
999
  this.#actions.get(this.#prevActionName)?.setEffectiveTimeScale(rate);
889
1000
  }
890
1001
  }
@@ -122,6 +122,15 @@ The HUD overlays a **live 3D scene**. The pixels between widgets show the game.
122
122
  their opaque chrome (frame, brackets, ornament); the plate itself is
123
123
  rebuilt at runtime in CSS matching the mockup's tint (see the three layers
124
124
  above).
125
+ - **No rectangular backing plates behind bars, digits, or icons — in the
126
+ mockup, in sprites, or in CSS.** The Stage-1 template's widget-construction
127
+ paragraph forbids them at the source (widgets are shaped silhouettes drawn
128
+ over the scene — never delete that paragraph), and the runtime never adds
129
+ one back: ornament lives on the widget's own silhouette. A screen that
130
+ truly needs a plate (a menu sheet, an inventory panel) shapes it from the
131
+ art's real interior with `npx genex ui plate` — never a bare rounded
132
+ rectangle. Measured across 7 genres: without this rule every genre grows
133
+ heavy generic plates behind its bars and digits.
125
134
  - **4–7 widgets.** Fewer doesn't read as a HUD; more clutters the screen and
126
135
  burns generations.
127
136
  - **Every widget needs internal contrast** — a panel fill, outline stroke, or
@@ -225,22 +234,29 @@ and [references/stage2-prompt-template.md](references/stage2-prompt-template.md)
225
234
 
226
235
  **Order of work: kick Stage 1 off as your FIRST action at the UI plan
227
236
  gate — the Stage-1 mockup IS the game concept.** There is no separate
228
- UI-free concept image before it: this one generation carries scene + HUD,
229
- serves as the user's style checkpoint, and anchors all later art
230
- (`$genex-threejs-game-ui` owns the checkpoint choreography). **Stage 2 onward
231
- is frame-anchored, so it waits for the user's keep/change YES on that
232
- checkpoint kick it off the instant they approve, not before.** The scene half
237
+ UI-free concept image before it: this ONE generation (no candidate variants
238
+ unless the player asks) carries scene + HUD, serves as the user's style
239
+ checkpoint, and anchors all later art (`$genex-threejs-game-ui` owns the
240
+ checkpoint choreography). **The moment the mockup lands, enqueue Stage 2 +
241
+ the menu still + the logotype `--no-wait` IMMEDIATELY THEN show the
242
+ player the frame and ask keep/change as information, never as a gate.**
243
+ Silence = the concept stands; a "change" answer loops the concept with the
244
+ player's notes and the chain re-runs from the new frame — image-priced,
245
+ cheap by design, so say so in one line and do it. Only the menu VIDEO waits
246
+ (`$genex-ai-menu` owns its event triple). The scene half
233
247
  of the prompt is written in TEXT from the game plan — `[GAME_SCENE]` in the
234
248
  Stage-1 template: setting, the moment, what the player is doing, lighting.
235
249
  **If a concept/reference image already exists — the user's own concept art, or
236
250
  a look frame you generated and they approved — anchor Stage 1 to it with
237
- `--edit <that-url>` so the HUD inherits its exact palette, materials, and
238
- lighting; that anchoring is what makes the final HUD actually match the
251
+ `--edit <that-url-or-file>` so the HUD inherits its exact palette, materials,
252
+ and lighting; that anchoring is what makes the final HUD actually match the
239
253
  concept, and skipping it is why a text-only mockup drifts.** A text-only
240
- Stage 1 is the fallback for when no reference exists. (To `--edit` a
241
- user-supplied image it must be reachable as an R2 URL a bare chat attachment
242
- is not; reproduce it as a generation first, or say plainly you can't anchor to
243
- it rather than silently guessing.) Then write the
254
+ Stage 1 is the fallback for when no reference exists. A user-supplied
255
+ reference needs no upload step: `--edit` takes a local file path directly
256
+ (`--edit ./reference.png`, ≤4 MB inlined) a chat attachment saved to disk
257
+ is a perfectly good anchor, and "I can't feed the local screenshot into the
258
+ art chain" is never true and never a reason to reach for another tool.
259
+ Then write the
244
260
  widget layout and wiring code (placement, masked-fill scaffolding,
245
261
  plain-CSS placeholder bars) while it renders — the CSS HUD keeps
246
262
  the game playable until the sprites land. When a sprite lands it REPLACES
@@ -250,19 +266,14 @@ is the black-box defect. The mockup is a STYLE anchor
250
266
  only: the widget set and
251
267
  layout come from the element inventory and the game contract — a mechanic
252
268
  the mockup invented (a lap counter, a stamina orb) does not enter the HUD,
253
- and a mechanic it failed to show still does. Generate 2 mockup candidates in one
254
- call (`--candidates 2`) and pick the better one: a re-roll costs the whole
255
- serial chain, a second candidate costs nothing extra in wall-clock. If the
256
- user's concept-loop feedback rejects the style after Stage 2+ ran,
257
- re-run from Stage 1 with their notes — cheaper than it sounds (one image
258
- is the whole concept now), so say so in one line and do it.
269
+ and a mechanic it failed to show still does.
259
270
 
260
271
  ```bash
261
272
  # Stage 1 — the game CONCEPT: full HUD composited over the game's own scene.
262
- # TEXT-described; add `--edit <concept-url>` to anchor it to an existing
263
- # concept/reference image so the HUD inherits its exact style.
264
- # Two candidates in one call; pick the better, save its URL:
265
- npx genex image "<filled stage-1 prompt>" --size 2560x1440 --quality high --candidates 2
273
+ # TEXT-described; add `--edit <concept-url-or-local-file>` to anchor it to an
274
+ # existing concept/reference image so the HUD inherits its exact style.
275
+ # ONE concept image; save its URL:
276
+ npx genex image "<filled stage-1 prompt>" --size 2560x1440 --quality high
266
277
 
267
278
  # Stage 2 — deconstruct the mockup into an asset sheet on white:
268
279
  npx genex image "<filled stage-2 prompt>" --edit <mockup-url> --quality high
@@ -319,6 +330,12 @@ To fix ONE sprite, don't re-run the pipeline:
319
330
  (the canvas sliced it — regenerate the sheet with more margin around every
320
331
  element) or looks like a fragment (extreme aspect / near-empty box — try
321
332
  `--dilate` to glue split pieces).
333
+ - **Stacked meters cluster.** Meters that sit adjacent/stacked in the mockup
334
+ (HP directly over stamina) tend to come back as one clustered crop on the
335
+ sheet — when your mockup stacks meters, ask the Stage-2 sheet for extra
336
+ whitespace between the STACKED meter cells specifically (measured on a
337
+ live run: the tools hard-refuse the resulting neighbor-in-crop defect, so
338
+ spacing up front saves a sheet re-roll).
322
339
  - **gpt-image-2 silently squashes past 3:1** — never request a canvas with
323
340
  aspect beyond 3:1. For thin strips (a wide bar frame), generate inside a
324
341
  ≤3:1 canvas with margins, then `genex ui trim` down to the art.
@@ -619,10 +636,10 @@ separate concept spend), a couple of minutes each at high quality — budget an
619
636
  hour end to end, not five minutes. That fits comfortably inside the image rate
620
637
  limit; local `genex ui` steps are free and instant. Two rules keep the clock
621
638
  honest: the sprite pipeline runs WHILE you build (kick Stage 1 first, code
622
- against CSS placeholders, swap sprites in as stages land never make the
623
- player-facing v0 wait on it), and candidates beat re-rolls (`--candidates 2`
624
- on the mockup: choosing between two costs zero extra wall-clock; regenerating
625
- after a bad single costs the whole chain).
639
+ against CSS placeholders, swap sprites in as stages land), and the chain
640
+ never parks behind the keep/change answer — Stage 2, the menu still, and the
641
+ logotype enqueue the moment the mockup lands (the order-of-work rule above);
642
+ a later "change" loops the concept at image prices.
626
643
 
627
644
  ## Publish checklist
628
645
 
@@ -641,8 +658,9 @@ after a bad single costs the whole chain).
641
658
  - `npx genex image` — `--size <WxH>` exact pixels (multiples of 16, each side
642
659
  ≤ 3840, aspect at most 3:1); `--quality <low|medium|high>` (high for the
643
660
  mockup/sheet, medium for single sprites); `--candidates <2|3|4>` several
644
- variants in ONE call (use 2 for the mockup pick the better, skip the
645
- re-roll); `--edit <url>` image-to-image edit of an R2 URL; `--clean <url>`
661
+ variants in ONE call (only when the player asks for variants the
662
+ doctrine is ONE concept); `--edit <url|file>` image-to-image edit of an
663
+ R2 URL or a local image file (≤4 MB, inlined); `--clean <url>`
646
664
  background removal only; `--remove-bg` chains removal after a
647
665
  generation/edit; `--bg-mode <sprite|glyph|sheet>` picks the removal model
648
666
  (`glyph` for digits/closed shapes; `--clean` defaults to `sheet`; `matte`
@@ -1,7 +1,8 @@
1
1
  # Stage 1 prompt template — the game concept: full-HUD mockup
2
2
 
3
- Fill the four placeholders, then pass the whole text as the prompt to
4
- `npx genex image "<filled prompt>" --size 2560x1440 --quality high --candidates 2`.
3
+ Fill the placeholders, then pass the whole text as the prompt to
4
+ `npx genex image "<filled prompt>" --size 2560x1440 --quality high`.
5
+ ONE concept image — no candidate variants unless the player asks for them.
5
6
  This image IS the game concept — the user's style checkpoint and the anchor
6
7
  for all later art — so the scene half deserves the same care as the HUD half.
7
8
 
@@ -26,11 +27,35 @@ Placeholders:
26
27
  rival truck closing on the left, late-afternoon glare"
27
28
  - `[STYLE_BRIEF]` — your full 3–5 sentence style brief with the named 4–5 hue
28
29
  palette. Do NOT append the per-sprite cutout sentence here — this is a full
29
- frame, not a cutout.
30
+ frame, not a cutout. **End the brief with a named register anchor**: "in
31
+ the register of <2–3 real games>" from the table below. Anchors set the
32
+ REGISTER (stroke weight, ornament budget, composure) — never copy their
33
+ assets, and never show their names in-game. Pick 2–3; genres between rows
34
+ blend the neighbors.
35
+
36
+ | Genre register | Anchor games (2–3) |
37
+ |---|---|
38
+ | Gothic / soulslike | Bloodborne, Elden Ring |
39
+ | Clean sci-fi / arena shooter | Destiny 2, Titanfall 2 |
40
+ | Arcade racing / rally | Forza Horizon, vintage Baja rally decals |
41
+ | Cozy / farming / life sim | Stardew Valley, Spiritfarer |
42
+ | Analog survival horror | Resident Evil 7, Signalis |
43
+ | Retro pixel | Shovel Knight |
44
+ | Ornate high fantasy RPG | Diablo IV |
45
+
46
+ - `[N]` — the exact count of elements in `[ELEMENT_LIST]`, spelled out in
47
+ the ONLY-these line. The bans in that line are load-bearing: without them
48
+ the model completes the genre's canonical HUD past your list (invented
49
+ kill feeds, leaderboards, timers, extra slots — measured in 5/7 genres).
30
50
  - `[ELEMENT_LIST]` — a bullet list of your 4–7 chosen widgets with one-line
31
51
  descriptions. List only asset-backed widgets; pure-geometry elements
32
52
  (crosshairs, tick rails, plain shapes) are built in code and stay off the
33
- list.
53
+ list. **Name each element as the shaped object it is** — "bare glowing
54
+ digits with a pip row", "an etched line silhouette", "stencil digits held
55
+ by two rivet brackets". Never "panel" or "plate" unless a physical plate
56
+ IS the art (a diegetic device, a pinned note). **State exact counts**
57
+ ("exactly one tool slot", "exactly three gem sockets") — the model honors
58
+ them.
34
59
 
35
60
  ## The template
36
61
 
@@ -39,10 +64,12 @@ A screenshot of a complete game HUD for [GENRE_BRIEF]. The HUD is composited ove
39
64
 
40
65
  Visual style: [STYLE_BRIEF]
41
66
 
42
- The HUD includes these elements, arranged naturally as a real game would lay them out (you decide the layout — do not force a grid, place each element where it makes the HUD readable and combat-ready):
67
+ The HUD includes ONLY these [N] elements and nothing else — do not add any other UI: no kill feed, no leaderboard, no map, no timer, no chat, no crosshair, no extra slots, no duplicates. Arrange them naturally as a real game would lay them out (you decide the layout — do not force a grid, place each element where it makes the HUD readable and combat-ready):
43
68
 
44
69
  [ELEMENT_LIST]
45
70
 
71
+ Widget construction rules: every widget is a shaped object with its own silhouette — an ornamented frame or emblem drawn directly over the game scene. NO rectangular backing panels behind bars, digits, or icons; no dark filler boxes; the game scene stays visible right up to each widget's frame edge. All ornament and material character lives ON the frame outline itself. Keep the frames detailed and characterful — confident AAA game UI, not a sterile minimal overlay. Size widgets like a shipped game: the HUD hugs the screen edges and no single widget exceeds about one eighth of the frame width. Every meter channel interior reads visibly darker than both its fill and its surrounding frame.
72
+
46
73
  This is a real in-game screenshot. Sharp detail on every UI element. No motion blur on the HUD. The HUD is clear and combat-readable. No watermarks. No external annotations.
47
74
 
48
75
  CRITICAL — flat HUD framing: draw every HUD element flat and head-on, parallel to the screen plane, like a 2D overlay painted directly onto the display (orthographic / screen-space UI). The HUD must NOT be tilted, angled, skewed, rotated in 3D, shown in perspective, or made to recede into depth — no isometric interface, no vanishing point on the panels, no 3D-extruded or floating-at-an-angle widgets, no curved/wrapped screen. ONLY the game scene behind the HUD may show 3D depth and perspective; the HUD layer itself is a flat 2D plane with square-on, axis-aligned edges, so each widget can be cleanly cut out as a flat sprite.
@@ -53,6 +80,16 @@ not deconstruct cleanly (angled panel edges have no clean silhouette, so
53
80
  Stage 2 produces sliced, skewed cutouts). If the mockup comes back tilted,
54
81
  regenerate it before proceeding; do not try to salvage it downstream.
55
82
 
83
+ **Never delete the widget-construction paragraph either** — it is what keeps
84
+ the mockup from coming back as generic rectangles: without it, every genre
85
+ grows heavy rectangular backing plates behind its bars and digits, and the
86
+ lane's whole value (shaped, characterful chrome) is lost. The channel-contrast
87
+ sentence in it is deliberate phrasing: channels read "visibly darker than
88
+ both their fill and their frame" — a RELATIVE rule that works on light
89
+ palettes too (an absolute "dark" trough fought cozy/pale briefs). Stage 2's
90
+ EMPTY-state law (dark empty tracks on the sheet) is separate and stays
91
+ absolute.
92
+
56
93
  One more Stage-1 rule that pays off at mask time: **meter channels must be
57
94
  continuous** — never place a label or ornament in the MIDDLE of a fill
58
95
  channel (it splits the mask and the fill into fragments); labels sit above or
@@ -25,7 +25,11 @@ Fill `[ASSET_LIST]`, then pass the whole text as the prompt to
25
25
  - **requests variant cells for discrete icon counters** — hearts, ammo
26
26
  pips, stars ship as repeated sprites, not masks, so their item asks for
27
27
  the states as SEPARATE same-size cells ("the heart icon: one full, one
28
- half, one empty cell") instead of an annotated pair.
28
+ half, one empty cell") instead of an annotated pair;
29
+ - **asks for extra whitespace between meters that were STACKED in the
30
+ mockup** ("place the health-bar cells and the stamina-bar cells in
31
+ separate rows with generous spacing") — adjacent meters otherwise cluster
32
+ into one crop and the extraction tools refuse the pair.
29
33
 
30
34
  ## The template
31
35
 
@@ -180,11 +180,18 @@ cycle watched AS RENDERED. A metadata probe (ffprobe) can't see a seam, a
180
180
  panel covering the video, or a video that never plays — only watching can.
181
181
 
182
182
  **Work async — the menu must never block the game.** The still is
183
- `--edit`-anchored to the concept mockup, so it's style-dependent: generate it
184
- the moment the user approves the concept (not before), enqueue the video with
185
- `--no-wait`, ship the CSS menu
186
- (buttons + title over the frame IMAGE as a static backdrop), and swap the
187
- `<video>` in when `genex wait` prints the URL. The frame image doubles as the
183
+ `--edit`-anchored to the concept mockup, so it's style-dependent: enqueue it
184
+ `--no-wait` **the moment the concept mockup LANDS** it rides the same
185
+ immediate chain as the HUD Stage-2 sheet and the logotype; the user's
186
+ keep/change answer never gates it (a later "change" just re-edits it against
187
+ the new frame, image-priced). **Only the VIDEO the expensive item waits,
188
+ and its trigger is an event, never a clock: fire it at the FIRST of (a) the
189
+ user's yes to the concept, (b) the next `genex preview` push after the still
190
+ has landed (a shipped milestone with the user silent = the pick stands), or
191
+ (c) style art being the only work left. Never fire it while a user objection
192
+ is open** ("change the colors" blocks it until the loop resolves). Ship the
193
+ CSS menu (buttons + title over the frame IMAGE as a static backdrop) and
194
+ swap the `<video>` in when `genex wait` prints the URL. The frame image doubles as the
188
195
  **loading screen background** — it exists minutes before the video does (see
189
196
  `$genex-threejs-game-ui`'s loader spec). Both assets live in Genex storage
190
197
  (R2) — permanent, public, CORS-open; you load them straight from the printed
@@ -10,6 +10,12 @@ tech demo that boots. You are the director; the other Genex skills are your
10
10
  specialists. Load only the skills that change the result; never the whole
11
11
  pack by default.
12
12
 
13
+ **After ANY context compaction or session resume**, re-read the game's
14
+ `AGENTS.md` (the Genex build contract block), `DESIGN.md`, and the skill for
15
+ the stage you are executing — never keep building from memory alone. A
16
+ compaction that eats the conversation does not release you from the
17
+ pipeline; those two files are how it comes back.
18
+
13
19
  ## 1. Check what you can do (once, before planning)
14
20
 
15
21
  Look at your own tool list and note the answers — the rest of this workflow
@@ -27,6 +33,13 @@ uses them:
27
33
  Never claim a capability you didn't find, and never stall because one is
28
34
  missing.
29
35
 
36
+ One more thing to note while you're looking: your platform may bundle its own
37
+ image / video / site generation workflows. **They are not part of any Genex
38
+ lane.** All generated art, audio, video, characters, and UI come from `genex`
39
+ commands, unless the player explicitly asks for another tool by name — and a
40
+ local reference image is never a reason to switch tools: `genex image --edit`
41
+ and `--inpaint` take a local file path directly.
42
+
30
43
  ## 2. Scope check — what is this?
31
44
 
32
45
  - **A new game** → the full flow: contract (§3), teams menu (§4), build order
@@ -58,7 +71,10 @@ for the user. Ask only what genuinely forks the build:
58
71
  - any real ambiguity in the request itself.
59
72
 
60
73
  Never ask about SDKs, engines, renderers, file layout, or anything
61
- technical — those are your decisions. If the player is silent or has no way
74
+ technical — those are your decisions. **The silence fallback applies only
75
+ AFTER the questions have been posted in chat.** A request that already names
76
+ the game does not skip the interview — then it's the confirm-pitch round.
77
+ Asking is never optional; waiting is: if the player is silent or has no way
62
78
  to answer, proceed on your own stated assumptions and write each one into
63
79
  DESIGN.md → Decisions as "assumed — player didn't answer"; the build never
64
80
  stalls on the interview.
@@ -132,7 +148,7 @@ game that needs concrete objects or surfaces, decide a small core set from the
132
148
  game IDEA — and from the Content lines when there are any (locations and the
133
149
  enemy roster name the set) — and put it in the Assets table up front. This set
134
150
  is concept-INDEPENDENT (prompted from the idea, not the concept image, and it
135
- mostly survives a style change), so it does NOT wait on the concept yes. Each
151
+ mostly survives a style change), so it never waits on the concept at all. Each
136
152
  `npx genex` job is an independent ~1-minute render: launch them concurrently
137
153
  in the background (`--no-wait`), scaffold the scene while they run, and wire
138
154
  each in as it lands, with a procedural placeholder until then:
@@ -164,11 +180,18 @@ The mandatory rows, in order, each with its one "done when" line:
164
180
  shows; the tier is what keeps a phone boot alive.
165
181
  3. **UI plan gate** — `$genex-threejs-game-ui`, every game: the screen
166
182
  inventory, one shared style brief, 2–3 AAA references, the menu archetype,
167
- then the concept image with its full HUD already on it. Show the player
168
- the picture the moment it lands and ask the keep-or-change question with
169
- your question tool. Done when: the player said yes to a frame. The yes
170
- gates ONLY style-dependent art everything else keeps moving while you
171
- wait.
183
+ then ONE concept image with its full HUD already on it (no candidate
184
+ variants unless the player asks). **The moment it lands: decide the HUD
185
+ lane (the lane beat below), enqueue the Stage-2 sheet + the menu still +
186
+ the logotype `--no-wait` IMMEDIATELY, and only THEN show the player the
187
+ frame and ask keep-or-change with your question tool — as information,
188
+ never as a gate.** Silence = the concept stands; a "change" answer loops
189
+ the concept with the player's notes and the chain re-runs from the new
190
+ frame (image-priced — cheap by design). Only the menu VIDEO waits, for
191
+ the FIRST of: the player's yes · the next `genex preview` after the menu
192
+ still landed · style work being the only work left — and it never fires
193
+ while a player objection is open. Done when: the sheet, still, and
194
+ logotype are enqueued and the frame is in front of the player.
172
195
  4. **Content contract when the request names plural content** — quests,
173
196
  enemies, bosses, locations, spells, items, or a content genre (an RPG, an
174
197
  adventure, an open world, a story game) — `$genex-threejs-game-content`.
@@ -188,10 +211,26 @@ The mandatory rows, in order, each with its one "done when" line:
188
211
  declares its `genex.matchmaking` block before preview. Done when: the
189
212
  model, start rule, and late-join behavior are stated in DESIGN.md and the
190
213
  netcode feel gate ran before handoff.
191
- 7. **Ship the first playable v0 fast and preview it.** The scaffold prompt
192
- owns the player-facing milestones and links — don't restate them; obey
193
- them. Done when: the player has a draft link within about ten minutes of
194
- the build starting.
214
+ 7. **Ship the playable v0 and preview it.** The scaffold prompt owns the
215
+ player-facing milestones and links — don't restate them; obey them. Done
216
+ when: the v0 loop is genuinely playable and the player has their draft
217
+ page link.
218
+
219
+ **The HUD lane — the concept decides it, and DESIGN.md records it.** You
220
+ make this call as the art director the moment the mockup lands: ornate /
221
+ painterly / material widget chrome (carved bone, etched metal, glowing
222
+ runes, brushed gold) → **sprites** — the `$genex-ai-hud` pipeline is
223
+ mandatory. Chrome that is flat geometry + typography, where a CSS rebuild
224
+ would be screenshot-indistinguishable from the mockup → **CSS allowed**,
225
+ styled from the brief. The litmus test: would a screenshot of the CSS
226
+ rebuild pass for the mockup at a glance? Unsure or ambiguous → sprites.
227
+ Record it as one DESIGN.md line — `HUD lane: sprites (…)` or
228
+ `HUD lane: CSS (…, one-line justification)` — the preview preflight checks
229
+ for it. In BOTH lanes: micro-text (damage numbers, timers, ammo digits)
230
+ stays HTML text in the brief's font, and no rectangular backing plates
231
+ behind bars, digits, or icons — ever (a truly needed shaped plate comes
232
+ from `npx genex ui plate`). Only the player may decline the generated HUD,
233
+ and the player's explicit lane request wins in both directions.
195
234
 
196
235
  Two rules for every game that moves (decide both before building, state them
197
236
  in DESIGN.md):
@@ -227,10 +266,12 @@ agent: you stay the director.
227
266
  rule is anti-collision, never a reason to serialize work.
228
267
  - You stay the integrator and the only writer of shared files (boot, main
229
268
  loop, netcode). Workers never spawn workers — one level deep, always.
230
- - Concept-DEPENDENT rows (the HUD chain, style-matched art) wait for the
231
- player's yes on the concept; concept-INDEPENDENT rows (world/terrain,
232
- content data, enemies, asset wiring) launch immediately. Typing a big game
233
- alone, line by line, is how sessions run out before the world exists.
269
+ - Concept-DEPENDENT rows (the HUD chain, style-matched art) launch the
270
+ moment the concept LANDS — the Stage-2 chain enqueues immediately (§5.3);
271
+ only the menu video waits for its event triple. Concept-INDEPENDENT rows
272
+ (world/terrain, content data, enemies, asset wiring) launch immediately
273
+ either way. Typing a big game alone, line by line, is how sessions run
274
+ out before the world exists.
234
275
  - Give each worker everything by path: the `DESIGN.md` path, its Modules row,
235
276
  and the skill files it needs (skills live in this project —
236
277
  `.claude/skills/<name>/SKILL.md`, `.codex/skills/…`, or `.cursor/skills/…`,