@genex-ai/cli-demo 1.13.1-dev.558 → 1.13.2-dev.559

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 CHANGED
@@ -700,11 +700,11 @@ var CONTRACT_END = "<!-- genex:contract:end -->";
700
700
  var GENEX_CONTRACT_BLOCK = `${CONTRACT_BEGIN}
701
701
  # Genex build contract (always in effect for this game)
702
702
 
703
- Your agentic capabilities that can boost your creation (all via \`npx genex \u2026\`): generate \`model\` (\`--image\` from a reference image; \`model segment <id>\` into named parts; \`model rig <id>\` + \`model animate <rig-id> --preset walk\` for non-biped bodies \u2014 7 body plans) \xB7 \`sfx\` \xB7 \`music\` \xB7 \`voice\` \xB7 \`texture\` \xB7 \`image\` (\`--edit\` \xB7 \`--inpaint\` \xB7 \`--glass\` \xB7 \`--clean\` \xB7 \`--upscale\`) \xB7 \`video\` \xB7 rigged \`character\` / \`creature\` \xB7 \`character animate <id> "<verb>"\` (also \`creature animate\`; \`--locomotion\` for the 8-way movement set, \`--video\` for your own footage) \xB7 the pixel toolbox \`ui extract|masks|plate|text-color|trim|audit\` \xB7 vendored \`controller character|car|drone|touch|quality|chat\` \xB7 \`animations search\` \xB7 \`wait <id>\` / \`wait --all\` \xB7 \`preview\` / \`publish\` \xB7 \`rename <name>\` (move the game to a new address \u2014 keeps its plays and likes, kills the old link, needs \`--yes\` once published, and the game must be rebuilt after). Full options: \`npx genex --help\`. There is NO sky generator: every sky, horizon, time of day and weather mood is built in code \u2014 your call how, from a background colour and fog to an atmospheric sky shader with a real sun. It costs nothing, weighs nothing on phones, and keeps the sun in sync with the scene's own lighting, which a painted panorama never can. Task\u2192lane routing lives in the \`genex-game-director\` skill's routing map \u2014 re-load it whenever you're unsure which lane owns a task.
703
+ Your agentic capabilities that can boost your creation (all via \`npx genex \u2026\`): generate \`model\` (\`--image\` from a reference image; \`model segment <id>\` into named parts; \`model rig <id>\` + \`model animate <rig-id> --preset walk\` for non-biped bodies \u2014 7 body plans) \xB7 \`sfx\` \xB7 \`music\` \xB7 \`voice\` \xB7 \`texture\` \xB7 \`image\` (\`--edit\` \xB7 \`--inpaint\` \xB7 \`--glass\` \xB7 \`--clean\` \xB7 \`--upscale\`) \xB7 \`video\` \xB7 rigged \`character\` / \`creature\` \xB7 \`character animate <id> "<verb>"\` (also \`creature animate\`; \`--locomotion\` for the 8-way movement set, \`--video\` for your own footage) \xB7 the pixel toolbox \`ui extract|masks|plate|text-color|trim|audit\` \xB7 vendored \`controller character|car|drone|touch|quality|chat\` \xB7 \`animations search\` \xB7 \`wait <id>\` / \`wait --all\` \xB7 \`preview\` / \`publish\` \xB7 \`rename <name>\` (move the game to a new address \u2014 keeps its plays and likes, kills the old link, needs \`--yes\` once published, and the game must be rebuilt after). Full options: \`npx genex --help\`. There is NO sky generator: every sky, horizon, time of day and weather mood is built in code \u2014 your call how, from a background colour and fog to an atmospheric sky shader with a real sun. It costs nothing, weighs nothing on phones, and keeps the sun in sync with the scene's own lighting, which a painted panorama never can. Task\u2192lane routing lives in the \`genex-game-director\` skill's routing map \u2014 load the owning row before the first file of each subsystem, and re-load the map after any compaction.
704
704
 
705
- Important note: put soul into your creations, with many details and love. Aim to make them realistic and feel real. Use genex capabilities as an extension, but never limit your imagination \u2014 you are a powerful agent. Use your built-in sub-agents and iteration loops (\`/loop\` or your platform's equivalent) where you need them to reach outstanding results. Verify yourself. Anything you put in front of the player should already feel alive and impressive \u2014 never a bare scene waiting for "later". Build what's still missing before polishing what already works.
705
+ Important note: put soul into your creations, with many details and love. Aim to make them realistic and feel real. Reach for genex capabilities by default \u2014 they are how the world gets real assets (law 22) \u2014 but never limit your imagination \u2014 you are a powerful agent. Use your built-in sub-agents and iteration loops (\`/loop\` or your platform's equivalent) where you need them to reach outstanding results. Verify yourself. Anything you put in front of the player should already feel alive and impressive \u2014 never a bare scene waiting for "later". Build what's still missing before polishing what already works.
706
706
 
707
- 1. Load the \`genex-game-director\` skill before starting the requested work. Route from the player's latest clear request: a focused request starts directly without replaying discovery or commissioning unrelated lanes. After ANY context compaction or session resume, re-read this file and \`DESIGN.md\`, re-load the skill for the stage you are executing, and continue from the Build plan's \`Now:\` line \u2014 never from memory alone.
707
+ 1. Load the \`genex-game-director\` skill before starting the requested work. Route from the player's latest clear request: a focused request starts directly without replaying discovery or commissioning unrelated lanes. Before the first file of any subsystem, load its owning lane from the director's routing map and write it as \`Lane:\` beside that work in \`DESIGN.md\` (\`Lane: none \u2014 <why>\` when no row matches) \u2014 a matching row is not optional, and loading one lane never commissions another. After ANY context compaction or session resume, re-read this file and \`DESIGN.md\`, re-load the skill for the stage you are executing, and continue from the Build plan's \`Now:\` line \u2014 never from memory alone.
708
708
  2. Ask only when one unresolved answer materially changes the work. If the request is clear, do not repeat an interview, confirm the pitch, force a concept round, or ask whole-game-vs-one-part again. For a genuinely broad new game, ask one decision: whole coordinated build or one request-relevant part first. If there is no clear request \u2014 a setup prompt pasted with nothing of their own \u2014 ask in PLAIN CHAT what they want to make, in their own words, and never fill the blank by pitching concepts. Once you know what you are building, ask one decision at a time with your built-in question tool with clickable answer options when you have one; use a short plain-chat question otherwise. If the player stays silent after a necessary question, proceed on stated assumptions where reasonable and record each as "assumed \u2014 player didn't answer" in DESIGN.md \u2192 Decisions.
709
709
  3. \`DESIGN.md\` at the project root is the durable design contract AND build plan. Keep three truths distinct: the player's requested outcome, the current \`Now:\` focus, and open commitments. It must carry a \`## Build plan & status\` section with the working mode (\`whole coordinated build\`, \`step by step\`, or \`focused change\`), numbered milestones with status marks, and a \`Now:\` line naming the current one \u2014 a milestone is done only when its work reached a preview. The latest clear request may replace \`Now:\` immediately; it never silently shrinks the requested outcome or deletes unrelated commitments. Keep every decision, assumption, generation id/URL, local output, and wiring state current. When the plan first lands, tell the player in one plain line that you recorded what they asked for in \`DESIGN.md\` and will keep it current.
710
710
  4. Use \`genex\` commands to generate art, audio, video, characters, and UI. A local reference image is not a reason to switch tools: pass its file path to genex (\`--edit\`, \`--inpaint\`, and model's \`--image\` accept local paths). Don't limit yourself: add details, and mix generated assets with procedural generation to make scenes and games detailed and lively.
@@ -713,7 +713,7 @@ Important note: put soul into your creations, with many details and love. Aim to
713
713
  7. Never draw a rectangular backing plate behind bars, digits, or icons \u2014 in sprites or CSS. Ornament lives on the widget's own silhouette; a truly needed shaped plate comes from \`npx genex ui plate\`.
714
714
  8. Never park ready work behind a question, and never stall on an unanswered one \u2014 decide, state the decision in chat, record it, keep building.
715
715
  9. Before any publish and before ending a session: run \`npx genex wait\` on every generation you enqueued and wire in what landed \u2014 never park landed assets. Fonts the brief names are LOADED for real, Escape pauses, the loader shows something of the game rather than a black screen, and the player wears the game's own generated character (or DESIGN.md records why it doesn't).
716
- 10. The player's body is the game's own generated character (\`npx genex character "<look>"\` \u2192 \`npx genex controller character --character <id>\`), enqueued with your first art actions, not after them. It applies wherever a human body appears on screen \u2014 first-person included, the moment remotes, a look-down body, a shadow, or a menu portrait shows one. Games whose player is not a person (car, ship, RTS cursor, board) generate that object with \`npx genex model\` instead, or build it procedurally. Characters: Meshy/Mixamo/VRM rigs rest facing +Z. Set yaw explicitly when placing a rig; never mirror a SkinnedMesh with negative scale. In any two-character scene, verify in a capture that they face each other, not the camera.
716
+ 10. The player's body is the game's own generated character (\`npx genex character "<look>"\` \u2192 \`npx genex controller character --character <id>\`), enqueued with your first art actions, not after them. It applies wherever a human body appears on screen \u2014 first-person included, the moment remotes, a look-down body, a shadow, or a menu portrait shows one. Games whose player is not a person (car, ship, RTS cursor, board) generate that object with \`npx genex model\` instead, or build it procedurally \u2014 held to law 22's bar like everything else the player looks at. Characters: Meshy/Mixamo/VRM rigs rest facing +Z. Set yaw explicitly when placing a rig; never mirror a SkinnedMesh with negative scale. In any two-character scene, verify in a capture that they face each other, not the camera.
717
717
  11. Verify by looking: one smoke check per milestone, after that milestone's preview push, in local test mode (\`?genex_local_test=1\`) with a real gameplay screenshot. A claim without a capture is not verification. Local-test evidence proves visuals and controls ONLY \u2014 label it that way when you show the player, and never work around the draft sign-in gate any other way.
718
718
  12. Treat every \`genex\` warning line \u2014 preflight, \`ui audit\`, \`wait\` nudges \u2014 as work, not noise.
719
719
  13. Every finished Build-plan milestone ends with \`npx genex preview\` and the player's page link (\`<dashboard>/draft/<slug>\`, with \`<dashboard>\` from \`.genex/project.json\`) \u2014 never a localhost link, a file path, or the bare play origin presented as their game. That page shows the build you just previewed; \`preview\` never disturbs the build players are on. When the player is happy with it, \`npx genex promote\` makes that exact build live for everyone \u2014 no rebuild. After every round of player feedback, end with a preview push. After the first release, the game is two versions and the player only ever hears these two words for them: the **draft** (their working copy, updated by \`preview\`) and the **public version** (what everyone plays). Command names are yours, not theirs \u2014 never say "promote" to the player; ask directly: "Want me to update the public version?", one line, once per round, and keep building while you wait \u2014 updating the public version is the ONE action that waits for an explicit yes, and more change requests instead of a yes mean "not yet". The player's "publish it" / "publish the update" / "update it" / "yes" after the first release ALL mean \`genex promote\` \u2014 never \`genex publish\` again, which would ship an untried rebuild. \`promote\` ships the last previewed build, so if anything was edited since the last \`preview\`, preview again before promoting.
@@ -724,6 +724,8 @@ Important note: put soul into your creations, with many details and love. Aim to
724
724
  18. NEVER delete, empty, move, rename, or overwrite anything you did not create yourself. This folder may hold the player's own reference images, notes, sketches, or an earlier attempt \u2014 files that exist nowhere else and have no undo, no trash, no backup. A non-empty folder is normal and is NEVER something to clean up, and "start clean" is never a reason. That rules out \`rm\`/\`rm -rf\`, \`git clean\`, \`git checkout -- .\`, \`git reset --hard\` over their work, deleting to resolve a conflict or a stuck interactive prompt, and every setup tool's offer to empty a directory (\`--force\`, \`--overwrite\`, "Remove existing files") \u2014 scaffold into a fresh subfolder and copy in instead. You may add files and edit the ones you wrote. If a step genuinely cannot continue without removing something of theirs, STOP and ask, naming the exact files, and wait for a yes \u2014 "it looks like junk" is never that yes. This binds hardest during setup, where it runs fast and automatically before the player has asked for anything at all.
725
725
  19. If the game sells anything, it sells it for coin at a fixed, visible price, and NEVER sells chance. Run this test on any purchasable thing before building it: does the player pay (with coin, or with anything coin bought, directly or indirectly), is the outcome uncertain when they pay, and is there a prize they wanted \u2014 all three yes means it is paid randomness, and you build the deterministic version instead. That rules out loot boxes, gacha, mystery boxes, crates, card packs, prize wheels and raffles; wagering, staking, betting, coinflips and casino or slot mechanics denominated in coin; and donation prompts, tip jars or any player-to-player coin transfer, because coin buys goods and never just moves. Randomness the player EARNS by playing is gameplay, not commerce \u2014 an enemy dropping a random item, a chest found in the level, a procedural layout, a crit roll \u2014 and is completely fine. Every coin price renders its real-money equivalent beside it (the server sends one with every item), item prices sit on the platform's price grid so no player is left holding change they cannot spend, and nothing in a shop carries a countdown, "limited time", or a stock counter. When a request crosses one of these lines, name the mechanic, give the one-sentence reason, propose a specific compliant alternative, and build that \u2014 never the banned version "as an option", never a partial one, and never after asking the player to confirm they want it. Load \`$genex-monetization\` before building a shop.
726
726
  20. When the player names a specific thing to integrate \u2014 a library, a controller, an asset, a part of an existing game, or a tagged reference the platform placed in \`.genex/refs/<slug>/\` \u2014 integrate the real thing: read its source first and build from it, never a lighter look-alike written from scratch because it was faster. A tagged reference owns the subsystem it was tagged for, outranking the default lane and the vendored controllers for that subsystem. If you genuinely can't use it, say so in one plain line and ask how to proceed.
727
+ 21. A turn ends in exactly one of two ways: on a question the player must answer before the next step can be chosen, or on a handoff \u2014 the draft link, what changed, what to try, and what is still open. Never close a turn on a promise. "I'll keep building", "while I keep working", "adding that now" are things you say and then DO before you stop; if you are stopping, say that you have stopped and what remains. While the Build plan's \`Now:\` line has open work and no answer is needed from the player, the turn is not over: preview, hand off, and start the next milestone in the same turn, until the requested outcome is reached or the platform's own budget ends the session. If a stop was forced on you anyway, the first line of your next turn is the promise you left and where it stands.
728
+ 22. **What the player sees must read as the thing it is**, in this game's own style: a building as that building, a person as a person, a prop as that prop, a surface as its material \u2014 and a coloured box, a capsule, or a flat grey block standing in for one of them is never its finished version. Generation is the default route to that bar wherever code will not honestly reach it: the player's character, whatever the request names, whatever the player walks up to, enters, or interacts with, and the music and sound underneath \u2014 reach for the belt on your own judgment, without stopping to ask; a status line saying what you queued is enough, and \`--no-wait\` keeps you building while it lands. Procedural code is a first-class engine for what is structural, repeated, distant, or parametric \u2014 terrain, sky, fences, paving, modular kits, filler \u2014 and for anything else only when the result meets the same bar and you have looked at it in a capture before its row says \`landed\`. Decide per object, not per category, and write the route on each Assets row in \`DESIGN.md\` \u2014 the paid flow for generated pieces, the procedural flow with its local path for code-built ones. An Assets table with no generation in it is a decision, not a default: record it as \`Generation: none \u2014 <why code alone reaches the bar here>\` or generate.
727
729
  ${CONTRACT_END}
728
730
  `;
729
731
  var GENEX_TOOLS_CONTRACT_BLOCK = `${CONTRACT_BEGIN}
@@ -2466,10 +2468,222 @@ function relTime(iso) {
2466
2468
 
2467
2469
  // src/lib/deploy.ts
2468
2470
  import "child_process";
2471
+
2472
+ // src/lib/generation-ledger.ts
2473
+ import fs12 from "fs/promises";
2474
+ import path12 from "path";
2475
+ var ledgerPath = (cwd) => path12.join(cwd, ".genex", "generations.ndjson");
2476
+ async function append(cwd, event) {
2477
+ try {
2478
+ await fs12.access(path12.join(cwd, ".genex"));
2479
+ await fs12.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
2480
+ `, "utf8");
2481
+ } catch {
2482
+ }
2483
+ }
2484
+ async function readLedger(cwd = process.cwd()) {
2485
+ let raw;
2486
+ try {
2487
+ raw = await fs12.readFile(ledgerPath(cwd), "utf8");
2488
+ } catch {
2489
+ return [];
2490
+ }
2491
+ const entries = /* @__PURE__ */ new Map();
2492
+ for (const line of raw.split("\n")) {
2493
+ if (!line.trim()) continue;
2494
+ try {
2495
+ const e = JSON.parse(line);
2496
+ if (e.t === "q" && typeof e.id === "string" && typeof e.kind === "string") {
2497
+ if (!entries.has(e.id)) {
2498
+ entries.set(e.id, {
2499
+ id: e.id,
2500
+ kind: e.kind,
2501
+ prompt: typeof e.prompt === "string" ? e.prompt : "",
2502
+ ...typeof e.tail === "string" ? { promptTail: e.tail } : {},
2503
+ queuedAt: typeof e.at === "string" ? e.at : "",
2504
+ status: "queued"
2505
+ });
2506
+ }
2507
+ } else if (e.t === "done" && typeof e.id === "string") {
2508
+ const entry = entries.get(e.id);
2509
+ if (entry && (e.status === "completed" || e.status === "failed")) {
2510
+ entry.status = e.status;
2511
+ if (Array.isArray(e.urls)) entry.urls = e.urls.filter((u) => typeof u === "string");
2512
+ }
2513
+ }
2514
+ } catch {
2515
+ }
2516
+ }
2517
+ return [...entries.values()];
2518
+ }
2519
+ async function recordQueued(id, kind, prompt, cwd = process.cwd()) {
2520
+ await append(cwd, {
2521
+ t: "q",
2522
+ id,
2523
+ kind,
2524
+ prompt: prompt.slice(0, 120),
2525
+ ...prompt.length > 120 ? { tail: prompt.slice(-120) } : {},
2526
+ at: (/* @__PURE__ */ new Date()).toISOString()
2527
+ });
2528
+ }
2529
+ async function recordTerminal(id, status, urls, cwd = process.cwd()) {
2530
+ const existing = (await readLedger(cwd)).find((e) => e.id === id);
2531
+ if (existing && existing.status === status) return;
2532
+ await append(cwd, { t: "done", id, status, ...urls?.length ? { urls } : {} });
2533
+ }
2534
+ async function countFailed(kind, cwd = process.cwd()) {
2535
+ return (await readLedger(cwd)).filter((e) => e.kind === kind && e.status === "failed").length;
2536
+ }
2537
+ async function countOutcomes(kind, cwd = process.cwd()) {
2538
+ const rows = (await readLedger(cwd)).filter((e) => e.kind === kind);
2539
+ return {
2540
+ failed: rows.filter((e) => e.status === "failed").length,
2541
+ completed: rows.filter((e) => e.status === "completed").length
2542
+ };
2543
+ }
2544
+
2545
+ // src/lib/generation-plan.ts
2546
+ var LANE_OF_KIND = {
2547
+ image: "image",
2548
+ video: "video",
2549
+ sfx: "sfx",
2550
+ music: "music",
2551
+ voice: "voice",
2552
+ texture: "texture",
2553
+ skybox: "skybox",
2554
+ model: "model",
2555
+ model_segment: "model",
2556
+ model_rig: "model",
2557
+ model_animation: "model",
2558
+ character: "character",
2559
+ character_concept: "character",
2560
+ character_preview: "character",
2561
+ character_animation: "character",
2562
+ character_motion: "character",
2563
+ motion: "motion"
2564
+ };
2565
+ var LANE_OF_SKILL = {
2566
+ "genex-ai-model": "model",
2567
+ "genex-ai-texture": "texture",
2568
+ "genex-ai-image": "image",
2569
+ "genex-ai-video": "video",
2570
+ "genex-ai-sfx": "sfx",
2571
+ "genex-ai-music": "music",
2572
+ "genex-ai-voice": "voice",
2573
+ "genex-ai-character": "character",
2574
+ "genex-ai-hud": "image",
2575
+ "genex-ai-menu": "image"
2576
+ };
2577
+ var LANE_COMMAND = {
2578
+ model: "npx genex model",
2579
+ texture: "npx genex texture",
2580
+ image: "npx genex image",
2581
+ video: "npx genex video",
2582
+ sfx: "npx genex sfx",
2583
+ music: "npx genex music",
2584
+ voice: "npx genex voice",
2585
+ character: "npx genex character",
2586
+ motion: 'npx genex character animate <id> "<verb>"'
2587
+ };
2588
+ var LANE_TOKEN = /\$(genex-[a-z0-9-]+)/g;
2589
+ var PROCEDURAL_ROW = /procedural|code-built|hand-built|three\.?js|\.tsx?\b|building \(/i;
2590
+ var PAID_ROW = /\b(model|glb|mesh|texture|image|sprite|poster|decal|video|sfx|sound|music|track|voice|character|creature|motion)\b/i;
2591
+ var LANE_WORDS = [
2592
+ [/\b(model|glb|mesh)\b/i, "model"],
2593
+ [/\btexture\b/i, "texture"],
2594
+ [/\b(image|sprite|poster|decal)\b/i, "image"],
2595
+ [/\bvideo\b/i, "video"],
2596
+ [/\b(sfx|sound)\b/i, "sfx"],
2597
+ [/\b(music|track)\b/i, "music"],
2598
+ [/\bvoice\b/i, "voice"],
2599
+ [/\b(character|creature)\b/i, "character"],
2600
+ [/\bmotion\b/i, "motion"]
2601
+ ];
2602
+ var GENERATION_NONE = /^\s*Generation:\s*none\s*—/im;
2603
+ var STATUS = /\b(proposed|planned|generating|landed|wired)\b/i;
2604
+ function sectionRows(design, heading) {
2605
+ const lines = design.split("\n");
2606
+ const start = lines.findIndex((l) => heading.test(l));
2607
+ if (start < 0) return [];
2608
+ const rows = [];
2609
+ for (let i = start + 1; i < lines.length; i += 1) {
2610
+ const line = lines[i] ?? "";
2611
+ if (/^##\s/.test(line)) break;
2612
+ if (!line.trim().startsWith("|")) continue;
2613
+ const cells = line.split("|").map((c2) => c2.trim()).filter((c2) => c2.length > 0);
2614
+ if (cells.length === 0 || cells.every((c2) => /^:?-+:?$/.test(c2))) continue;
2615
+ if (/^asset$/i.test(cells[0] ?? "") || /^module$/i.test(cells[0] ?? "")) continue;
2616
+ rows.push(cells);
2617
+ }
2618
+ return rows;
2619
+ }
2620
+ function classify(cells) {
2621
+ const name = cells[0] ?? "";
2622
+ if (/^<.*>$/.test(name)) return null;
2623
+ const rest = cells.slice(1).join(" ");
2624
+ const status = (STATUS.exec(rest) ?? [""])[0].toLowerCase();
2625
+ if (PROCEDURAL_ROW.test(rest)) return { name, lane: null, status, procedural: true };
2626
+ const token = new RegExp(LANE_TOKEN.source).exec(rest)?.[1];
2627
+ const fromSkill = token ? LANE_OF_SKILL[token] : void 0;
2628
+ if (fromSkill) return { name, lane: fromSkill, status, procedural: false };
2629
+ if (!PAID_ROW.test(rest) && !/https?:\/\//.test(rest) && !/generating\s*\(/i.test(rest)) return null;
2630
+ const lane = LANE_WORDS.find(([re]) => re.test(rest))?.[1] ?? null;
2631
+ return { name, lane, status, procedural: false };
2632
+ }
2633
+ function auditGenerationPlan(input) {
2634
+ const { design, ledger } = input;
2635
+ if (design.trim().length === 0) return [];
2636
+ const assetRows = sectionRows(design, /^##\s+Assets\b/i);
2637
+ const proceduralRows = sectionRows(design, /^##\s+Procedural assets\b/i);
2638
+ const rows = [
2639
+ ...assetRows.map(classify),
2640
+ ...proceduralRows.map((c2) => {
2641
+ const r = classify(c2);
2642
+ return r === null ? null : { ...r, lane: null, procedural: true };
2643
+ })
2644
+ ].filter((r) => r !== null);
2645
+ const declaredLanes = /* @__PURE__ */ new Set();
2646
+ for (const m of design.matchAll(LANE_TOKEN)) {
2647
+ const lane = m[1] ? LANE_OF_SKILL[m[1]] : void 0;
2648
+ if (lane) declaredLanes.add(lane);
2649
+ }
2650
+ const lanesRun = new Set(ledger.map((e) => LANE_OF_KIND[e.kind] ?? e.kind));
2651
+ const warnings = [];
2652
+ const owed = /* @__PURE__ */ new Map();
2653
+ for (const row of rows) {
2654
+ if (row.procedural || row.lane === null) continue;
2655
+ if (row.status !== "planned" && row.status !== "generating") continue;
2656
+ if (lanesRun.has(row.lane)) continue;
2657
+ if (row.lane === "skybox") continue;
2658
+ const held = owed.get(row.lane) ?? [];
2659
+ held.push(row.status === "generating" ? `${row.name} (says generating, not in this project's ledger)` : row.name);
2660
+ owed.set(row.lane, held);
2661
+ }
2662
+ for (const lane of declaredLanes) {
2663
+ if (lanesRun.has(lane) || owed.has(lane) || lane === "skybox") continue;
2664
+ if (!rows.some((r) => r.lane === lane)) owed.set(lane, ["declared as this subsystem's lane"]);
2665
+ }
2666
+ for (const [lane, names] of owed) {
2667
+ const shown = names.slice(0, 3).join(", ") + (names.length > 3 ? `, +${names.length - 3} more` : "");
2668
+ warnings.push(
2669
+ `DESIGN.md plans generated ${lane} work (${shown}) and this project has never run a ${lane} generation \u2014 the plan says so and the generation ledger has nothing. Generate it (${LANE_COMMAND[lane] ?? `npx genex ${lane}`} "<prompt>" --no-wait), or change the row to say what happens instead and why.`
2670
+ );
2671
+ }
2672
+ const paidRows = rows.filter((r) => !r.procedural);
2673
+ const codeRows = rows.filter((r) => r.procedural);
2674
+ if (paidRows.length === 0 && codeRows.length >= 1 && ledger.length === 0 && !GENERATION_NONE.test(design)) {
2675
+ warnings.push(
2676
+ `DESIGN.md's asset plan is all code-built rows and this project has never run a generation. What the player sees must read as the thing it is (build contract, law 22): a coloured box or a capsule is not a finished building, person, or prop. Generate what the request names and what the player looks at up close (npx genex model / texture / music \u2026 --no-wait), or record "Generation: none \u2014 <why code alone reaches the bar here>" beside the table.`
2677
+ );
2678
+ }
2679
+ return warnings;
2680
+ }
2681
+
2682
+ // src/lib/deploy.ts
2469
2683
  import crypto3 from "crypto";
2470
- import fs14 from "fs/promises";
2684
+ import fs15 from "fs/promises";
2471
2685
  import os8 from "os";
2472
- import path13 from "path";
2686
+ import path14 from "path";
2473
2687
 
2474
2688
  // ../../packages/mobile-scan/src/image-dims.ts
2475
2689
  function u32be(b, o) {
@@ -2733,12 +2947,12 @@ function tierFor(estVramMb) {
2733
2947
  }
2734
2948
 
2735
2949
  // src/commands/ui.ts
2736
- import fs13 from "fs/promises";
2737
- import path12 from "path";
2950
+ import fs14 from "fs/promises";
2951
+ import path13 from "path";
2738
2952
  import { PNG as PNG2 } from "pngjs";
2739
2953
 
2740
2954
  // src/lib/png-tools.ts
2741
- import fs12 from "fs/promises";
2955
+ import fs13 from "fs/promises";
2742
2956
  import { PNG } from "pngjs";
2743
2957
  var ALPHA_TRANSPARENT_MAX = 16;
2744
2958
  var isHttpUrl = (s) => /^https?:\/\//i.test(s);
@@ -2749,12 +2963,12 @@ async function loadPng(input) {
2749
2963
  if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
2750
2964
  buf = Buffer.from(await res.arrayBuffer());
2751
2965
  } else {
2752
- buf = await fs12.readFile(input);
2966
+ buf = await fs13.readFile(input);
2753
2967
  }
2754
2968
  return PNG.sync.read(buf);
2755
2969
  }
2756
2970
  async function writePng(file, png) {
2757
- await fs12.writeFile(file, PNG.sync.write(png));
2971
+ await fs13.writeFile(file, PNG.sync.write(png));
2758
2972
  }
2759
2973
  function cropPng(image, box) {
2760
2974
  const out = new PNG({ width: box.w, height: box.h });
@@ -3054,7 +3268,7 @@ async function uiExtract(opts, log) {
3054
3268
  const dilatePx = opts.dilate ?? 0;
3055
3269
  const sheet = await loadPng(input);
3056
3270
  const { width: W, height: H, data } = sheet;
3057
- await fs13.mkdir(outDir, { recursive: true });
3271
+ await fs14.mkdir(outDir, { recursive: true });
3058
3272
  log.plain(c.bold("genex ui extract"));
3059
3273
  log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
3060
3274
  let hasTransparency = false;
@@ -3283,7 +3497,7 @@ async function uiExtract(opts, log) {
3283
3497
  rimPixels: speckle.sampled
3284
3498
  });
3285
3499
  }
3286
- const outPath = path12.join(outDir, `${name}.png`);
3500
+ const outPath = path13.join(outDir, `${name}.png`);
3287
3501
  await writePng(outPath, out);
3288
3502
  const sidecar = {
3289
3503
  name,
@@ -3302,7 +3516,7 @@ async function uiExtract(opts, log) {
3302
3516
  defringed
3303
3517
  };
3304
3518
  const { name: _n, out: _o, ...sidecarBody } = sidecar;
3305
- await fs13.writeFile(
3519
+ await fs14.writeFile(
3306
3520
  outPath.replace(/\.png$/i, "") + ".bbox.json",
3307
3521
  JSON.stringify(sidecarBody, null, 2)
3308
3522
  );
@@ -3311,8 +3525,8 @@ async function uiExtract(opts, log) {
3311
3525
  `${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
3312
3526
  );
3313
3527
  }
3314
- const debugPath = path12.join(outDir, "extract-debug.json");
3315
- await fs13.writeFile(
3528
+ const debugPath = path13.join(outDir, "extract-debug.json");
3529
+ await fs14.writeFile(
3316
3530
  debugPath,
3317
3531
  JSON.stringify(
3318
3532
  {
@@ -3774,7 +3988,7 @@ async function uiMasks(opts, log) {
3774
3988
  const registrationTolerance = opts.registrationTolerance ?? 0.04;
3775
3989
  const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
3776
3990
  const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
3777
- await fs13.mkdir(outDir, { recursive: true });
3991
+ await fs14.mkdir(outDir, { recursive: true });
3778
3992
  log.plain(c.bold("genex ui masks"));
3779
3993
  log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
3780
3994
  const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
@@ -3827,11 +4041,11 @@ async function uiMasks(opts, log) {
3827
4041
  });
3828
4042
  }
3829
4043
  const overlay = makeOverlay(clean2, converted.png);
3830
- const framePath = path12.join(outDir, `${pair.name}-frame.png`);
3831
- const maskPath = path12.join(outDir, `${pair.name}-mask.png`);
3832
- const annotatedPath = path12.join(outDir, `${pair.name}-annotated-source.png`);
3833
- const overlayPath = path12.join(outDir, `${pair.name}-overlay.png`);
3834
- const metaPath = path12.join(outDir, `${pair.name}.annotated-progress.json`);
4044
+ const framePath = path13.join(outDir, `${pair.name}-frame.png`);
4045
+ const maskPath = path13.join(outDir, `${pair.name}-mask.png`);
4046
+ const annotatedPath = path13.join(outDir, `${pair.name}-annotated-source.png`);
4047
+ const overlayPath = path13.join(outDir, `${pair.name}-overlay.png`);
4048
+ const metaPath = path13.join(outDir, `${pair.name}.annotated-progress.json`);
3835
4049
  await writePng(framePath, clean2);
3836
4050
  await writePng(maskPath, converted.png);
3837
4051
  await writePng(annotatedPath, annotated);
@@ -3878,7 +4092,7 @@ async function uiMasks(opts, log) {
3878
4092
  },
3879
4093
  overlay: overlayPath
3880
4094
  };
3881
- await fs13.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
4095
+ await fs14.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
3882
4096
  `);
3883
4097
  results.push(meta);
3884
4098
  const fb = converted.bbox;
@@ -3894,8 +4108,8 @@ async function uiMasks(opts, log) {
3894
4108
  );
3895
4109
  }
3896
4110
  }
3897
- const indexPath = path12.join(outDir, "annotated-progress.json");
3898
- await fs13.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
4111
+ const indexPath = path13.join(outDir, "annotated-progress.json");
4112
+ await fs14.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
3899
4113
  `);
3900
4114
  log.plain("");
3901
4115
  log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
@@ -4021,7 +4235,7 @@ async function uiTextColor(opts, log) {
4021
4235
  };
4022
4236
  process.stdout.write(`${JSON.stringify(result, null, 2)}
4023
4237
  `);
4024
- if (opts.out) await fs13.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
4238
+ if (opts.out) await fs14.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
4025
4239
  `);
4026
4240
  if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
4027
4241
  }
@@ -4053,7 +4267,7 @@ async function uiTrim(opts, log) {
4053
4267
  const sidecar = computeBBoxes(trimmed);
4054
4268
  const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
4055
4269
  const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
4056
- await fs13.writeFile(
4270
+ await fs14.writeFile(
4057
4271
  sidecarPath,
4058
4272
  JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
4059
4273
  );
@@ -4160,7 +4374,7 @@ async function uiPlate(opts, log) {
4160
4374
  fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
4161
4375
  }
4162
4376
  await writePng(outPath, out);
4163
- const name = path12.basename(outPath);
4377
+ const name = path13.basename(outPath);
4164
4378
  log.plain(c.bold("genex ui plate"));
4165
4379
  log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
4166
4380
  log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
@@ -4325,13 +4539,13 @@ async function walkFiles(dir) {
4325
4539
  const out = [];
4326
4540
  let entries;
4327
4541
  try {
4328
- entries = await fs13.readdir(dir, { withFileTypes: true });
4542
+ entries = await fs14.readdir(dir, { withFileTypes: true });
4329
4543
  } catch {
4330
4544
  return out;
4331
4545
  }
4332
4546
  for (const entry of entries) {
4333
4547
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
4334
- const p = path12.join(dir, entry.name);
4548
+ const p = path13.join(dir, entry.name);
4335
4549
  if (entry.isDirectory()) out.push(...await walkFiles(p));
4336
4550
  else out.push(p);
4337
4551
  }
@@ -4340,7 +4554,7 @@ async function walkFiles(dir) {
4340
4554
  async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4341
4555
  const viewportFindings = [];
4342
4556
  try {
4343
- const indexHtml = await fs13.readFile(path12.join(cwd, "index.html"), "utf8");
4557
+ const indexHtml = await fs14.readFile(path13.join(cwd, "index.html"), "utf8");
4344
4558
  if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
4345
4559
  viewportFindings.push({
4346
4560
  kind: "viewport-meta",
@@ -4354,28 +4568,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4354
4568
  }
4355
4569
  } catch {
4356
4570
  }
4357
- const absAssets = path12.resolve(cwd, assetDir);
4571
+ const absAssets = path13.resolve(cwd, assetDir);
4358
4572
  try {
4359
- if (!(await fs13.stat(absAssets)).isDirectory()) {
4573
+ if (!(await fs14.stat(absAssets)).isDirectory()) {
4360
4574
  return viewportFindings.length > 0 ? viewportFindings : null;
4361
4575
  }
4362
4576
  } catch {
4363
4577
  return viewportFindings.length > 0 ? viewportFindings : null;
4364
4578
  }
4365
- const srcFiles = (await walkFiles(path12.resolve(cwd, srcDir))).filter(
4366
- (p) => AUDIT_SRC_EXTS.has(path12.extname(p).toLowerCase())
4579
+ const srcFiles = (await walkFiles(path13.resolve(cwd, srcDir))).filter(
4580
+ (p) => AUDIT_SRC_EXTS.has(path13.extname(p).toLowerCase())
4367
4581
  );
4368
4582
  try {
4369
- for (const name of await fs13.readdir(cwd)) {
4370
- const ext = path12.extname(name).toLowerCase();
4371
- if (ext === ".html" || ext === ".css") srcFiles.push(path12.join(cwd, name));
4583
+ for (const name of await fs14.readdir(cwd)) {
4584
+ const ext = path13.extname(name).toLowerCase();
4585
+ if (ext === ".html" || ext === ".css") srcFiles.push(path13.join(cwd, name));
4372
4586
  }
4373
4587
  } catch {
4374
4588
  }
4375
4589
  const sources = [];
4376
4590
  for (const p of srcFiles) {
4377
4591
  try {
4378
- sources.push({ rel: path12.relative(cwd, p), text: await fs13.readFile(p, "utf8") });
4592
+ sources.push({ rel: path13.relative(cwd, p), text: await fs14.readFile(p, "utf8") });
4379
4593
  } catch {
4380
4594
  }
4381
4595
  }
@@ -4385,11 +4599,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4385
4599
  const metaByName = /* @__PURE__ */ new Map();
4386
4600
  const bboxByPng = /* @__PURE__ */ new Map();
4387
4601
  for (const p of assetFiles) {
4388
- const base = path12.basename(p);
4602
+ const base = path13.basename(p);
4389
4603
  const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
4390
4604
  if (metaMatch) {
4391
4605
  try {
4392
- const meta = JSON.parse(await fs13.readFile(p, "utf8"));
4606
+ const meta = JSON.parse(await fs14.readFile(p, "utf8"));
4393
4607
  metaByName.set(metaMatch[1], {
4394
4608
  cleanCrop: meta.clean?.crop ?? null,
4395
4609
  loosened: meta.loosened === true
@@ -4400,7 +4614,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4400
4614
  }
4401
4615
  if (base.endsWith(".bbox.json")) {
4402
4616
  try {
4403
- const sidecar = JSON.parse(await fs13.readFile(p, "utf8"));
4617
+ const sidecar = JSON.parse(await fs14.readFile(p, "utf8"));
4404
4618
  if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
4405
4619
  } catch {
4406
4620
  }
@@ -4409,7 +4623,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4409
4623
  for (const [name, meta] of metaByName) {
4410
4624
  if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
4411
4625
  for (const p of assetFiles) {
4412
- const base = path12.basename(p);
4626
+ const base = path13.basename(p);
4413
4627
  if (!base.toLowerCase().endsWith(".png")) continue;
4414
4628
  if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
4415
4629
  if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
@@ -4433,7 +4647,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4433
4647
  }
4434
4648
  const pngByBase = /* @__PURE__ */ new Map();
4435
4649
  for (const p of assetFiles) {
4436
- const base = path12.basename(p);
4650
+ const base = path13.basename(p);
4437
4651
  if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
4438
4652
  }
4439
4653
  for (const [maskBase, maskPath] of pngByBase) {
@@ -4446,8 +4660,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4446
4660
  let frame;
4447
4661
  let mask;
4448
4662
  try {
4449
- frame = PNG2.sync.read(await fs13.readFile(framePath));
4450
- mask = PNG2.sync.read(await fs13.readFile(maskPath));
4663
+ frame = PNG2.sync.read(await fs14.readFile(framePath));
4664
+ mask = PNG2.sync.read(await fs14.readFile(maskPath));
4451
4665
  } catch {
4452
4666
  continue;
4453
4667
  }
@@ -4466,7 +4680,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4466
4680
  if (!referenced(base)) continue;
4467
4681
  let png;
4468
4682
  try {
4469
- png = PNG2.sync.read(await fs13.readFile(p));
4683
+ png = PNG2.sync.read(await fs14.readFile(p));
4470
4684
  } catch {
4471
4685
  continue;
4472
4686
  }
@@ -4486,7 +4700,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4486
4700
  }
4487
4701
  const maskReported = /* @__PURE__ */ new Set();
4488
4702
  for (const p of assetFiles) {
4489
- const m = /^(.+)\.annotated-progress\.json$/.exec(path12.basename(p));
4703
+ const m = /^(.+)\.annotated-progress\.json$/.exec(path13.basename(p));
4490
4704
  if (!m) continue;
4491
4705
  const maskBase = `${m[1]}-mask.png`;
4492
4706
  if (!referenced(maskBase)) {
@@ -4498,14 +4712,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4498
4712
  }
4499
4713
  }
4500
4714
  for (const p of assetFiles) {
4501
- const base = path12.basename(p);
4715
+ const base = path13.basename(p);
4502
4716
  if (!base.toLowerCase().endsWith(".png")) continue;
4503
4717
  if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
4504
4718
  if (maskReported.has(base)) continue;
4505
4719
  if (!referenced(base)) {
4506
4720
  findings.push({
4507
4721
  kind: "unwired-sprite",
4508
- message: `${path12.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
4722
+ message: `${path13.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
4509
4723
  });
4510
4724
  }
4511
4725
  }
@@ -4692,7 +4906,7 @@ async function printUiAuditPreflight(log) {
4692
4906
  }
4693
4907
  async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4694
4908
  try {
4695
- const design = await fs14.readFile(path13.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
4909
+ const design = await fs15.readFile(path14.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
4696
4910
  const warnings = [];
4697
4911
  if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
4698
4912
  warnings.push(
@@ -4700,13 +4914,14 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4700
4914
  );
4701
4915
  }
4702
4916
  if (!/player character:/i.test(design)) {
4703
- const hasCharacter = await fs14.access(path13.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
4917
+ const hasCharacter = await fs15.access(path14.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
4704
4918
  if (!hasCharacter && await loadsPlayerBody(cwd)) {
4705
4919
  warnings.push(
4706
4920
  `Player character is the stock avatar \u2014 no generated character is wired. The game's own generated character is the player's body wherever a human body appears on screen, first-person included (genex-ai-character). Generate it, or record "Player character: VRM \u2014 <reason>" in DESIGN.md (no human body in this game / out of credits / player declined).`
4707
4921
  );
4708
4922
  }
4709
4923
  }
4924
+ warnings.push(...auditGenerationPlan({ design, ledger: await readLedger(cwd) }));
4710
4925
  for (const w of warnings) log.warn(w);
4711
4926
  } catch {
4712
4927
  }
@@ -4714,12 +4929,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4714
4929
  async function loadsPlayerBody(cwd) {
4715
4930
  const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
4716
4931
  try {
4717
- const entries = await fs14.readdir(path13.join(cwd, "src"), { recursive: true });
4932
+ const entries = await fs15.readdir(path14.join(cwd, "src"), { recursive: true });
4718
4933
  for (const rel of entries) {
4719
4934
  if (rel.includes("node_modules")) continue;
4720
- if (rel.split(path13.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
4935
+ if (rel.split(path14.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
4721
4936
  if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
4722
- const text = await fs14.readFile(path13.join(cwd, "src", rel), "utf8").catch(() => "");
4937
+ const text = await fs15.readFile(path14.join(cwd, "src", rel), "utf8").catch(() => "");
4723
4938
  if (BODY_LOADERS.test(text)) return true;
4724
4939
  }
4725
4940
  } catch {
@@ -4756,9 +4971,9 @@ async function deployGame(ctx, opts, log) {
4756
4971
  }
4757
4972
  log.success("Built.");
4758
4973
  }
4759
- const distDir = path13.join(cwd, "dist");
4974
+ const distDir = path14.join(cwd, "dist");
4760
4975
  const siteDir = await isDir(distDir) ? distDir : cwd;
4761
- const rel = path13.relative(cwd, siteDir) || ".";
4976
+ const rel = path14.relative(cwd, siteDir) || ".";
4762
4977
  if (siteDir === cwd) await writeGitignore(cwd, log);
4763
4978
  const files = await collectFiles(siteDir);
4764
4979
  if (files.length === 0) {
@@ -4847,7 +5062,7 @@ async function deployGame(ctx, opts, log) {
4847
5062
  }
4848
5063
  async function hasBuildScript(cwd) {
4849
5064
  try {
4850
- const pkg = JSON.parse(await fs14.readFile(path13.join(cwd, "package.json"), "utf8"));
5065
+ const pkg = JSON.parse(await fs15.readFile(path14.join(cwd, "package.json"), "utf8"));
4851
5066
  return Boolean(pkg.scripts?.build);
4852
5067
  } catch {
4853
5068
  return false;
@@ -4856,12 +5071,12 @@ async function hasBuildScript(cwd) {
4856
5071
  async function collectFiles(root) {
4857
5072
  const out = [];
4858
5073
  const walk2 = async (dir, prefix) => {
4859
- for (const e of await fs14.readdir(dir, { withFileTypes: true })) {
5074
+ for (const e of await fs15.readdir(dir, { withFileTypes: true })) {
4860
5075
  const relPath = prefix ? `${prefix}/${e.name}` : e.name;
4861
5076
  if (e.isDirectory()) {
4862
- if (!EXCLUDE_DIRS.has(e.name)) await walk2(path13.join(dir, e.name), relPath);
5077
+ if (!EXCLUDE_DIRS.has(e.name)) await walk2(path14.join(dir, e.name), relPath);
4863
5078
  } else if (e.isFile() && !isSecretEnvFile(e.name)) {
4864
- out.push({ relPath, bytes: await fs14.readFile(path13.join(dir, e.name)) });
5079
+ out.push({ relPath, bytes: await fs15.readFile(path14.join(dir, e.name)) });
4865
5080
  }
4866
5081
  }
4867
5082
  };
@@ -5103,7 +5318,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
5103
5318
  log.error("Couldn't save your game's source \u2014 please try again.");
5104
5319
  return false;
5105
5320
  };
5106
- const gitDir = await fs14.mkdtemp(path13.join(os8.tmpdir(), "genex-source-"));
5321
+ const gitDir = await fs15.mkdtemp(path14.join(os8.tmpdir(), "genex-source-"));
5107
5322
  const base = { GIT_DIR: gitDir };
5108
5323
  if (urlHasEmbeddedCredentials(pushUrl)) {
5109
5324
  base.GIT_CONFIG_COUNT = "1";
@@ -5118,12 +5333,12 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
5118
5333
  };
5119
5334
  try {
5120
5335
  if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
5121
- await fs14.writeFile(
5122
- path13.join(gitDir, "info", "exclude"),
5336
+ await fs15.writeFile(
5337
+ path14.join(gitDir, "info", "exclude"),
5123
5338
  // .env* are secrets — never publish them; `!` keeps the non-secret template.
5124
5339
  ["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
5125
5340
  );
5126
- const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path13.join(gitDir, "index-source") };
5341
+ const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path14.join(gitDir, "index-source") };
5127
5342
  let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
5128
5343
  if (!lfs) {
5129
5344
  log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
@@ -5175,7 +5390,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
5175
5390
  } catch {
5176
5391
  return failed();
5177
5392
  } finally {
5178
- await fs14.rm(gitDir, { recursive: true, force: true }).catch(() => {
5393
+ await fs15.rm(gitDir, { recursive: true, force: true }).catch(() => {
5179
5394
  });
5180
5395
  }
5181
5396
  }
@@ -5227,7 +5442,7 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
5227
5442
  }
5228
5443
  async function isDir(p) {
5229
5444
  try {
5230
- return (await fs14.stat(p)).isDirectory();
5445
+ return (await fs15.stat(p)).isDirectory();
5231
5446
  } catch {
5232
5447
  return false;
5233
5448
  }
@@ -5711,81 +5926,6 @@ async function promoteBuild(apiUrl, projectId, token, log) {
5711
5926
  // src/lib/detect-features.ts
5712
5927
  import fs16 from "fs/promises";
5713
5928
  import path15 from "path";
5714
-
5715
- // src/lib/generation-ledger.ts
5716
- import fs15 from "fs/promises";
5717
- import path14 from "path";
5718
- var ledgerPath = (cwd) => path14.join(cwd, ".genex", "generations.ndjson");
5719
- async function append(cwd, event) {
5720
- try {
5721
- await fs15.access(path14.join(cwd, ".genex"));
5722
- await fs15.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
5723
- `, "utf8");
5724
- } catch {
5725
- }
5726
- }
5727
- async function readLedger(cwd = process.cwd()) {
5728
- let raw;
5729
- try {
5730
- raw = await fs15.readFile(ledgerPath(cwd), "utf8");
5731
- } catch {
5732
- return [];
5733
- }
5734
- const entries = /* @__PURE__ */ new Map();
5735
- for (const line of raw.split("\n")) {
5736
- if (!line.trim()) continue;
5737
- try {
5738
- const e = JSON.parse(line);
5739
- if (e.t === "q" && typeof e.id === "string" && typeof e.kind === "string") {
5740
- if (!entries.has(e.id)) {
5741
- entries.set(e.id, {
5742
- id: e.id,
5743
- kind: e.kind,
5744
- prompt: typeof e.prompt === "string" ? e.prompt : "",
5745
- ...typeof e.tail === "string" ? { promptTail: e.tail } : {},
5746
- queuedAt: typeof e.at === "string" ? e.at : "",
5747
- status: "queued"
5748
- });
5749
- }
5750
- } else if (e.t === "done" && typeof e.id === "string") {
5751
- const entry = entries.get(e.id);
5752
- if (entry && (e.status === "completed" || e.status === "failed")) {
5753
- entry.status = e.status;
5754
- if (Array.isArray(e.urls)) entry.urls = e.urls.filter((u) => typeof u === "string");
5755
- }
5756
- }
5757
- } catch {
5758
- }
5759
- }
5760
- return [...entries.values()];
5761
- }
5762
- async function recordQueued(id, kind, prompt, cwd = process.cwd()) {
5763
- await append(cwd, {
5764
- t: "q",
5765
- id,
5766
- kind,
5767
- prompt: prompt.slice(0, 120),
5768
- ...prompt.length > 120 ? { tail: prompt.slice(-120) } : {},
5769
- at: (/* @__PURE__ */ new Date()).toISOString()
5770
- });
5771
- }
5772
- async function recordTerminal(id, status, urls, cwd = process.cwd()) {
5773
- const existing = (await readLedger(cwd)).find((e) => e.id === id);
5774
- if (existing && existing.status === status) return;
5775
- await append(cwd, { t: "done", id, status, ...urls?.length ? { urls } : {} });
5776
- }
5777
- async function countFailed(kind, cwd = process.cwd()) {
5778
- return (await readLedger(cwd)).filter((e) => e.kind === kind && e.status === "failed").length;
5779
- }
5780
- async function countOutcomes(kind, cwd = process.cwd()) {
5781
- const rows = (await readLedger(cwd)).filter((e) => e.kind === kind);
5782
- return {
5783
- failed: rows.filter((e) => e.status === "failed").length,
5784
- completed: rows.filter((e) => e.status === "completed").length
5785
- };
5786
- }
5787
-
5788
- // src/lib/detect-features.ts
5789
5929
  async function detectEmbedSdkVersion(cwd = process.cwd()) {
5790
5930
  try {
5791
5931
  const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
@@ -19391,7 +19531,7 @@ var EXPECT_DIR = {
19391
19531
  bl: [D, -D],
19392
19532
  br: [-D, -D]
19393
19533
  };
19394
- function classify(stemRaw) {
19534
+ function classify2(stemRaw) {
19395
19535
  const stem = stemRaw.replace(/-s\d+$/, "");
19396
19536
  if (stem.includes("jump") || stem.includes("ollie") || stem.includes("trick")) {
19397
19537
  return { kind: "oneshot", expect: null };
@@ -19782,7 +19922,7 @@ function score(kind, m, gates = DEFAULT_GATES) {
19782
19922
  }
19783
19923
  function analyzeTake(stem, buf, gates = DEFAULT_GATES) {
19784
19924
  const t = loadTake(buf);
19785
- const { kind, expect } = classify(stem);
19925
+ const { kind, expect } = classify2(stem);
19786
19926
  const F = t.F;
19787
19927
  const lo = kind !== "oneshot" ? Math.min(RAMP, Math.max(4, Math.floor(F / 4))) : 8;
19788
19928
  const w = [lo, F - Math.min(TAIL, Math.max(2, Math.floor(F / 8)))];
@@ -20047,7 +20187,7 @@ function compileSet(inputs, setName, cfg = DEFAULT_MOTION_CONFIG) {
20047
20187
  const gaits = {};
20048
20188
  const aimTakes = [];
20049
20189
  for (const { stem, take } of inputs) {
20050
- const { kind } = classify(stem);
20190
+ const { kind } = classify2(stem);
20051
20191
  const aim = /aim|rifle|pistol/.test(stem);
20052
20192
  if (aim && (kind === "idle" || kind === "freeform")) aimTakes.push({ stem, take });
20053
20193
  if (kind === "loco") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "1.13.1-dev.558",
3
+ "version": "1.13.2-dev.559",
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": {
@@ -9,13 +9,17 @@ Turn a text prompt into a real, game-ready **GLB** and drop it into the project.
9
9
 
10
10
  ## When to use this vs. procedural geometry
11
11
 
12
- - **Use `npx genex model`** for a specific, recognizable object a barrel, a chair, a
13
- sword, a spaceship, an animal. You get a real textured mesh.
14
- - **Use `$genex-threejs-procedural-assets`** when code is the more efficient
15
- engine: structures and buildings, modular kits, and anything placed many
16
- times with variation (editable, seeded, no GLB file). Mixing both in one
17
- scene is the normal way to build a detailed world — generated hero pieces
18
- over procedural dressing.
12
+ - **Use `npx genex model`** for a specific, recognizable object the player will
13
+ look at — a barrel, a chair, a sword, a spaceship, an animal, a named
14
+ building, the thing the request asked for. You get a real textured mesh, and
15
+ this is the default route whenever code would not honestly make the object
16
+ read as itself.
17
+ - **Use `$genex-threejs-procedural-assets`** for what is structural, repeated,
18
+ distant, or parametric — modular kits, fences, walls, paving, terrain,
19
+ anything placed many times with variation (editable, seeded, no GLB file) —
20
+ and for a close-up object only when the code result meets the same bar and
21
+ you have checked it in a capture. Mixing both in one scene is the normal way
22
+ to build a detailed world.
19
23
 
20
24
  **The output is a STATIC, unrigged mesh by default — no skeleton, no animation
21
25
  clips.** A "wolf" or "guard" from this command can be posed and moved as one
@@ -1,14 +1,19 @@
1
1
  ---
2
2
  name: genex-game-director
3
- description: The lightweight Genex request router. Read the latest game, tool, component, or focused-change request; ask only genuinely unresolved build forks; keep DESIGN.md current; and load only the Genex skills that the requested work actually needs. Use first for new work, resumed work, or whenever ownership is unclear.
3
+ description: The Genex request router. Read the latest game, tool, component, or focused-change request; ask only genuinely unresolved build forks; keep DESIGN.md current; and load each owning lane BEFORE the first file of the subsystem it owns — only the lanes the work touches, never the whole pack, and never fewer than the work needs. Use first for new work, resumed work, or whenever ownership is unclear.
4
4
  ---
5
5
 
6
6
  # Genex Game Director
7
7
 
8
8
  The player chooses what to build and in what order. You route that request;
9
- the owning Genex skills control how their lanes run. Load only skills that
10
- change the requested result—never the whole pack by default, and never
11
- commission unrelated work because an old workflow listed it.
9
+ the owning Genex skills control how their lanes run.
10
+
11
+ Routing binds in BOTH directions: load only the lanes the requested work
12
+ touches — never the whole pack by default, and never unrelated work because an
13
+ old workflow listed it — and load a lane BEFORE the first file of the subsystem
14
+ it owns. A matching row in the routing map is not a suggestion: each lane
15
+ exists because writing that subsystem without it ships a known defect.
16
+ Loading one lane never commissions another.
12
17
 
13
18
  **After any context compaction or session resume**, re-read the project's
14
19
  `AGENTS.md`, `DESIGN.md` when present, and the skill for the work you are
@@ -103,32 +108,56 @@ Never invent a provider generation ID for local code. Sections for gameplay,
103
108
  content, UI, world, assets, multiplayer, character/animation, tools, or
104
109
  modules are conditional on the request and existing project.
105
110
 
106
- ## 4. Route to the owning lane
107
-
108
- Use the [routing map](references/routing-map.md) for exact ownership. The main
109
- Genex asset and runtime lanes are:
110
-
111
- - `npx genex model "<prompt>"`—a generated GLB (also `--image` from a
112
- reference picture, `model segment` into named parts, `model rig` +
113
- `model animate` for non-biped bodies) `$genex-ai-model`
114
- - `npx genex texture "<prompt>"`—a generated surface → `$genex-ai-texture`
115
- - sky, horizon, time of day, weather mood—built in code, never generated; no
116
- command and no skill owns it, so pick what this game needs
117
- - `npx genex sfx "<prompt>"` / `music` / `voice`
118
- `$genex-ai-sfx`, `$genex-ai-music`, `$genex-ai-voice`
119
- - `npx genex image "<prompt>"` / `video`—2D and motion art
120
- `$genex-ai-image`, `$genex-ai-video`
121
- - UI, HUD, and menu requests `$genex-threejs-game-ui`,
122
- `$genex-ai-hud`, `$genex-ai-menu`
123
- - `npx genex character`, `creature`, and their motion commands
124
- `$genex-ai-character`, `$genex-threejs-creatures`
125
- - movement and vehicles `$genex-threejs-character-controller`,
126
- `$genex-threejs-vehicle-controllers`
127
- - phone input and phone-survivable rendering
128
- `$genex-threejs-touch-controls`, `$genex-threejs-adaptive-quality`
129
- - structures, buildings, modular kits, repeated or varied environment pieces,
130
- and parameterized objectscode is often the more efficient engine for
131
- these `$genex-threejs-procedural-assets`
111
+ ## 4. Routing map — load the owning lane before the first file
112
+
113
+ For every subsystem the current milestone touches, find its row below, load
114
+ that skill, and write the lane beside the work in `DESIGN.md` — a `Lane` cell
115
+ on the module's row in the Modules table, or a `Lane:` line under `Now:` in
116
+ focused or step-by-step work. `Lane: none <why>` is the only way to write a
117
+ subsystem with no lane. Then write the code. Never reconstruct a lane's API or
118
+ vendored code from memory of another engine.
119
+
120
+ | Work needed | Load |
121
+ | --- | --- |
122
+ | shot composition, chase/side/orbit rigs, camera handoffs, projection ownership, pointer look, mouse-aimed action, mouse-look, the screen-direction contract for hand-rolled steering/pan/look input signs, floating origins | `$genex-threejs-camera-direction` |
123
+ | on-foot player movement: walk/run/jump/crouch, third-person character, slopes, stairs, moving platforms, the player's body loader, directional locomotion, transitions, action motion | `$genex-threejs-character-controller` |
124
+ | **attacking, casting, aiming or reloading WHILE moving** — any action the legs must keep running under; a weapon carry stance over stock locomotion; a wind-up the character holds while walking | `$genex-threejs-character-controller` (`references/animations.md`, upper-body layering) |
125
+ | dash, dodge, roll, blink, backstep, a lunging attack — any burst that moves the character itself | `$genex-threejs-character-controller` (`references/tuning-and-presets.md`, dash recipe) |
126
+ | the game's own generated character—the player's body wherever a human body appears—or Meshy animation coverage beyond the stock pack: reference-informed A-pose concepts, exact action IDs, same-rig adapter | `$genex-ai-character` + `$genex-threejs-character-controller` |
127
+ | a character/enemy needs motion the catalog lacks—a signature move, boss telegraph, death, full 8-way set, or the player's footage; free plan before spend | `$genex-ai-character` motion section + `references/motion-generation.md` |
128
+ | remote player bodies in multiplayer—never hand-built primitives: the game's generated character when it has one, otherwise the player's `p.avatarUrl` VRM | `$genex-threejs-multiplayer` + `$genex-threejs-character-controller` |
129
+ | enemies, NPCs, or creatures: rigged bipeds via `npx genex creature`; non-biped body plans (quadruped, flier, serpent, aquatic, multi-leg) via `npx genex model rig` + `model animate`; static plus procedural motion only for shapes with no body plan; collider, facing, hit reaction, death | `$genex-threejs-creatures` |
130
+ | cars, drones, vehicle physics, gearbox, character↔vehicle enter/exit | `$genex-threejs-vehicle-controllers` |
131
+ | playable on phones: joystick, virtual buttons, drag zones, genre touch recipes, rotate-device overlay | `$genex-threejs-touch-controls` |
132
+ | phone-survivable rendering: device tiers, DPR/shadow/post budgets, runtime governor, generated-asset rungs, Quality picker, dispose-on-swap | `$genex-threejs-adaptive-quality` |
133
+ | anything falls, collides, gets pushed, or needs colliders/events | `$genex-threejs-physics-rapier` |
134
+ | launch/docking timelines, authored transform phases, springs, convergence, deterministic prop/debris motion | `$genex-threejs-procedural-animation` |
135
+ | rebuild a reference prop, hard-surface object, modular decoration, or structural environment piece (kits, fences, walls, paving) as editable parameterized Three.js code held to the asset bar at the end of this section | `$genex-threejs-procedural-assets` |
136
+ | stylized/abstract shader-authored materials, the procedural/PBR material boundary | `$genex-threejs-procedural-assets` |
137
+ | particles, trails, plasma, shockwaves, pooled bursts, and event effects | `$genex-threejs-procedural-vfx` |
138
+ | stable large-world shadows, cascades, clipmaps, cached updates | `$genex-threejs-shadow-systems` |
139
+ | eye adaptation, tone mapping, output color, LUT grading, and proven static grain | `$genex-threejs-exposure-color-grading` |
140
+ | fixed-view screenshots, input direction, facing, temporal and budget evidence | `$genex-threejs-visual-validation` |
141
+ | the default for a concrete object the player looks at up close: a generated GLB for a prop, vehicle, building, or object — from text or a reference image (`--image`); split into named parts (`model segment`); rig + animate any mesh (`model rig` / `model animate`) | `$genex-ai-model` |
142
+ | generated surface or terrain texture with real-world UV scale | `$genex-ai-texture` |
143
+ | sky, skybox, horizon, time of day, weather mood, night or space backdrop | build it in code in the scene—there is no sky command and no owning skill, so pick the technique this game needs |
144
+ | poster, sign, sprite, decal, reference sheet, or other 2D art | `$genex-ai-image` |
145
+ | in-world motion art or another requested video | `$genex-ai-video` |
146
+ | sound effect, one looping music bed, or a short spoken line | `$genex-ai-sfx`, `$genex-ai-music`, or `$genex-ai-voice` |
147
+ | requested UI/HUD/menu/interface work, a visible UI problem, or an interface you decided this game wants built with generated art | `$genex-threejs-game-ui` |
148
+ | selling anything for platform coin: a shop, an item catalog, boosts, cosmetics, "make it earn"; also any request for a loot box, gacha, wager, casino mechanic or donation prompt, which that skill refuses and replaces | `$genex-monetization` |
149
+ | cinematic menu/title/pause/victory/defeat/lobby/credits video treatment | `$genex-ai-menu` |
150
+ | drawn HUD chrome the game's style wants—one element or a matched set of frames, masks, and icons | `$genex-ai-hud` |
151
+ | the game works but feels flat, floaty, or unresponsive: input response, camera, impacts, cooldowns, difficulty, fail/retry | `$genex-threejs-game-feel` |
152
+ | realtime multiplayer: movement sync, shared ball/NPC, host-run scores/enemies, shots/emotes, persistence | `$genex-threejs-multiplayer` |
153
+ | players talking to each other by TEXT — chat, room chat, "let them type to each other" (`room.chat` + `npx genex controller chat`) | `$genex-threejs-multiplayer` |
154
+ | players TALKING — voice chat, mic, "hear each other", proximity/positional voice (`room.voice` + `npx genex controller voice`; party-sized, max 6 per room — say that number out loud before building the feature) | `$genex-threejs-multiplayer` |
155
+ | player identity, sign-in, guests, saves/progress, per-player state, shared persistent world, leaderboards—mandatory for every game | `$genex-threejs-embed-auth` |
156
+ | the player EXPLICITLY asks to make an asset or publish something from their game as an asset for the free library | `$genex-asset-author` (`genex asset new`) — never offer this unprompted; no game duty routes here |
157
+
158
+ Request-shape rules, working modes and the protected-lane handoffs stay in the
159
+ [routing map](references/routing-map.md); read it when the request shape, not
160
+ the subsystem, is what is unclear.
132
161
 
133
162
  For “make this image 3D,” ask **one** route question only when both results
134
163
  honestly fit: a generated GLB built from the picture itself
@@ -145,12 +174,23 @@ supplied and creating one is part of the requested work, use the existing
145
174
  Assets row.
146
175
 
147
176
  The request sets the floor for the asset plan, never the ceiling: a world
148
- should feel dressed and alive, so plan the detail its genre implies — and pick
149
- the more efficient engine per object. Paid generation buys hero pieces (the
150
- player's character, key props, music); procedural code is the
151
- unlimited detail engine for structures, buildings, modular kits, and repeated
152
- or varied dressing (`$genex-threejs-procedural-assets` owns that lane) and
153
- for the sky, which is never generated at all. Mixing
177
+ should feel dressed and alive, so plan the detail its genre implies — and
178
+ choose the engine per object, never per category. The bar is the same for both
179
+ engines: whatever the player looks at must read as the thing it is, in this
180
+ game's own style a building as that building, a person as a person, a prop as
181
+ that prop. A coloured box, a capsule or a flat grey block is not a finished
182
+ version of any of them. Generation is the default route to that bar wherever
183
+ code will not honestly reach it: the player's character, anything the request
184
+ names, anything the player walks up to, enters, or interacts with, and the
185
+ music and sound under it. Procedural code (`$genex-threejs-procedural-assets`)
186
+ is a first-class engine for what is structural, repeated, distant, or
187
+ parametric — terrain, sky (never generated), fences, paving, modular kits,
188
+ filler — and for anything else only when the result meets the same bar and you
189
+ have checked it in a capture before its row says `landed`. Write the route on
190
+ each Assets row: the paid flow for generated pieces, the procedural flow with
191
+ its local path for code-built ones — the table is where the per-object decision
192
+ lives, and a table with no generated row records
193
+ `Generation: none — <why code alone reaches the bar here>` beside it. Mixing
154
194
  both in one scene is the normal way to build, never a fallback. In focused
155
195
  work, stay inside the touched scope. Run independent planned generations with
156
196
  `--no-wait`, scaffold while they land, and preserve their IDs, URLs, and
@@ -37,11 +37,15 @@ so it survives a long pause, context compaction, handoff, and remix.
37
37
  status flow `proposed → planned → generating (id) → landed (URL) → wired`.
38
38
  A landed asset is unfinished until wired or visibly cancelled. Preserve
39
39
  generation IDs, permanent URLs, local outputs, and wiring state across
40
- compaction.
40
+ compaction. Every row shows its route: generated rows carry the paid flow;
41
+ code-built rows carry the procedural flow and a local path. A table with no
42
+ generated row at all carries `Generation: none — <why code alone reaches the
43
+ bar here>` beside it (build contract, law 22).
41
44
  - **Code-built procedural assets use their own truthful status flow:**
42
45
  `proposed → planned → building (blockout | detail | material | runtime) →
43
46
  landed (<local TypeScript path>) → wired`. They never invent a provider
44
- generation ID. A generated reference image is a separate normal Assets row.
47
+ generation ID. `landed` on a code-built row means a capture at gameplay
48
+ distance showed it reads as the thing it is. A generated reference image is a separate normal Assets row.
45
49
  - Sections are request-driven. Gameplay, content, UI, assets, world,
46
50
  multiplayer, character/animation, tools, modules, and procedural assets
47
51
  appear only when the request or existing project uses them.
@@ -73,6 +77,7 @@ or explicitly re-scope it.>
73
77
  Working mode: <whole coordinated build | step by step | focused change>
74
78
  Now: ▶ <number/name of the current milestone or focused task>
75
79
  Done when: <one plain, observable condition for the current work>
80
+ Lane: <$genex-… owning the current subsystem | none — why no row matches>
76
81
  1. <milestone> — ✅ → previewed
77
82
  2. <milestone> — ▶ in progress (<main agent | sub-agent>)
78
83
  3. <milestone> — ⏸ paused (<reason>)
@@ -121,7 +126,10 @@ Menu video: <generation id/URL, or the still standing in while it renders>
121
126
  | <asset> | <model/image/texture/…> | planned | — |
122
127
  | <asset> | <kind> | generating (<id>) | — |
123
128
  | <asset> | <kind> | landed → <URL> | yes/no |
129
+ | <asset> | procedural-code | building (blockout) | — |
124
130
  Status flow: proposed → planned → generating (id) → landed (URL) → wired.
131
+ Code-built rows: proposed → planned → building (…) → landed (<local path>) → wired.
132
+ Generation: none — <why code alone reaches the bar in this game> (only when no row is generated)
125
133
 
126
134
  **HUD pipeline state (only while the sprites lane is running) — keep this
127
135
  current; it is how that pipeline survives a context compaction.**
@@ -152,9 +160,9 @@ Play-button rule: nothing connects before the click when a Play screen exists.
152
160
  Verification still open: <two-identity/netcode evidence>
153
161
 
154
162
  ## Modules (whole coordinated mode or another useful split only)
155
- | Module | Owns files | Built by | Done when |
156
- |---|---|---|---|
157
- | <request-derived stream> | <disjoint paths> | <parallel/serial + reason> | <observable result> |
163
+ | Module | Lane | Owns files | Built by | Done when |
164
+ |---|---|---|---|---|
165
+ | <request-derived stream> | <$genex-… or none — why> | <disjoint paths> | <parallel/serial + reason> | <observable result> |
158
166
 
159
167
  ## Verification evidence
160
168
  - <preview URL/date and what it proves>
@@ -34,49 +34,17 @@ copy demo architecture.
34
34
 
35
35
  ## Route by system
36
36
 
37
- | Work needed | Load |
38
- | --- | --- |
39
- | shot composition, chase/side/orbit rigs, camera handoffs, projection ownership, pointer look, mouse-aimed action, mouse-look, the screen-direction contract for hand-rolled steering/pan/look input signs, floating origins | `$genex-threejs-camera-direction` |
40
- | on-foot player movement: walk/run/jump/crouch, third-person character, slopes, stairs, moving platforms, the player's body loader, directional locomotion, transitions, action motion | `$genex-threejs-character-controller` |
41
- | **attacking, casting, aiming or reloading WHILE moving** — any action the legs must keep running under; a weapon carry stance over stock locomotion; a wind-up the character holds while walking | `$genex-threejs-character-controller` (`references/animations.md`, upper-body layering) |
42
- | dash, dodge, roll, blink, backstep, a lunging attack — any burst that moves the character itself | `$genex-threejs-character-controller` (`references/tuning-and-presets.md`, dash recipe) |
43
- | the game's own generated character—the player's body wherever a human body appears—or Meshy animation coverage beyond the stock pack: reference-informed A-pose concepts, exact action IDs, same-rig adapter | `$genex-ai-character` + `$genex-threejs-character-controller` |
44
- | a character/enemy needs motion the catalog lacks—a signature move, boss telegraph, death, full 8-way set, or the player's footage; free plan before spend | `$genex-ai-character` motion section + `references/motion-generation.md` |
45
- | remote player bodies in multiplayer—never hand-built primitives: the game's generated character when it has one, otherwise the player's `p.avatarUrl` VRM | `$genex-threejs-multiplayer` + `$genex-threejs-character-controller` |
46
- | enemies, NPCs, or creatures: rigged bipeds via `npx genex creature`; non-biped body plans (quadruped, flier, serpent, aquatic, multi-leg) via `npx genex model rig` + `model animate`; static plus procedural motion only for shapes with no body plan; collider, facing, hit reaction, death | `$genex-threejs-creatures` |
47
- | cars, drones, vehicle physics, gearbox, character↔vehicle enter/exit | `$genex-threejs-vehicle-controllers` |
48
- | playable on phones: joystick, virtual buttons, drag zones, genre touch recipes, rotate-device overlay | `$genex-threejs-touch-controls` |
49
- | phone-survivable rendering: device tiers, DPR/shadow/post budgets, runtime governor, generated-asset rungs, Quality picker, dispose-on-swap | `$genex-threejs-adaptive-quality` |
50
- | anything falls, collides, gets pushed, or needs colliders/events | `$genex-threejs-physics-rapier` |
51
- | launch/docking timelines, authored transform phases, springs, convergence, deterministic prop/debris motion | `$genex-threejs-procedural-animation` |
52
- | rebuild a reference prop, hard-surface object, modular decoration, or simple environment piece as editable parameterized Three.js code | `$genex-threejs-procedural-assets` |
53
- | stylized/abstract shader-authored materials, the procedural/PBR material boundary | `$genex-threejs-procedural-assets` |
54
- | particles, trails, plasma, shockwaves, pooled bursts, and event effects | `$genex-threejs-procedural-vfx` |
55
- | stable large-world shadows, cascades, clipmaps, cached updates | `$genex-threejs-shadow-systems` |
56
- | eye adaptation, tone mapping, output color, LUT grading, and proven static grain | `$genex-threejs-exposure-color-grading` |
57
- | fixed-view screenshots, input direction, facing, temporal and budget evidence | `$genex-threejs-visual-validation` |
58
- | a generated GLB for a concrete prop, vehicle, building, or object — from text or a reference image (`--image`); split into named parts (`model segment`); rig + animate any mesh (`model rig` / `model animate`) | `$genex-ai-model` |
59
- | generated surface or terrain texture with real-world UV scale | `$genex-ai-texture` |
60
- | sky, skybox, horizon, time of day, weather mood, night or space backdrop | build it in code in the scene—there is no sky command and no owning skill, so pick the technique this game needs |
61
- | poster, sign, sprite, decal, reference sheet, or other 2D art | `$genex-ai-image` |
62
- | in-world motion art or another requested video | `$genex-ai-video` |
63
- | sound effect, one looping music bed, or a short spoken line | `$genex-ai-sfx`, `$genex-ai-music`, or `$genex-ai-voice` |
64
- | requested UI/HUD/menu/interface work, a visible UI problem, or an interface you decided this game wants built with generated art | `$genex-threejs-game-ui` |
65
- | selling anything for platform coin: a shop, an item catalog, boosts, cosmetics, "make it earn"; also any request for a loot box, gacha, wager, casino mechanic or donation prompt, which that skill refuses and replaces | `$genex-monetization` |
66
- | cinematic menu/title/pause/victory/defeat/lobby/credits video treatment | `$genex-ai-menu` |
67
- | drawn HUD chrome the game's style wants—one element or a matched set of frames, masks, and icons | `$genex-ai-hud` |
68
- | the game works but feels flat, floaty, or unresponsive: input response, camera, impacts, cooldowns, difficulty, fail/retry | `$genex-threejs-game-feel` |
69
- | realtime multiplayer: movement sync, shared ball/NPC, host-run scores/enemies, shots/emotes, persistence | `$genex-threejs-multiplayer` |
70
- | players talking to each other by TEXT — chat, room chat, "let them type to each other" (`room.chat` + `npx genex controller chat`) | `$genex-threejs-multiplayer` |
71
- | players TALKING — voice chat, mic, "hear each other", proximity/positional voice (`room.voice` + `npx genex controller voice`; party-sized, max 6 per room — say that number out loud before building the feature) | `$genex-threejs-multiplayer` |
72
- | player identity, sign-in, guests, saves/progress, per-player state, shared persistent world, leaderboards—mandatory for every game | `$genex-threejs-embed-auth` |
73
- | the player EXPLICITLY asks to make an asset or publish something from their game as an asset for the free library | `$genex-asset-author` (`genex asset new`) — never offer this unprompted; no game duty routes here |
37
+ The ownership table lives in **SKILL.md §4** so it is in front of you the
38
+ moment the director loads, rather than behind a link you have to decide to
39
+ open. This file keeps the request-shape and working-mode rules; read it when
40
+ the shape of the request, not the owning subsystem, is what is unclear.
74
41
 
75
42
  ## Request-sized execution
76
43
 
77
44
  ### Focused work
78
45
 
79
- Load only the owning row or rows, preserve the current architecture, implement
46
+ Load the owning row or rows before the first file of each subsystem, and
47
+ none beyond the work — preserve the current architecture, implement
80
48
  the requested result, and run its relevant check. UI work routes directly to
81
49
  UI; movement routes directly to movement; publishing routes directly to the
82
50
  existing publish flow. Do not add a concept, unrelated asset batch, full-game
@@ -132,12 +100,12 @@ flow and state what remains.
132
100
 
133
101
  ## Procedural assets (code-built)
134
102
 
135
- Use `$genex-threejs-procedural-assets` whenever code is the more efficient
136
- engine for an objectstructures, buildings, modular kits, repeated or varied
137
- props, parameterized objects — or when the player asks for procedural,
138
- parametric, or code-built work. It is a first-class lane, not a fallback, and
139
- mixing it with generated GLBs in one scene is the normal way to build a
140
- detailed world. An image alone still doesn't decide the route (ask the one
103
+ Use `$genex-threejs-procedural-assets` whenever code will honestly make the
104
+ object read as the thing it is structural pieces, modular kits, repeated or
105
+ varied props, parameterized objects — or when the player asks for procedural,
106
+ parametric, or code-built work. It is a first-class lane, not a fallback, held
107
+ to the same bar as a generated GLB, and mixing it with generated GLBs in one
108
+ scene is the normal way to build a detailed world. An image alone still doesn't decide the route (ask the one
141
109
  route question when both honestly fit), and never use it for player bodies,
142
110
  rigged characters/creatures, or animation.
143
111
 
@@ -1,30 +1,34 @@
1
1
  ---
2
2
  name: genex-threejs-procedural-assets
3
- description: Build editable, parameterized Three.js objects in code — structures, buildings, modular kits, repeated or varied props whenever code is the more efficient engine for them, or the user asks for procedural/parametric work. A first-class lane alongside generated GLBs; mix both freely.
3
+ description: Build editable, parameterized Three.js objects in code — modular kits, structural pieces, repeated or varied props, and any object code will honestly make read as the thing it is or whenever the user asks for procedural/parametric work. A first-class lane alongside generated GLBs, held to the same bar; mix both freely.
4
4
  ---
5
5
 
6
6
  # Genex Three.js Procedural Assets
7
7
 
8
8
  Build a recognizable asset as local, editable Three.js code. This lane is a
9
- first-class engine, not a fallback: for structures, buildings, modular kits,
10
- and anything placed many times with variation, code is often more efficient
11
- than a generated GLB and mixing procedural pieces with generated hero
12
- pieces in the same scene is the normal way to build a detailed, lively world.
9
+ first-class engine, not a fallback and it is held to the same bar as a
10
+ generated GLB: the result must read as the thing it is when the player looks at
11
+ it, in the game's own style. A coloured box or a flat grey block is a blockout,
12
+ never a landed asset. Mixing procedural pieces with generated pieces in the
13
+ same scene is the normal way to build a detailed, lively world.
13
14
 
14
15
  ## Choose the route
15
16
 
16
17
  - Start directly when the user says procedural, parametric, code-built,
17
18
  customizable, seeded, or asks for controlled variations.
18
- - Also start directly when code is plainly the efficient route: buildings and
19
- structures, modular environment kits, fences, pipes, rails, and anything
20
- placed many times with variation.
19
+ - Also start directly for what is structural, repeated, distant, or
20
+ parametric: terrain, modular environment kits, fences, pipes, rails, walls,
21
+ paving, and anything placed many times with variation.
22
+ - For anything the player looks at up close, walks into, or interacts with — a
23
+ named building, a signature prop, a vehicle — code is a choice, not a
24
+ default: take it only when you will honestly reach the bar above, and confirm
25
+ it in a capture at gameplay distance before the row reads `landed`. Otherwise
26
+ `$genex-ai-model` owns it.
21
27
  - An attached or available image does not activate this skill by itself.
22
28
  - If “make this image 3D” could honestly mean either route, ask exactly one
23
29
  question: **“Do you want a generated textured GLB, or editable procedural
24
30
  Three.js code?”**
25
31
  - Use `$genex-ai-model` when the user chooses a generated textured GLB.
26
- - Use this skill for props, hard-surface objects, modular decorations, and
27
- simple structures or environment pieces.
28
32
  - Do not use it for characters, creatures, rigging, skeletal animation, or
29
33
  character likeness. Keep those in their existing specialist lanes.
30
34
 
@@ -60,6 +64,9 @@ proposed → planned → building (blockout | detail | material | runtime)
60
64
  → landed (<local TypeScript path>) → wired
61
65
  ```
62
66
 
67
+ `landed` means a capture at gameplay distance showed it reads as the thing it
68
+ is — not that the file exists.
69
+
63
70
  A Genex-generated reference keeps its separate image row and normal paid
64
71
  generation status. Note that the procedural-code row is derived from that
65
72
  image. A private user reference is not a paid generation row.