@genex-ai/cli-demo 1.34.0-dev.693 → 1.34.1-dev.694
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 +342 -253
- package/package.json +1 -1
- package/templates/skills/genex-ai-character/SKILL.md +6 -0
- package/templates/skills/genex-ai-music/SKILL.md +16 -19
- package/templates/skills/genex-ai-music/references/genex-audio-gesture.md +47 -0
- package/templates/skills/genex-ai-sfx/SKILL.md +14 -3
- package/templates/skills/genex-ai-voice/SKILL.md +15 -4
- package/templates/skills/genex-game-director/SKILL.md +9 -21
- package/templates/skills/genex-game-director/references/design-contract.md +7 -6
- package/templates/skills/genex-game-director/references/routing-map.md +7 -9
- package/templates/skills/genex-lane-card/SKILL.md +3 -3
- package/templates/skills/genex-threejs-exposure-color-grading/SKILL.md +10 -1
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +7 -1
- package/templates/skills/genex-threejs-visual-validation/references/genex-audio-output.md +43 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.34.
|
|
3
|
+
"version": "1.34.1-dev.694",
|
|
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": {
|
|
@@ -381,3 +381,9 @@ game with a generated character, every remote wears it — one
|
|
|
381
381
|
re-run from the id of the stage before it, never from the beginning.
|
|
382
382
|
- **The rig or a pose is bad** — reject and regenerate. Never repair limbs at
|
|
383
383
|
runtime; validation never rewrites limb animation.
|
|
384
|
+
|
|
385
|
+
## Choose the body by its role
|
|
386
|
+
|
|
387
|
+
Use this paid custom-character workflow for the player's body and characters the player examines or interacts with closely. For a background population, first reuse procedural bodies and motion or compatible existing rigged assets. Add distinct paid bodies only for a requirement reuse cannot satisfy, or an explicit user request. Catalog motion needs a compatible skeleton and may be paid.
|
|
388
|
+
|
|
389
|
+
Structural QA reports blocking findings separately from advisories. In the direct-text lane, the server already tries both Meshy rest poses and can fall back to Uthana; do not pay for another T-pose attempt merely because the combined diagnostic names both failures. Read the delivered rig provider and capabilities: Uthana fallback has no Meshy controller pack or catalog clips. Use its supported motion workflow. Joint-visibility framing improves the input specification but is not proof a generated rig will pass.
|
|
@@ -61,7 +61,7 @@ Genre + mood + instrumentation + tempo + loop wording:
|
|
|
61
61
|
|
|
62
62
|
Music is **non-positional** (`THREE.Audio`, not `PositionalAudio`), looped,
|
|
63
63
|
and QUIET — the default volume is **0.30**: it sits under the sfx, never over
|
|
64
|
-
them.
|
|
64
|
+
them. Copy the shared [gesture gate](references/genex-audio-gesture.md) into your audio module first. It resumes the context from a gesture independently of decoding:
|
|
65
65
|
|
|
66
66
|
```ts
|
|
67
67
|
import * as THREE from "three";
|
|
@@ -72,24 +72,21 @@ camera.add(listener); // ONE listener serves music and every sfx
|
|
|
72
72
|
// the URL `npx genex music` printed (R2 sends CORS headers, cross-origin works):
|
|
73
73
|
const MUSIC_URL = "https://assets.genex.technology/generations/<id>/audio-music";
|
|
74
74
|
const music = new THREE.Audio(listener);
|
|
75
|
+
let audioEnabled = true; // bind to playing && !muted; pause existing music when false
|
|
76
|
+
let loaded = false;
|
|
77
|
+
const audioGate = installAudioGestureGate(listener.context, () => {
|
|
78
|
+
if (loaded && !music.isPlaying) music.play();
|
|
79
|
+
}, () => audioEnabled);
|
|
75
80
|
new THREE.AudioLoader().loadAsync(MUSIC_URL).then((buffer) => {
|
|
76
81
|
music.setBuffer(buffer);
|
|
77
82
|
music.setLoop(true);
|
|
78
|
-
music.setVolume(0.30);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// Suspend when the tab hides (decode work + politeness), resume on return:
|
|
88
|
-
document.addEventListener("visibilitychange", () => {
|
|
89
|
-
const ctx = listener.context;
|
|
90
|
-
if (document.hidden) void ctx.suspend();
|
|
91
|
-
else void ctx.resume();
|
|
92
|
-
});
|
|
83
|
+
music.setVolume(0.30);
|
|
84
|
+
loaded = true;
|
|
85
|
+
audioGate.refresh(); // a gesture may already have arrived while decoding
|
|
86
|
+
}).catch(error => console.warn("Music failed to load", error));
|
|
87
|
+
// On pause/mute: set audioEnabled, pause music if needed, then audioGate.refresh().
|
|
88
|
+
// Dispose audioGate when this scene is destroyed.
|
|
89
|
+
|
|
93
90
|
```
|
|
94
91
|
|
|
95
92
|
Decode the track (and every sfx clip) while the **menu** is up, not on PLAY:
|
|
@@ -143,9 +140,9 @@ hard settings rule — wire the sliders in the same milestone as the track.
|
|
|
143
140
|
- **"Email not verified" (`email_verification_required`)** — generation credits
|
|
144
141
|
unlock after the account's email is verified. Give the user the verify link
|
|
145
142
|
the CLI printed, wait for them to confirm, then re-run the command.
|
|
146
|
-
- **No sound** —
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
- **No sound** — call `listener.context.resume()` directly in the intended
|
|
144
|
+
click/keydown, independently of decoding. Check that it reaches `running`,
|
|
145
|
+
then verify a triggered event after final gain and positional attenuation.
|
|
149
146
|
- **The loop "pops" every cycle** — the track has an intro/outro (the prompt
|
|
150
147
|
missed the loop wording). One regeneration with "seamless loop, consistent
|
|
151
148
|
energy, no intro, no outro" is fair; after that, crossfade tail-into-head
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Genex audio gesture gate
|
|
2
|
+
|
|
3
|
+
Copy this helper into the game's audio module once, shared by music, SFX and voice.
|
|
4
|
+
Install it before starting asynchronous decoding. `enabled` reads the current
|
|
5
|
+
playing/unmuted state; `ready` starts loaded continuous audio idempotently.
|
|
6
|
+
Call `refresh()` after decoding or a pause/mute state change. One-shots still
|
|
7
|
+
belong to their actual gameplay event, never replay a backlog after unlock.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
function installAudioGestureGate(context, ready, enabled = () => true) {
|
|
11
|
+
let authorized = false, disposed = false, resuming = false;
|
|
12
|
+
const refresh = () => {
|
|
13
|
+
if (!disposed && authorized && enabled() && !document.hidden && context.state === 'running') ready();
|
|
14
|
+
};
|
|
15
|
+
const gesture = () => {
|
|
16
|
+
if (disposed || !enabled() || document.hidden || resuming) return;
|
|
17
|
+
resuming = true;
|
|
18
|
+
// resume is invoked IN the gesture, before any await or asset decoding.
|
|
19
|
+
let resumed;
|
|
20
|
+
try { resumed = context.resume(); }
|
|
21
|
+
catch (error) { resuming = false; console.warn('Audio resume failed', error); return; }
|
|
22
|
+
Promise.resolve(resumed).then(() => { authorized = true; refresh(); })
|
|
23
|
+
.catch(error => console.warn('Audio resume failed', error))
|
|
24
|
+
.finally(() => { resuming = false; });
|
|
25
|
+
};
|
|
26
|
+
const visibility = () => {
|
|
27
|
+
if (document.hidden) void context.suspend().catch(error => console.warn('Audio suspend failed', error));
|
|
28
|
+
else if (authorized && enabled()) gesture();
|
|
29
|
+
};
|
|
30
|
+
window.addEventListener('pointerdown', gesture);
|
|
31
|
+
window.addEventListener('keydown', gesture);
|
|
32
|
+
document.addEventListener('visibilitychange', visibility);
|
|
33
|
+
return { refresh,
|
|
34
|
+
canPlay: () => authorized && enabled() && !document.hidden && context.state === 'running',
|
|
35
|
+
dispose() {
|
|
36
|
+
disposed = true;
|
|
37
|
+
window.removeEventListener('pointerdown', gesture);
|
|
38
|
+
window.removeEventListener('keydown', gesture);
|
|
39
|
+
document.removeEventListener('visibilitychange', visibility);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Mute/pause handlers stop or pause current playback themselves and then call
|
|
46
|
+
`refresh()`. The gate never changes gains or unmutes a game. If the context
|
|
47
|
+
cannot resume on visibility return, the next gesture can retry.
|
|
@@ -44,6 +44,10 @@ import * as THREE from "three";
|
|
|
44
44
|
|
|
45
45
|
const listener = new THREE.AudioListener();
|
|
46
46
|
camera.add(listener);
|
|
47
|
+
// Copy the shared gesture gate below; register BEFORE any asynchronous decode.
|
|
48
|
+
let audioEnabled = true; // Bind to the game's playing and unmuted state.
|
|
49
|
+
const audioGate = installAudioGestureGate(listener.context, () => {}, () => audioEnabled);
|
|
50
|
+
// audioEnabled is the game's current playing && !muted state.
|
|
47
51
|
|
|
48
52
|
// the URL `npx genex sfx` printed (R2 sends CORS headers, so cross-origin works):
|
|
49
53
|
const SFX_URL = "https://assets.genex.technology/generations/<id>/audio-sfx";
|
|
@@ -55,7 +59,7 @@ sound.setRefDistance(5);
|
|
|
55
59
|
laserMesh.add(sound); // follows the object
|
|
56
60
|
|
|
57
61
|
// on fire:
|
|
58
|
-
if (!sound.isPlaying) sound.play();
|
|
62
|
+
if (audioGate.canPlay() && !sound.isPlaying) sound.play();
|
|
59
63
|
```
|
|
60
64
|
|
|
61
65
|
**Global** (UI/non-spatial — same volume everywhere): use `new THREE.Audio(listener)`
|
|
@@ -186,8 +190,9 @@ To silence a loop, ride the volume to 0 and leave it running — never `stop()`/
|
|
|
186
190
|
- **"Email not verified" (`email_verification_required`)** — generation credits
|
|
187
191
|
unlock after the account's email is verified. Give the user the verify link the
|
|
188
192
|
CLI printed, wait for them to confirm, then re-run the command.
|
|
189
|
-
- **No sound** —
|
|
190
|
-
|
|
193
|
+
- **No sound** — call `listener.context.resume()` directly in the intended
|
|
194
|
+
click/keydown, independently of decoding. Check that it reaches `running`,
|
|
195
|
+
then verify a triggered event after final gain and positional attenuation.
|
|
191
196
|
- **Too quiet/loud** — `sound.setVolume(0..1)`; for positional, tune
|
|
192
197
|
`setRefDistance` / `setRolloffFactor`.
|
|
193
198
|
- **Stutters on a phone while moving, at every quality setting** — audio, not
|
|
@@ -195,3 +200,9 @@ To silence a loop, ride the volume to 0 and leave it running — never `stop()`/
|
|
|
195
200
|
the same triage from the performance side).
|
|
196
201
|
- **A sound is late the first time, then fine** — it was still decoding. Preload
|
|
197
202
|
during the menu.
|
|
203
|
+
|
|
204
|
+
## Gesture unlock and audible evidence
|
|
205
|
+
|
|
206
|
+
Copy the `$genex-ai-music` shared gesture gate (`references/genex-audio-gesture.md`) once into your audio module and share it with every lane. Define `audioEnabled` from playing/unmuted state before installing it. Register before decoding; `THREE.Audio.play()` does not resume a suspended context. Keep mute/pause ownership in the game. Do not queue stale one-shots for later unlock.
|
|
207
|
+
|
|
208
|
+
For **no sound**, first check a real gesture called `listener.context.resume()` and that it reached running. Then check decoded data, source start, gains and positional attenuation. Measure a deliberately triggered audible event using $genex-threejs-visual-validation; a running context or loaded file alone is insufficient.
|
|
@@ -81,7 +81,11 @@ Non-positional (`THREE.Audio`) for narrator/announcer voices; positional
|
|
|
81
81
|
import * as THREE from "three";
|
|
82
82
|
|
|
83
83
|
const listener = new THREE.AudioListener();
|
|
84
|
-
camera.add(listener);
|
|
84
|
+
camera.add(listener);
|
|
85
|
+
// Copy the shared gesture gate below; register BEFORE any asynchronous decode.
|
|
86
|
+
let audioEnabled = true; // Bind to the game's playing and unmuted state.
|
|
87
|
+
const audioGate = installAudioGestureGate(listener.context, () => {}, () => audioEnabled);
|
|
88
|
+
// audioEnabled is the game's current playing && !muted state. // ONE listener serves music, sfx, and voice
|
|
85
89
|
|
|
86
90
|
const VOICE_URL = "https://assets.genex.technology/generations/<id>/audio-voice";
|
|
87
91
|
const line = new THREE.Audio(listener); // PositionalAudio(listener) at the speaker's mesh for world speech
|
|
@@ -93,6 +97,7 @@ new THREE.AudioLoader().loadAsync(VOICE_URL).then((buffer) => {
|
|
|
93
97
|
});
|
|
94
98
|
|
|
95
99
|
function sayLine(text: string): void {
|
|
100
|
+
if (!line.buffer || !audioGate.canPlay()) return;
|
|
96
101
|
if (line.isPlaying) line.stop(); // a new line interrupts the old — never overlap voices
|
|
97
102
|
line.play();
|
|
98
103
|
subtitles.textContent = text; // SUBTITLE every voice line, always
|
|
@@ -148,6 +153,12 @@ function sayLine(text: string): void {
|
|
|
148
153
|
- **Wrong language/accent** — the model speaks the language of the TEXT; for
|
|
149
154
|
an accent, pick a different cast voice (or a specific `--voice-id`) rather
|
|
150
155
|
than describing the accent in the text.
|
|
151
|
-
- **No sound** —
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
- **No sound** — call `listener.context.resume()` directly in the intended
|
|
157
|
+
click/keydown, independently of decoding. Check that it reaches `running`,
|
|
158
|
+
then verify a triggered event after final gain and positional attenuation.
|
|
159
|
+
|
|
160
|
+
## Gesture unlock and audible evidence
|
|
161
|
+
|
|
162
|
+
Copy the `$genex-ai-music` shared gesture gate (`references/genex-audio-gesture.md`) once into your audio module and share it with every lane. Define `audioEnabled` from playing/unmuted state before installing it. Register before decoding; `THREE.Audio.play()` does not resume a suspended context. Keep mute/pause ownership in the game. Do not queue stale one-shots for later unlock.
|
|
163
|
+
|
|
164
|
+
For **no sound**, first check a real gesture called `listener.context.resume()` and that it reached running. Then check decoded data, source start, gains and positional attenuation. Measure a deliberately triggered audible event using $genex-threejs-visual-validation; a running context or loaded file alone is insufficient.
|
|
@@ -148,9 +148,9 @@ vendored code from memory of another engine.
|
|
|
148
148
|
| eye adaptation, tone mapping, output color, LUT grading, and proven static grain | `$genex-threejs-exposure-color-grading` |
|
|
149
149
|
| fixed-view screenshots, input direction, facing, temporal and budget evidence | `$genex-threejs-visual-validation` |
|
|
150
150
|
| **a model the player already has** — a `.glb` they exported, bought or made elsewhere: it is IMPORTED, never rebuilt. `npx genex model import <file.glb>` (free) for props and non-biped bodies (then `model rig`/`model animate`), `npx genex character import <file.glb>` for a humanoid that should walk (Uthana auto-rig, then `character animate --locomotion`) | `$genex-ai-model` · `$genex-ai-character` |
|
|
151
|
-
| 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`). A space routed to code above does NOT settle these:
|
|
151
|
+
| 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`). A space routed to code above does NOT settle these: the ground plan can be code while an object examined up close is generated | `$genex-ai-model` (first time this session: `$genex-lane-card`) |
|
|
152
152
|
| generated surface or terrain texture with real-world UV scale | `$genex-ai-texture` |
|
|
153
|
-
| sky, skybox, horizon, time of day, weather mood, night or space backdrop | build it in code
|
|
153
|
+
| sky, skybox, horizon, time of day, weather mood, night or space backdrop | build it in code; $genex-threejs-exposure-color-grading owns gameplay lighting and time-of-day readability, with atmosphere techniques chosen for the scene |
|
|
154
154
|
| poster, sign, sprite, decal, reference sheet, or other 2D art | `$genex-ai-image` |
|
|
155
155
|
| in-world motion art or another requested video | `$genex-ai-video` |
|
|
156
156
|
| sound effect, one looping music bed, or a short spoken line | `$genex-ai-sfx`, `$genex-ai-music`, or `$genex-ai-voice` |
|
|
@@ -185,25 +185,11 @@ supplied and creating one is part of the requested work, use the existing
|
|
|
185
185
|
`npx genex image` lane unchanged and record that paid reference as its own
|
|
186
186
|
Assets row.
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
that prop. A coloured box, a capsule or a flat grey block is not a finished
|
|
194
|
-
version of any of them. Generation is the default route to that bar wherever
|
|
195
|
-
code will not honestly reach it: the player's character, anything the request
|
|
196
|
-
names, anything the player walks up to, enters, or interacts with, and the
|
|
197
|
-
music and sound under it. Procedural code (`$genex-threejs-procedural-assets`)
|
|
198
|
-
is a first-class engine for what is structural, repeated, distant, or
|
|
199
|
-
parametric — terrain, sky (never generated), fences, paving, modular kits,
|
|
200
|
-
filler — and for anything else only when the result meets the same bar and you
|
|
201
|
-
have checked it in a capture before its row says `landed`. Write the route on
|
|
202
|
-
each Assets row: the paid flow for generated pieces, the procedural flow with
|
|
203
|
-
its local path for code-built ones — the table is where the per-object decision
|
|
204
|
-
lives, and a table with no generated row records
|
|
205
|
-
`Generation: none — <why code alone reaches the bar here>` beside it. Mixing
|
|
206
|
-
both in one scene is the normal way to build, never a fallback. In focused
|
|
188
|
+
Every asset must read as the thing it is from the gameplay camera. Decide per object which engine meets that bar; procedural authoring is a first-class engine.
|
|
189
|
+
|
|
190
|
+
Plan one reusable implementation per distinct required gameplay or visual role before adding variants. Each Assets row records **role**, **reuse** (instances or compatible existing assets), and **done when** (the visible or playable requirement it satisfies), beside its route and price. Background populations default to reusable procedural bodies and motion, or compatible existing rigged assets. Catalog clips require a compatible skeleton and may cost credits; they are not free animation for arbitrary procedural meshes. Reserve custom paid character workflows for the player body and characters examined or interacted with closely; explicit user requests still take precedence.
|
|
191
|
+
|
|
192
|
+
Add a variant only when reuse leaves a demonstrated requirement unmet. Unspent allowance is not unfinished work. Procedural pieces must meet the same visual bar as generated ones; a placeholder capsule or box is not a finished person or prop. Keep the existing Assets table, and record `Generation: none — <why code or reuse reaches the bar>` when no paid generation is needed. Mixing engines is normal. In focused
|
|
207
193
|
work, stay inside the touched scope. Generate in batches you can finish, and
|
|
208
194
|
generate what the wallet can carry: every build has an asset allowance, and
|
|
209
195
|
`npx genex budget` prints it live with the price of each kind. Each Assets row
|
|
@@ -433,3 +419,5 @@ than restarting design discovery.
|
|
|
433
419
|
requested acceptance and relevant owning-lane checks are true. Never report a
|
|
434
420
|
skill as applied because you read it, an asset as wired because it generated,
|
|
435
421
|
or the full requested outcome as complete while Open commitments remain.
|
|
422
|
+
|
|
423
|
+
For an allowance increase or any approval question, call `ask_user` with `timeoutPolicy: no-consent`. A recommended option is not permission. On timeout the trim stands: keep the existing allowance and continue independent work. Use `assume-default` only for reversible design choices, never spending or permissions.
|
|
@@ -121,12 +121,13 @@ HUD lane: sprites (<which elements the sprite lane is producing>)
|
|
|
121
121
|
Menu video: <generation id/URL, or the still standing in while it renders>
|
|
122
122
|
|
|
123
123
|
## Assets — the generation plan AND budget (only when used)
|
|
124
|
-
| Asset | Kind | Status | Wired? |
|
|
125
|
-
|
|
126
|
-
| <asset> | <model/image/texture/…> | planned | — |
|
|
127
|
-
| <asset> | <kind> | generating (<id>) | — |
|
|
128
|
-
| <asset> | <kind> | landed → <URL> | yes/no |
|
|
129
|
-
| <asset> | procedural-code | building (blockout) | — |
|
|
124
|
+
| Asset | Kind | Status | Wired? | Role | Reuse | Done when |
|
|
125
|
+
|---|---|---|---|---|---|---|
|
|
126
|
+
| <asset> | <model/image/texture/…> | planned | — | <required gameplay/visual role> | <instances or existing asset> | <observable requirement> |
|
|
127
|
+
| <asset> | <kind> | generating (<id>) | — | <role> | <reuse> | <requirement> |
|
|
128
|
+
| <asset> | <kind> | landed → <URL> | yes/no | <role> | <reuse> | <requirement> |
|
|
129
|
+
| <asset> | procedural-code | building (blockout) | — | <role> | <reuse> | <requirement> |
|
|
130
|
+
Start with one implementation per distinct required role. Add variants only for an unmet requirement; unspent allowance is not unfinished work. Trim to the allowance or ask before increasing it.
|
|
130
131
|
Status flow: proposed → planned → generating (id) → landed (URL) → wired.
|
|
131
132
|
Code-built rows: proposed → planned → building (…) → landed (<local path>) → wired.
|
|
132
133
|
Generation: none — <why code alone reaches the bar in this game> (only when no row is generated)
|
|
@@ -113,15 +113,13 @@ SPACE the player moves through — a level, an interior, a whole building — is
|
|
|
113
113
|
is off on the stand.
|
|
114
114
|
|
|
115
115
|
**That fallback claims the SPACE, never the things standing in it.** MEASURED
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
code with generated buildings and characters standing on it is the normal shape,
|
|
124
|
-
not a contradiction.
|
|
116
|
+
The space row claims the SPACE, never the things standing in it. Routing
|
|
117
|
+
terrain or a ground plan to code does not decide how close-interaction props,
|
|
118
|
+
characters or structures are made. Choose each by its gameplay/visual role,
|
|
119
|
+
reuse and completion requirement. Background populations can reuse procedural
|
|
120
|
+
bodies/motion or compatible existing rigs; custom paid character workflows
|
|
121
|
+
serve the player body and close interactions, subject to explicit requests.
|
|
122
|
+
Catalog animation requires a compatible skeleton and may be paid.
|
|
125
123
|
|
|
126
124
|
- Prefer a supplied reference. A private attachment stays local unless the
|
|
127
125
|
player explicitly permits upload, publication, or commit.
|
|
@@ -72,9 +72,7 @@ after that. `npx genex budget` prints it live: balance, spendable, the
|
|
|
72
72
|
allowance, spent this build, remaining, and the price of every kind. Run it
|
|
73
73
|
before the first batch and after each preview.
|
|
74
74
|
|
|
75
|
-
The allowance is
|
|
76
|
-
brief names — leaving it unspent while you code-build the named things is as
|
|
77
|
-
wrong as draining it on props. Plan the asset table against it: the named
|
|
75
|
+
The allowance is a ceiling, not a target. Unspent allowance is not unfinished work. Start with one reusable implementation per required role, and add variants only when reuse leaves a demonstrated requirement unmet. Plan the asset table against it: the named
|
|
78
76
|
things first, then what makes the scene read, on the cheapest lane that reaches
|
|
79
77
|
the bar. If the plan needs more than it holds, ask the player with the number
|
|
80
78
|
BEFORE trimming; only their word raises it (`npx genex budget --assets
|
|
@@ -97,3 +95,5 @@ A lane whose provider wallet is empty fails every call and refunds every one.
|
|
|
97
95
|
`npx genex doctor` prints each lane live/mock/paused with its credit state. If a
|
|
98
96
|
lane is red: **build that thing in code and move on** — do not re-run it, and do
|
|
99
97
|
not stall the game waiting for it. Say in chat what you fell back to.
|
|
98
|
+
|
|
99
|
+
Use `timeoutPolicy: no-consent` for allowance questions. Background populations reuse procedural bodies/motion or compatible existing rigs; catalog animation needs a compatible skeleton and can be paid. Custom character workflows serve the player body and close interactions, subject to explicit user direction.
|
|
@@ -71,8 +71,17 @@ c += g * uGrain * lw; // uGrain ~0.02–0.03, NOT 0.045
|
|
|
71
71
|
- **See it in the running game**, and specifically look for shimmer while nothing
|
|
72
72
|
moves (`$genex-threejs-visual-validation` has the two-frozen-frame check).
|
|
73
73
|
|
|
74
|
+
## Gameplay lighting and dark-phase readability
|
|
75
|
+
|
|
76
|
+
Own the scene's key/fill/environment balance across its playable time-of-day phases. Keep useful fill or environmental illumination when the key light fades; tune the final rendered image rather than prescribing a universal light intensity. Preserve the intended mood and avoid clipping the bright phase.
|
|
77
|
+
|
|
78
|
+
Capture the darkest intended **playable** phase at a representative view after reaching it with real controls or time progression. Split the full gameplay screenshot into 4×4 tiles. For each pixel use sRGB luma `(0.2126*r + 0.7152*g + 0.0722*b)/255`. For tile `(x,y)` in 0..3, its weight is `1 / (1 + (x-1.5)**2 + (y-1.5)**2)`: centre 0.666667, edge 0.285714, corner 0.181818. Divide the weighted sum of tile means by total weight. Require **>= 0.08**; retain the bright-phase upper band of 0.85 and inspect tonal range and contrast. This is screenshot-domain luma, not the HDR/linear luminance used for exposure adaptation.
|
|
79
|
+
|
|
80
|
+
Inspect the screenshot too: bright HUD text must not hide unreadable gameplay. The general prober's median exposure result is separate; it cannot prove the darkest phase. Without a confirmed darkest-phase capture, report unknown. Fold this into the milestone smoke pass.
|
|
81
|
+
|
|
74
82
|
## Failure conditions
|
|
75
83
|
|
|
84
|
+
- the confirmed darkest playable phase falls below 0.08 centre-weighted sRGB luma, or a bright HUD masks unreadable play;
|
|
76
85
|
- tone mapping occurs in both materials and post;
|
|
77
86
|
- exposure is used to repair physically inconsistent light ratios;
|
|
78
87
|
- meter weighting and scene framing are not inspected;
|
|
@@ -87,4 +96,4 @@ c += g * uGrain * lw; // uGrain ~0.02–0.03, NOT 0.045
|
|
|
87
96
|
|
|
88
97
|
Keep HDR glow, AO, atmosphere, and shared render-target ordering in the game's
|
|
89
98
|
existing post stack. This skill owns exposure, tone mapping, grading, and output
|
|
90
|
-
conversion
|
|
99
|
+
conversion, gameplay lighting and time-of-day readability.
|
|
@@ -153,7 +153,7 @@ everything twice.
|
|
|
153
153
|
7. **Ask the scene the three things the screenshot cannot answer** (below). Run it
|
|
154
154
|
once, in the same browser you already have open.
|
|
155
155
|
|
|
156
|
-
### The screenshot has
|
|
156
|
+
### The screenshot has four blind spots — query them
|
|
157
157
|
|
|
158
158
|
A capture of the whole arena is taken from one camera at one distance. Three real
|
|
159
159
|
defect classes are invisible in it and all shipped to players:
|
|
@@ -383,3 +383,9 @@ visual-system work — the sequence above.
|
|
|
383
383
|
This skill evaluates an implementation; it does not supply the implementation
|
|
384
384
|
mechanism. Load the subject or image-effect skill first, then use this protocol
|
|
385
385
|
to decide whether the result is acceptable.
|
|
386
|
+
|
|
387
|
+
## Audio and dark phases in the same smoke check
|
|
388
|
+
|
|
389
|
+
A still cannot prove audible output. Use the [development-only final-mix check](references/genex-audio-output.md) after a real gesture and trigger an intended audible event. Require observed peak RMS **> 0.002**, at least ten running-context samples, and mute off; missing evidence is unknown. Record context state, sample count, RMS, mute and event. Loaded audio and graph edges alone are not audibility.
|
|
390
|
+
|
|
391
|
+
For games with a dark playable phase, observe the darkest intended phase through the game's real controls or time progression and capture it, then follow $genex-threejs-exposure-color-grading. No dark-phase observation means unknown, never an assumed pass. Add these observations to the existing milestone smoke check rather than starting a repeated inspection loop.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Genex final-mix audio check
|
|
2
|
+
|
|
3
|
+
Use in development only. Pass the game's real final master gain (Three.js:
|
|
4
|
+
`listener.getInput()`), after per-source gain and positional attenuation.
|
|
5
|
+
Every intended source must feed that master. This observes the real mix; it
|
|
6
|
+
does not fix a silent source or unlock the browser. First enter gameplay with
|
|
7
|
+
a real gesture and leave mute off. `trigger` fires an actual audible game
|
|
8
|
+
event. Do not use a synthetic test tone as evidence for the game's audio.
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
async function measureFinalMix(context, master, { muted, event, trigger }) {
|
|
12
|
+
if (!event || typeof trigger !== 'function') return { result: 'unknown', reason: 'No intended audible event' };
|
|
13
|
+
const analyser = context.createAnalyser();
|
|
14
|
+
const silentSink = context.createGain();
|
|
15
|
+
analyser.fftSize = 2048;
|
|
16
|
+
silentSink.gain.value = 0;
|
|
17
|
+
master.connect(analyser);
|
|
18
|
+
analyser.connect(silentSink);
|
|
19
|
+
silentSink.connect(context.destination); // pull the tap without doubling the audible output
|
|
20
|
+
const samples = new Float32Array(analyser.fftSize);
|
|
21
|
+
let count = 0, peakRms = 0;
|
|
22
|
+
try {
|
|
23
|
+
trigger();
|
|
24
|
+
for (let i = 0; i < 40; i++) {
|
|
25
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
26
|
+
if (context.state !== 'running') continue;
|
|
27
|
+
analyser.getFloatTimeDomainData(samples);
|
|
28
|
+
let power = 0;
|
|
29
|
+
for (const value of samples) power += value * value;
|
|
30
|
+
peakRms = Math.max(peakRms, Math.sqrt(power / samples.length));
|
|
31
|
+
count++;
|
|
32
|
+
}
|
|
33
|
+
return { result: muted || count < 10 ? 'unknown' : peakRms > 0.002 ? 'pass' : 'fail',
|
|
34
|
+
contextState: context.state, samples: count, peakRms, muted, event, threshold: 0.002 };
|
|
35
|
+
} finally { master.disconnect(analyser); analyser.disconnect(); silentSink.disconnect(); }
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Run this during the existing milestone smoke check, with the intended event
|
|
40
|
+
and representative listening position recorded. Muted, insufficiently sampled
|
|
41
|
+
or untriggered checks are not passes. Quiet output with a running context
|
|
42
|
+
requires checking source starts, final gains and distance/rolloff. Do not turn
|
|
43
|
+
up a diagnostic-only signal to pass; verify the real event by listening too.
|