@genex-ai/cli-demo 0.71.0-dev.186 → 0.71.0
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 +8 -354
- package/package.json +1 -2
- package/templates/controllers/character/vrm/vrm-loader.ts +11 -74
- package/templates/skills/genex-ai-hud/SKILL.md +2 -8
- package/templates/skills/genex-ai-menu/SKILL.md +2 -14
- package/templates/skills/genex-ai-skybox/SKILL.md +4 -15
- package/templates/skills/genex-ai-texture/SKILL.md +1 -1
- package/templates/skills/genex-ai-video/SKILL.md +1 -1
- package/templates/skills/genex-explore/SKILL.md +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +2 -2
- package/templates/skills/genex-threejs-bloom/SKILL.md +1 -4
- package/templates/skills/genex-threejs-bloom/references/bloom.md +1 -1
- package/templates/skills/genex-threejs-camera-direction/SKILL.md +8 -48
- package/templates/skills/genex-threejs-camera-direction/references/camera-rigs.md +0 -62
- package/templates/skills/genex-threejs-embed-auth/SKILL.md +1 -4
- package/templates/skills/genex-threejs-game-feel/SKILL.md +1 -4
- package/templates/skills/genex-threejs-game-ui/SKILL.md +26 -90
- package/templates/skills/genex-threejs-game-ui/references/style-capsules.md +1 -2
- package/templates/skills/genex-threejs-image-pipeline/SKILL.md +0 -5
- package/templates/skills/genex-threejs-image-pipeline/references/image-pipeline.md +1 -1
- package/templates/skills/genex-threejs-lighting-design/SKILL.md +1 -5
- package/templates/skills/genex-threejs-multiplayer/SKILL.md +1 -7
- package/templates/skills/genex-threejs-multiplayer/references/host-physics.md +3 -6
- package/templates/skills/genex-threejs-physics-rapier/references/colliders-from-assets.md +0 -1
- package/templates/skills/genex-threejs-screen-space-ambient-occlusion/references/ambient-occlusion.md +1 -1
- package/templates/skills/genex-threejs-shadow-systems/SKILL.md +0 -6
- package/templates/skills/genex-threejs-shadow-systems/references/shadow-systems.md +1 -1
- package/templates/skills/genex-threejs-skill-router/SKILL.md +5 -26
- package/templates/skills/genex-threejs-skill-router/references/routing-map.md +9 -25
- package/templates/skills/genex-threejs-spectral-ocean/references/spectral-ocean.md +1 -1
- package/templates/skills/genex-threejs-touch-controls/SKILL.md +0 -11
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +12 -29
- package/templates/skills/genex-threejs-water-optics/references/water-optics.md +1 -1
- package/templates/skills/genex-updates/SKILL.md +1 -1
- package/templates/controllers/quality/governor.ts +0 -147
- package/templates/controllers/quality/pick-asset.ts +0 -57
- package/templates/controllers/quality/tier.ts +0 -170
- package/templates/skills/genex-threejs-adaptive-quality/SKILL.md +0 -141
- package/templates/skills/genex-threejs-adaptive-quality/references/adaptive-quality.md +0 -105
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use this reference for stable directional shadows across a large procedural scene using committed light-space centers, texel snapping, bounded refresh budgets, cross-level blending, and targeted invalidation.
|
|
4
4
|
|
|
5
|
-
> **Renderer note:** this reference assumes `WebGPURenderer` + TSL node materials. Check the project's actual renderer first — the Genex scaffold ships vanilla WebGL three.js. On WebGL, adapt the technique with standard materials / `EffectComposer` passes or pick a simpler alternative; never switch renderers mid-project.
|
|
5
|
+
> **Renderer note:** this reference assumes `WebGPURenderer` + TSL node materials. Check the project's actual renderer first — the Genex scaffold ships vanilla WebGL three.js. On WebGL, adapt the technique with standard materials / `EffectComposer` passes or pick a simpler alternative; never switch renderers mid-project.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -15,12 +15,11 @@ map, execution order, and acceptance gate.
|
|
|
15
15
|
|
|
16
16
|
| Work needed | Load |
|
|
17
17
|
| --- | --- |
|
|
18
|
-
| shot composition, chase/side/orbit rigs, camera handoffs, projection ownership, pointer look, mouse-aimed action (shooter, FPS/first-person, sniper, turret, crosshair/reticle), mouse-look,
|
|
18
|
+
| shot composition, chase/side/orbit rigs, camera handoffs, projection ownership, pointer look, mouse-aimed action (shooter, FPS/first-person, sniper, turret, crosshair/reticle), mouse-look, floating origins | `$genex-threejs-camera-direction` |
|
|
19
19
|
| on-foot player movement: walk/run/jump/crouch, third-person character, slopes, stairs, moving platforms, personal VRM animation, directional locomotion, transitions, action motion | `$genex-threejs-character-controller` |
|
|
20
20
|
| a custom generated playable humanoid or Meshy animation coverage beyond UAL: reference-informed A-pose concepts, explicit candidate + 10k-remesh approvals, exact action IDs, same-rig adapter | `$genex-ai-character` + `$genex-threejs-character-controller` |
|
|
21
21
|
| the player drives or flies something: cars, drones, vehicle physics, gearbox, enter/exit between character and vehicle | `$genex-threejs-vehicle-controllers` |
|
|
22
22
|
| playable on phones: touch/mobile input for any game — joystick, virtual buttons, drag zones, per-genre touch recipes, rotate-device overlay — wired by default for every NEW game when a recipe fits (skip with a one-line reason) | `$genex-threejs-touch-controls` |
|
|
23
|
-
| phone-survivable rendering — device tiers, DPR/shadow/post budgets, the runtime quality governor, per-tier asset rungs for generated skyboxes/textures, the Quality picker, dispose-on-swap discipline — **mandatory for every game at boot wiring** | `$genex-threejs-adaptive-quality` |
|
|
24
23
|
| anything falls, collides, gets pushed, or needs physics: Rapier world setup, colliders for meshes and GLBs, collision events | `$genex-threejs-physics-rapier` |
|
|
25
24
|
| launch and docking timelines, procedural transform phases, springs, staging, rotating-frame alignment, debris motion | `$genex-threejs-procedural-animation` |
|
|
26
25
|
| reusable scalar/vector fields, domain warping, causal masks, procedural normals | `$genex-threejs-procedural-fields` |
|
|
@@ -59,14 +58,6 @@ map, execution order, and acceptance gate.
|
|
|
59
58
|
mentions sign-in, saves, progress, per-player state, a persistent world, or
|
|
60
59
|
leaderboards. Multiplayer auth (`getColyseusAuth`) comes from it too.
|
|
61
60
|
|
|
62
|
-
**Adaptive quality is mandatory routing:** every game wires the device tier at
|
|
63
|
-
boot — `$genex-threejs-adaptive-quality` (three lines: `detectTier()` before the
|
|
64
|
-
renderer, tier-capped `setPixelRatio`, the governor in the loop; generated
|
|
65
|
-
skyboxes/textures load through their rungs). Phones enforce a hard GPU-memory
|
|
66
|
-
kill desktops never show you; the tier is what keeps a phone boot alive while
|
|
67
|
-
desktop keeps the full look. This is a completion gate like the post stack —
|
|
68
|
-
cheap to wire, not a new testing burden (desktop-only verification stands).
|
|
69
|
-
|
|
70
61
|
**Character-animation routing:** use the existing VRM + UAL character
|
|
71
62
|
controller by default. Use `npx genex character` when the game needs a custom
|
|
72
63
|
generated humanoid or an action unavailable in UAL. Before generating a Meshy
|
|
@@ -131,11 +122,7 @@ stack this game ships — read off the concept frame's OWN look (the grade, bloo
|
|
|
131
122
|
level, haze, grain it already shows) and which post the 2–3 AAA references lean
|
|
132
123
|
on, not a default single bloom; ONE built render-pass effect is the FLOOR
|
|
133
124
|
against no-post for EVERY game, never the target — ship the richness the concept
|
|
134
|
-
implies
|
|
135
|
-
tiers it is satisfied by the built tone-mapping/output pass with the light
|
|
136
|
-
additions the tier allows (FXAA/vignette), while the full named stack remains
|
|
137
|
-
the desktop floor — never ship the heavy stack undropped to phones (the UI
|
|
138
|
-
vignette div or a CSS canvas filter does not count on any tier;
|
|
125
|
+
implies (the UI vignette div or a CSS canvas filter does not count;
|
|
139
126
|
`$genex-threejs-image-pipeline` owns ordering when 2+ compose), a decision for
|
|
140
127
|
**every primitive surface** the game builds — walls, barriers, kerbs and
|
|
141
128
|
platforms each get a real texture or a **shader** where that surface wants motion
|
|
@@ -217,16 +204,8 @@ concept-driven — a richer first build beats a grey-box one.
|
|
|
217
204
|
drag-orbit (`pointerLockAim: false`) only with a stated reason (e.g. a
|
|
218
205
|
cursor-heavy UI core). **Never** — cursor-core games (click-to-move, tower
|
|
219
206
|
defense, builder, card/puzzle), orbit showcases, touch-only; these MUST pass
|
|
220
|
-
`pointerLockAim: false`.
|
|
221
|
-
|
|
222
|
-
and the full aim contract live in `$genex-threejs-camera-direction`.
|
|
223
|
-
- Input direction (always — every game that moves with keys or pointer):
|
|
224
|
-
D/ArrowRight must move or turn the player screen-RIGHT, mouse-right must turn
|
|
225
|
-
the view right, drag-pan axes share one convention. The screen-direction
|
|
226
|
-
contract and verified copy-paste bases live in
|
|
227
|
-
`$genex-threejs-camera-direction` — hand-rolled steering/pan/look math copies
|
|
228
|
-
one instead of deriving signs, and the smoke check's input-direction pass
|
|
229
|
-
verifies it.
|
|
207
|
+
`pointerLockAim: false`. The mechanism and the full aim contract live in
|
|
208
|
+
`$genex-threejs-camera-direction`.
|
|
230
209
|
- Art direction follows THIS game's concept. The style examples inside skills
|
|
231
210
|
are examples, not defaults — never default to neon/cyberpunk/synthwave (or any
|
|
232
211
|
other single register) unless the concept calls for it.
|
|
@@ -243,7 +222,7 @@ concept-driven — a richer first build beats a grey-box one.
|
|
|
243
222
|
- Use `$genex-threejs-visual-validation` before declaring graphics/procedural-system
|
|
244
223
|
work done. **Game fast path:** for game tasks that loaded no procedural/visual-system
|
|
245
224
|
skill, done = a screenshot plus an interaction smoke check (load the page, press each
|
|
246
|
-
control, see the visible response
|
|
225
|
+
control, see the visible response) — don't run the full diagnostic gate — PLUS the
|
|
247
226
|
floors from the routing-map's acceptance gate: the generated sprite HUD wired in
|
|
248
227
|
(not the CSS placeholder), pause on Escape, the branded loader with its key-art
|
|
249
228
|
background, the brief's fonts actually loaded, the renderer baseline + at least one
|
|
@@ -52,10 +52,8 @@ Three.js release or branch, and do not blindly copy demo architecture.
|
|
|
52
52
|
while you build the game. FIRST of all art: the gate's **concept frame** —
|
|
53
53
|
a playable-moment shot (verb + threat + objective in frame, per the
|
|
54
54
|
game-ui gate), shown to the user for a keep-or-change answer the moment
|
|
55
|
-
it lands
|
|
56
|
-
|
|
57
|
-
when notes arrive; later `--edit`-able generations anchor to it for STYLE
|
|
58
|
-
while the game contract owns content. Then the `$genex-ai-hud` Stage-1 mockup, enqueued
|
|
55
|
+
it lands; later `--edit`-able generations anchor to it for STYLE while
|
|
56
|
+
the game contract owns content. Then the `$genex-ai-hud` Stage-1 mockup, enqueued
|
|
59
57
|
here for EVERY game; and the `$genex-ai-menu` video whenever the menu
|
|
60
58
|
decision is yes (the default for every game — "it's only a draft" is not a
|
|
61
59
|
reason to decide no). Skipping this enqueue is
|
|
@@ -182,10 +180,7 @@ Three.js release or branch, and do not blindly copy demo architecture.
|
|
|
182
180
|
(`$genex-threejs-visual-validation` owns the capture discipline).
|
|
183
181
|
7. Add camera direction when framing, controls, transitions, or scale perception
|
|
184
182
|
affect play — or the game aims with the mouse (shooter/FPS/turret): the
|
|
185
|
-
step-4 bucket decision executes here.
|
|
186
|
-
steering/pan/look math: the screen-direction contract (D → screen-right,
|
|
187
|
-
mouse-right → view right, drag axes one convention) with verified copy-paste
|
|
188
|
-
bases lives there — signs are copied, never derived.
|
|
183
|
+
step-4 bucket decision executes here.
|
|
189
184
|
8. Add procedural animation when object motion needs authored phases,
|
|
190
185
|
convergence, looping, or deterministic timelines.
|
|
191
186
|
9. Add shared fields before writing multiple independent noise layers.
|
|
@@ -196,11 +191,7 @@ Three.js release or branch, and do not blindly copy demo architecture.
|
|
|
196
191
|
step-4 post plan, not inventing one now. This is a completion gate: the
|
|
197
192
|
named post stack must be BUILT before the game is called done, published,
|
|
198
193
|
or handed off — a game rendering on stock three.js defaults is not done,
|
|
199
|
-
and the gate does not wait for the word "done" to be said.
|
|
200
|
-
tier-gated (`$genex-threejs-adaptive-quality`): full on desktop, the built
|
|
201
|
-
tone-mapping pass + the tier's light additions on phones — wire the
|
|
202
|
-
governor's post toggle so phone tiers drop the heavy passes, never ship
|
|
203
|
-
them undropped.
|
|
194
|
+
and the gate does not wait for the word "done" to be said.
|
|
204
195
|
12. Once the loop is playable, build the planned interface states via
|
|
205
196
|
`$genex-threejs-game-ui` (HUD, pause on Escape, fail/retry, win, and the
|
|
206
197
|
full loading state grown from the step-6 loader), then `npx genex wait`
|
|
@@ -259,7 +250,7 @@ module — a corrupted entry point costs more than the minutes saved.
|
|
|
259
250
|
|
|
260
251
|
**Game fast path:** for a game task that loaded no procedural/visual-system skill,
|
|
261
252
|
done = a screenshot plus an interaction smoke check (load the page, press each
|
|
262
|
-
control, assert a visible response
|
|
253
|
+
control, assert a visible response — `$genex-threejs-visual-validation` has the
|
|
263
254
|
procedure), **plus the UI floor from `$genex-threejs-game-ui` (the generated
|
|
264
255
|
sprite HUD wired in — not the CSS placeholder — pause on Escape, the branded
|
|
265
256
|
loader with its key-art background, the brief's font pair actually loaded)
|
|
@@ -286,11 +277,7 @@ A routed Genex scene is incomplete until it exposes:
|
|
|
286
277
|
- named perceptual parameters for the important visual systems;
|
|
287
278
|
- debug views for generated fields, masks, or passes;
|
|
288
279
|
- a no-post baseline that still communicates the subject;
|
|
289
|
-
- a clear quality tier or render-budget knob when the effect is expensive
|
|
290
|
-
and for every GAME, the adaptive-quality tier wired at boot
|
|
291
|
-
(`$genex-threejs-adaptive-quality`): tier-capped pixel ratio, tier shadow
|
|
292
|
-
budget, the runtime governor in the loop, and generated skybox/texture
|
|
293
|
-
loads routed through their rungs;
|
|
280
|
+
- a clear quality tier or render-budget knob when the effect is expensive;
|
|
294
281
|
- when physics or controllers are in play, a fixed-timestep loop: per-frame
|
|
295
282
|
work (platforms, enter/exit, controller updates) runs inside the physics
|
|
296
283
|
world's before-step hook, then the world steps — never in the render loop;
|
|
@@ -300,12 +287,9 @@ A routed Genex scene is incomplete until it exposes:
|
|
|
300
287
|
|
|
301
288
|
**Publishing IS calling it done.** Before `npx genex publish`, every completion
|
|
302
289
|
gate above must pass — sprite HUD wired, Escape pause working, branded loader
|
|
303
|
-
with its key art, fonts loaded, renderer baseline + one built post effect
|
|
304
|
-
|
|
305
|
-
or
|
|
306
|
-
explicitly re-scoped by the user. `genex preview`/`publish` print a mobile
|
|
307
|
-
preflight (estimated phone GPU memory vs budget) — treat a warning there as a
|
|
308
|
-
gate item too. If any is still
|
|
290
|
+
with its key art, fonts loaded, renderer baseline + one built post effect,
|
|
291
|
+
world dressing placed or validly waived, and the content contract's
|
|
292
|
+
countables present or explicitly re-scoped by the user. If any is still
|
|
309
293
|
pending, say which and publish only after an explicit go-ahead.
|
|
310
294
|
|
|
311
295
|
Do not invent unavailable Genex service APIs. When preparing a game for Genex
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use this reference for a large, unbounded-looking ocean whose identity comes from directional spectral synthesis, staged inverse FFTs, derivative maps, Jacobian whitecaps, and coherent optical shading.
|
|
4
4
|
|
|
5
|
-
> **Renderer note:** this reference assumes `WebGPURenderer` + TSL node materials. Check the project's actual renderer first — the Genex scaffold ships vanilla WebGL three.js. On WebGL, adapt the technique with standard materials / `EffectComposer` passes or pick a simpler alternative; never switch renderers mid-project.
|
|
5
|
+
> **Renderer note:** this reference assumes `WebGPURenderer` + TSL node materials. Check the project's actual renderer first — the Genex scaffold ships vanilla WebGL three.js. On WebGL, adapt the technique with standard materials / `EffectComposer` passes or pick a simpler alternative; never switch renderers mid-project.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -75,10 +75,6 @@ Pick what matches the game; most games need exactly one or two of these.
|
|
|
75
75
|
lives on the stick; the static circle is fine for slower games. On the
|
|
76
76
|
bundled character controller, pass it through instead — see
|
|
77
77
|
`$genex-threejs-character-controller` (`joystick: { x: joy.x, y: joy.y }`).
|
|
78
|
-
Touch axes obey the same screen-direction contract as WASD and the mouse
|
|
79
|
-
(`$genex-threejs-camera-direction`): stick-right must move the player
|
|
80
|
-
screen-right, drag-right must turn the view right — a flipped feel is a sign
|
|
81
|
-
bug in the mapping, never a device quirk.
|
|
82
78
|
- **Camera look → drag zone on the right half.** Default `DragZone()` is
|
|
83
79
|
exactly that; per frame `const { dx, dy } = look.consumeDelta()` then apply
|
|
84
80
|
to yaw/pitch with the same sensitivity scale as the mouse path. Games on the
|
|
@@ -153,13 +149,6 @@ z-order stays: drag zone (5) under joystick/buttons (10) under pause menus.
|
|
|
153
149
|
canvas. If the game also needs taps there (tap to shoot), read them from the
|
|
154
150
|
zone (`onChange` + a small-movement threshold) or shrink the zone.
|
|
155
151
|
|
|
156
|
-
## Input is half of phone-playable
|
|
157
|
-
|
|
158
|
-
This skill is INPUT only. A phone that can steer a game that then runs out of
|
|
159
|
-
GPU memory still loses the player — the rendering half (device tiers, DPR and
|
|
160
|
-
shadow budgets, the runtime governor, per-tier asset rungs) lives in
|
|
161
|
-
`$genex-threejs-adaptive-quality` and is wired at boot for every game.
|
|
162
|
-
|
|
163
152
|
## When you skip
|
|
164
153
|
|
|
165
154
|
Say it in one plain line — *"skipped touch controls: the game is
|
|
@@ -72,17 +72,9 @@ everything twice.
|
|
|
72
72
|
skill's "Self-testing a draft" section) — so you see the game, not the
|
|
73
73
|
sign-in gate.
|
|
74
74
|
2. Press each documented control once (keys, pointer); assert a **visible
|
|
75
|
-
response
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
screen-RIGHT (for a pan camera the viewport slides right — the terrain
|
|
79
|
-
streams LEFT); `KeyA`/ArrowLeft mirrors it. Keyboard synthesis needs no
|
|
80
|
-
pointer lock, so this works headless. For drag rigs, drag right AND drag up:
|
|
81
|
-
both axes must follow the one stated convention (grab-the-world or
|
|
82
|
-
move-the-camera — never one each). A response in the WRONG direction is a
|
|
83
|
-
fail, not a note: two shipped games passed "controls respond" while D
|
|
84
|
-
steered screen-left. For an animated character, capture idle, walk, run,
|
|
85
|
-
crouch-idle, crouch-move, and jump. Inspect shoulders, elbows, wrists, and hands as well
|
|
75
|
+
response** to every one — the player moves, the camera turns, the button
|
|
76
|
+
fires. For an animated character, capture idle, walk, run, crouch-idle,
|
|
77
|
+
crouch-move, and jump. Inspect shoulders, elbows, wrists, and hands as well
|
|
86
78
|
as the feet: a fully bound non-T-pose can still be stylistically broken.
|
|
87
79
|
Reject shrugging palms-up poses, permanently raised elbows, or a gait whose
|
|
88
80
|
upper-body style contradicts the requested character. “No T-pose” is not
|
|
@@ -112,19 +104,16 @@ everything twice.
|
|
|
112
104
|
floating above detached wheels means the model was box-fit against the
|
|
113
105
|
preset's wheelbase (the vehicle skill's "Custom generated bodies" rules
|
|
114
106
|
fix it).
|
|
115
|
-
6.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
the pointer locks (cursor gone, mouse-RIGHT turns the view RIGHT and
|
|
121
|
-
mouse-UP looks UP — name BOTH axes; yaw-only evidence has let inverted pitch
|
|
122
|
-
ship); press Esc and assert the "click to aim/resume" cue appears. The cue
|
|
123
|
-
should be the bundled `createAimCue` helper (or an equivalent
|
|
107
|
+
6. Aim games get one extra pass (MANDATORY bucket — first-person or mouse-aimed):
|
|
108
|
+
state which pointer bucket the game chose; click the canvas and assert the
|
|
109
|
+
pointer locks (cursor gone, mouse-RIGHT turns the view RIGHT — check the axis,
|
|
110
|
+
not just that it moves); press Esc and assert the "click to aim/resume" cue
|
|
111
|
+
appears. The cue should be the bundled `createAimCue` helper (or an equivalent
|
|
124
112
|
`onAimChange`-driven overlay) — a MANDATORY-bucket game with no unlocked cue
|
|
125
|
-
fails.
|
|
126
|
-
|
|
127
|
-
|
|
113
|
+
fails. Also confirm a cursor-core game opted OUT (`pointerLockAim: false`) so it
|
|
114
|
+
isn't grabbing the cursor. Headless caveat: `requestPointerLock` throws in
|
|
115
|
+
headless Chromium — assert the wiring and the unlocked cue in a screenshot, and
|
|
116
|
+
say plainly that the lock itself needs one manual click.
|
|
128
117
|
7. **Ask the scene the three things the screenshot cannot answer** (below). Run it
|
|
129
118
|
once, in the same browser you already have open.
|
|
130
119
|
|
|
@@ -290,12 +279,6 @@ visual-system work — the sequence above.
|
|
|
290
279
|
|
|
291
280
|
- a MANDATORY-bucket aim game never requests pointer lock, or locks with no
|
|
292
281
|
visible unlocked cue;
|
|
293
|
-
- a movement key or look axis whose on-screen direction contradicts its label
|
|
294
|
-
(D turning the vehicle screen-left, mouse-up looking down with no invert
|
|
295
|
-
option);
|
|
296
|
-
- drag-pan axes that mix conventions (one axis grab-the-world, the other
|
|
297
|
-
move-the-camera);
|
|
298
|
-
- a non-cursor-core game that leaves the OS cursor visible during play;
|
|
299
282
|
- approval relies on a single frame;
|
|
300
283
|
- post-processing cannot be disabled per pass;
|
|
301
284
|
- random seeds are not reproducible;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use this reference for bounded or analytic water with shared displacement and normals, derivative-filtered detail, analytic reflection, heuristic refraction, absorption, and crest foam. Use `$genex-threejs-spectral-ocean` for stochastic FFT seas.
|
|
4
4
|
|
|
5
|
-
> **Renderer note:** this reference assumes `WebGPURenderer` + TSL node materials. Check the project's actual renderer first — the Genex scaffold ships vanilla WebGL three.js. On WebGL, adapt the technique with standard materials / `EffectComposer` passes or pick a simpler alternative; never switch renderers mid-project.
|
|
5
|
+
> **Renderer note:** this reference assumes `WebGPURenderer` + TSL node materials. Check the project's actual renderer first — the Genex scaffold ships vanilla WebGL three.js. On WebGL, adapt the technique with standard materials / `EffectComposer` passes or pick a simpler alternative; never switch renderers mid-project.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -38,7 +38,7 @@ update, so update immediately.)
|
|
|
38
38
|
Run exactly the command the nudge printed, from the game project root:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
npm i -D @genex-ai/cli-demo@
|
|
41
|
+
npm i -D @genex-ai/cli-demo@latest # the genex CLI (a dev dependency)
|
|
42
42
|
npm i @genex-ai/embed-sdk@latest # identity/saves SDK (ships inside the game)
|
|
43
43
|
npm i @genex-ai/multiplayer@latest # multiplayer SDK (only if the game uses it)
|
|
44
44
|
```
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
// Genex adaptive-quality: the runtime governor (mobile-readiness program).
|
|
2
|
-
// Tiers pick the START; the governor owns the RUNTIME knobs forever — phones
|
|
3
|
-
// thermally throttle after minutes, so a game that benched fine at 0:30
|
|
4
|
-
// degrades at 8:00. Feed it a frame time every frame; wire the callbacks to
|
|
5
|
-
// the runtime-changeable knobs only (DPR, post passes, draw distance, frame
|
|
6
|
-
// cap — NEVER context-creation flags like antialias, those are fixed).
|
|
7
|
-
//
|
|
8
|
-
// Step-down ladder on sustained slowness: DPR ×0.8 → post off → draw distance
|
|
9
|
-
// down → 30fps cap. Step-up is slow (hysteresis) and a step that failed twice
|
|
10
|
-
// is never re-attempted. It also self-reports renderer.info counts to
|
|
11
|
-
// window.__GENEX_QUALITY__ — the crash watchdog attaches them to its beacons,
|
|
12
|
-
// which is how the field data that calibrates the whole program gets its
|
|
13
|
-
// memory signal.
|
|
14
|
-
import type { QualityTier } from './tier.ts';
|
|
15
|
-
|
|
16
|
-
export interface GovernorCallbacks {
|
|
17
|
-
/** Apply a DPR multiplier (1 = tier cap). E.g. renderer.setPixelRatio(Math.min(realDpr, tier.dprCap * m)). */
|
|
18
|
-
setDprScale?: (multiplier: number) => void;
|
|
19
|
-
/** Toggle the post stack between the tier's level and 'off'. */
|
|
20
|
-
setPostEnabled?: (enabled: boolean) => void;
|
|
21
|
-
/** Apply a draw-distance multiplier (1 = tier scale). */
|
|
22
|
-
setDrawDistanceScale?: (multiplier: number) => void;
|
|
23
|
-
/** Apply a frame cap (0 = uncapped). Pace to a STABLE 30 over a stuttery 45. */
|
|
24
|
-
setFrameCap?: (fps: number) => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface RendererInfoLike {
|
|
28
|
-
info?: { memory?: { textures?: number; geometries?: number } };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const SLOW_WINDOW_MS = 4000; // sustained, not spikes — shader compiles must not trigger steps
|
|
32
|
-
const RECOVER_WINDOW_MS = 20000; // step up slowly (hysteresis)
|
|
33
|
-
const MEM_REPORT_MS = 5000;
|
|
34
|
-
|
|
35
|
-
export class QualityGovernor {
|
|
36
|
-
private tier: QualityTier;
|
|
37
|
-
private steps: Array<{ apply: () => void; revert: () => void; failures: number; pendingRecovery?: boolean }>;
|
|
38
|
-
private applied = 0;
|
|
39
|
-
private slowSince: number | null = null;
|
|
40
|
-
private goodSince: number | null = null;
|
|
41
|
-
private lastMemReport = 0;
|
|
42
|
-
private paused = false;
|
|
43
|
-
|
|
44
|
-
constructor(tier: QualityTier, callbacks: GovernorCallbacks, renderer?: RendererInfoLike) {
|
|
45
|
-
this.tier = tier;
|
|
46
|
-
this.rendererRef = renderer;
|
|
47
|
-
const c = callbacks;
|
|
48
|
-
this.steps = [
|
|
49
|
-
{
|
|
50
|
-
apply: () => c.setDprScale?.(0.8),
|
|
51
|
-
revert: () => c.setDprScale?.(1),
|
|
52
|
-
failures: 0,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
apply: () => c.setPostEnabled?.(false),
|
|
56
|
-
revert: () => c.setPostEnabled?.(true),
|
|
57
|
-
failures: 0,
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
apply: () => c.setDrawDistanceScale?.(0.6),
|
|
61
|
-
revert: () => c.setDrawDistanceScale?.(1),
|
|
62
|
-
failures: 0,
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
apply: () => c.setFrameCap?.(30),
|
|
66
|
-
revert: () => c.setFrameCap?.(this.tier.frameCap),
|
|
67
|
-
failures: 0,
|
|
68
|
-
},
|
|
69
|
-
];
|
|
70
|
-
// Visibility lifecycle: a backgrounded game must stop burning GPU/audio on
|
|
71
|
-
// exactly the thermally-constrained device class. The game's loop should
|
|
72
|
-
// also pause itself; the governor at minimum stops judging frames.
|
|
73
|
-
try {
|
|
74
|
-
document.addEventListener('visibilitychange', () => {
|
|
75
|
-
this.paused = document.visibilityState !== 'visible';
|
|
76
|
-
this.slowSince = null;
|
|
77
|
-
this.goodSince = null;
|
|
78
|
-
});
|
|
79
|
-
} catch {
|
|
80
|
-
/* non-DOM context */
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private rendererRef?: RendererInfoLike;
|
|
85
|
-
|
|
86
|
-
/** Call once per frame with the frame delta in ms (performance.now() based). */
|
|
87
|
-
frame(deltaMs: number): void {
|
|
88
|
-
if (this.paused) return;
|
|
89
|
-
const now = performance.now();
|
|
90
|
-
const budgetMs = 1000 / Math.min(this.tier.frameCap, 60) + 8; // ~24ms at 30, ~25ms at 60
|
|
91
|
-
|
|
92
|
-
if (deltaMs > budgetMs) {
|
|
93
|
-
this.goodSince = null;
|
|
94
|
-
if (this.slowSince == null) this.slowSince = now;
|
|
95
|
-
else if (now - this.slowSince > SLOW_WINDOW_MS) {
|
|
96
|
-
this.stepDown();
|
|
97
|
-
this.slowSince = null;
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
this.slowSince = null;
|
|
101
|
-
if (this.goodSince == null) this.goodSince = now;
|
|
102
|
-
else if (now - this.goodSince > RECOVER_WINDOW_MS) {
|
|
103
|
-
this.stepUp();
|
|
104
|
-
this.goodSince = null;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Memory self-report for the crash watchdog's beacons (field calibration).
|
|
109
|
-
if (now - this.lastMemReport > MEM_REPORT_MS) {
|
|
110
|
-
this.lastMemReport = now;
|
|
111
|
-
try {
|
|
112
|
-
const mem = this.rendererRef?.info?.memory;
|
|
113
|
-
if (mem) {
|
|
114
|
-
(window as Window & { __GENEX_QUALITY__?: { tex?: number; geo?: number } }).__GENEX_QUALITY__ =
|
|
115
|
-
{ tex: mem.textures ?? 0, geo: mem.geometries ?? 0 };
|
|
116
|
-
}
|
|
117
|
-
} catch {
|
|
118
|
-
/* observational only */
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
private stepDown(): void {
|
|
124
|
-
if (this.applied >= this.steps.length) return;
|
|
125
|
-
const step = this.steps[this.applied]!;
|
|
126
|
-
// A re-application of a rung whose recovery is still pending means the
|
|
127
|
-
// revert did NOT hold — that's the failure being counted, never the
|
|
128
|
-
// successful revert itself.
|
|
129
|
-
if (step.pendingRecovery) {
|
|
130
|
-
step.pendingRecovery = false;
|
|
131
|
-
step.failures++;
|
|
132
|
-
}
|
|
133
|
-
step.apply();
|
|
134
|
-
this.applied++;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private stepUp(): void {
|
|
138
|
-
if (this.applied === 0) return;
|
|
139
|
-
const step = this.steps[this.applied - 1]!;
|
|
140
|
-
// Never re-attempt a knob whose recovery failed twice (reverting it put
|
|
141
|
-
// the game back over budget both times) — it stays applied for the session.
|
|
142
|
-
if (step.failures >= 2) return;
|
|
143
|
-
step.pendingRecovery = true;
|
|
144
|
-
step.revert();
|
|
145
|
-
this.applied--;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// Genex adaptive-quality: per-tier asset variant selection (mobile-readiness
|
|
2
|
-
// program, Option A+D). Generated assets ship with GUARANTEED downscale rungs
|
|
3
|
-
// in R2 — sibling roles like "skybox-equirect@2048" — so phones can load a
|
|
4
|
-
// ~11 MB sky instead of the 8192x4096 original (~178 MB decoded). Desktop
|
|
5
|
-
// always loads the bare URL: the ladder exists so phones stop dying, never to
|
|
6
|
-
// make desktops uglier.
|
|
7
|
-
//
|
|
8
|
-
// FALLBACK CONTRACT: a rung can be missing (remixed old URLs, an environment
|
|
9
|
-
// whose backfill hasn't run). loadTextureWithFallback retries the bare URL on
|
|
10
|
-
// a rung failure, so the worst case is today's behavior — never a broken boot.
|
|
11
|
-
import type { QualityTier } from './tier.ts';
|
|
12
|
-
|
|
13
|
-
const GENEX_GENERATIONS_RE = /^https:\/\/assets\.genex\.technology\/generations\/[^/]+\/[^/@]+$/;
|
|
14
|
-
|
|
15
|
-
/** Rung widths by role family — mirrors the server's ladder (store.ts MOBILE_RUNGS). */
|
|
16
|
-
function rungWidthFor(url: string, tier: QualityTier): number | null {
|
|
17
|
-
if (tier.name !== 'phone' && tier.name !== 'phone-low') return null;
|
|
18
|
-
const role = url.split('/').pop() ?? '';
|
|
19
|
-
if (role === 'skybox-equirect') return tier.name === 'phone-low' ? 2048 : 4096;
|
|
20
|
-
if (role === 'texture-basecolor' || role === 'image-main' || /^image-alt-\d+$/.test(role)) {
|
|
21
|
-
return tier.name === 'phone-low' ? 1024 : 2048;
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Resolve the URL a THIS-tier device should load. Non-generated URLs and
|
|
28
|
-
* desktop tiers pass through untouched; phone tiers get the rung sibling.
|
|
29
|
-
*/
|
|
30
|
-
export function pickAsset(url: string, tier: QualityTier): string {
|
|
31
|
-
if (!GENEX_GENERATIONS_RE.test(url)) return url;
|
|
32
|
-
const width = rungWidthFor(url, tier);
|
|
33
|
-
return width ? `${url}@${width}` : url;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Load an image URL through the ladder with the bare-URL fallback. Use it for
|
|
38
|
-
* generated skyboxes/textures instead of raw TextureLoader.loadAsync:
|
|
39
|
-
*
|
|
40
|
-
* const texture = await loadTextureWithFallback(
|
|
41
|
-
* SKYBOX_URL, tier, (u) => new THREE.TextureLoader().loadAsync(u),
|
|
42
|
-
* );
|
|
43
|
-
*/
|
|
44
|
-
export async function loadTextureWithFallback<T>(
|
|
45
|
-
url: string,
|
|
46
|
-
tier: QualityTier,
|
|
47
|
-
load: (resolvedUrl: string) => Promise<T>,
|
|
48
|
-
): Promise<T> {
|
|
49
|
-
const picked = pickAsset(url, tier);
|
|
50
|
-
if (picked === url) return load(url);
|
|
51
|
-
try {
|
|
52
|
-
return await load(picked);
|
|
53
|
-
} catch {
|
|
54
|
-
// Missing rung (old asset, un-backfilled env) — degrade to the original.
|
|
55
|
-
return load(url);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
// Genex adaptive-quality: device-tier detection (mobile-readiness program).
|
|
2
|
-
// Boot-conservative by design — on phones you can recover from ugly, you
|
|
3
|
-
// cannot recover from a jetsam kill, and boot is exactly when iOS kills. The
|
|
4
|
-
// governor (governor.ts) steps quality UP after smooth seconds, so a
|
|
5
|
-
// too-cautious start costs moments of softness, never a crash.
|
|
6
|
-
//
|
|
7
|
-
// Detection honesty: no single signal is trustworthy. iOS masks the WebGL
|
|
8
|
-
// renderer string to "Apple GPU" (screen+DPR+OS-version is the real Apple
|
|
9
|
-
// signal); Android exposes detailed renderer strings (Adreno/Mali) worth a
|
|
10
|
-
// small lookup; the runtime governor measures actual frame times and corrects
|
|
11
|
-
// both directions. All heuristics here only pick the STARTING tier.
|
|
12
|
-
|
|
13
|
-
export type TierName = 'phone-low' | 'phone' | 'desktop' | 'desktop-high';
|
|
14
|
-
|
|
15
|
-
export interface QualityTier {
|
|
16
|
-
name: TierName;
|
|
17
|
-
/** Cap for renderer.setPixelRatio(Math.min(devicePixelRatio, dprCap)). */
|
|
18
|
-
dprCap: number;
|
|
19
|
-
/** WebGL context antialias (context-creation-FIXED — cannot change live). */
|
|
20
|
-
antialias: boolean;
|
|
21
|
-
/** Directional/spot shadow map size (0 = shadows off). */
|
|
22
|
-
shadowMapSize: number;
|
|
23
|
-
/** 'off' = tone mapping only; 'light' = +FXAA/vignette; 'full' = the named stack. */
|
|
24
|
-
postLevel: 'off' | 'light' | 'full';
|
|
25
|
-
/** Multiplier for particle counts / scatter density. */
|
|
26
|
-
particleScale: number;
|
|
27
|
-
/** Multiplier for draw distance / fog far. */
|
|
28
|
-
drawDistanceScale: number;
|
|
29
|
-
/** Frame target — phones pace to a STABLE 30 over a stuttery 45. */
|
|
30
|
-
frameCap: number;
|
|
31
|
-
/** Max remote players fully animated/drawn (multiplayer); rest billboard. */
|
|
32
|
-
remoteAvatarCap: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const TIERS: Record<TierName, QualityTier> = {
|
|
36
|
-
'phone-low': {
|
|
37
|
-
name: 'phone-low',
|
|
38
|
-
dprCap: 1,
|
|
39
|
-
antialias: false,
|
|
40
|
-
shadowMapSize: 512,
|
|
41
|
-
postLevel: 'off',
|
|
42
|
-
particleScale: 0.25,
|
|
43
|
-
drawDistanceScale: 0.5,
|
|
44
|
-
frameCap: 30,
|
|
45
|
-
remoteAvatarCap: 4,
|
|
46
|
-
},
|
|
47
|
-
phone: {
|
|
48
|
-
name: 'phone',
|
|
49
|
-
dprCap: 1.5,
|
|
50
|
-
antialias: false,
|
|
51
|
-
shadowMapSize: 1024,
|
|
52
|
-
postLevel: 'light',
|
|
53
|
-
particleScale: 0.5,
|
|
54
|
-
drawDistanceScale: 0.75,
|
|
55
|
-
frameCap: 60,
|
|
56
|
-
remoteAvatarCap: 8,
|
|
57
|
-
},
|
|
58
|
-
desktop: {
|
|
59
|
-
name: 'desktop',
|
|
60
|
-
dprCap: 2,
|
|
61
|
-
antialias: true,
|
|
62
|
-
shadowMapSize: 2048,
|
|
63
|
-
postLevel: 'full',
|
|
64
|
-
particleScale: 1,
|
|
65
|
-
drawDistanceScale: 1,
|
|
66
|
-
frameCap: 60,
|
|
67
|
-
remoteAvatarCap: 64,
|
|
68
|
-
},
|
|
69
|
-
'desktop-high': {
|
|
70
|
-
name: 'desktop-high',
|
|
71
|
-
dprCap: 2,
|
|
72
|
-
antialias: true,
|
|
73
|
-
shadowMapSize: 2048,
|
|
74
|
-
postLevel: 'full',
|
|
75
|
-
particleScale: 1,
|
|
76
|
-
drawDistanceScale: 1,
|
|
77
|
-
frameCap: 240,
|
|
78
|
-
remoteAvatarCap: 64,
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/** Quality setting persisted PER DEVICE (localStorage) — quality is a property
|
|
83
|
-
* of the phone, not the player, so it deliberately does not ride account
|
|
84
|
-
* state. 'auto' = heuristics + governor. */
|
|
85
|
-
const SETTING_KEY = 'genex:quality';
|
|
86
|
-
export type QualitySetting = 'auto' | 'low' | 'medium' | 'high';
|
|
87
|
-
|
|
88
|
-
export function getQualitySetting(): QualitySetting {
|
|
89
|
-
try {
|
|
90
|
-
const v = localStorage.getItem(SETTING_KEY);
|
|
91
|
-
if (v === 'low' || v === 'medium' || v === 'high') return v;
|
|
92
|
-
} catch {
|
|
93
|
-
/* storage blocked */
|
|
94
|
-
}
|
|
95
|
-
return 'auto';
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function setQualitySetting(v: QualitySetting): void {
|
|
99
|
-
try {
|
|
100
|
-
localStorage.setItem(SETTING_KEY, v);
|
|
101
|
-
} catch {
|
|
102
|
-
/* storage blocked */
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function isTouchDevice(): boolean {
|
|
107
|
-
try {
|
|
108
|
-
const coarse = matchMedia('(pointer: coarse)').matches;
|
|
109
|
-
const touch = navigator.maxTouchPoints > 1;
|
|
110
|
-
const nav = navigator as Navigator & { userAgentData?: { mobile?: boolean } };
|
|
111
|
-
const mobileUA = nav.userAgentData
|
|
112
|
-
? !!nav.userAgentData.mobile
|
|
113
|
-
: /Mobi|Android/i.test(navigator.userAgent);
|
|
114
|
-
return (touch && coarse) || mobileUA;
|
|
115
|
-
} catch {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Android GPU model → strong-enough-for-'phone' (vs 'phone-low'). Tiny,
|
|
121
|
-
* vendored on purpose (no detect-gpu dependency): renderer strings are only
|
|
122
|
-
* meaningful on Android/desktop anyway, and the governor corrects mistakes. */
|
|
123
|
-
const STRONG_ANDROID_GPU = /Adreno \(TM\) [67]\d\d|Adreno \(TM\) 8|Mali-G7[18]|Mali-G[89]\d|Immortalis|Xclipse/i;
|
|
124
|
-
|
|
125
|
-
function androidGpuLooksStrong(): boolean {
|
|
126
|
-
try {
|
|
127
|
-
const canvas = document.createElement('canvas');
|
|
128
|
-
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
129
|
-
if (!gl) return false;
|
|
130
|
-
const info = gl.getExtension('WEBGL_debug_renderer_info');
|
|
131
|
-
const renderer = info ? String(gl.getParameter(info.UNMASKED_RENDERER_WEBGL)) : '';
|
|
132
|
-
const ext = gl.getExtension('WEBGL_lose_context');
|
|
133
|
-
if (ext) ext.loseContext(); // free the probe context immediately
|
|
134
|
-
return STRONG_ANDROID_GPU.test(renderer);
|
|
135
|
-
} catch {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Pick the STARTING tier. Manual setting wins; 'auto' uses heuristics:
|
|
142
|
-
* - non-touch → desktop
|
|
143
|
-
* - touch: old OS or small memory signals → phone-low; strong/new → phone
|
|
144
|
-
* The kit may have clamped devicePixelRatio — prefer its pristine stash.
|
|
145
|
-
*/
|
|
146
|
-
export function detectTier(): QualityTier {
|
|
147
|
-
const setting = getQualitySetting();
|
|
148
|
-
if (setting === 'low') return TIERS['phone-low'];
|
|
149
|
-
if (setting === 'medium') return TIERS.phone;
|
|
150
|
-
if (setting === 'high') return TIERS.desktop;
|
|
151
|
-
|
|
152
|
-
if (!isTouchDevice()) return TIERS.desktop;
|
|
153
|
-
|
|
154
|
-
const ua = navigator.userAgent;
|
|
155
|
-
const kit = (window as Window & { __GENEX_KIT__?: { realDpr?: number } }).__GENEX_KIT__;
|
|
156
|
-
const dpr = kit?.realDpr ?? window.devicePixelRatio;
|
|
157
|
-
const isIOS = /iPhone|iPad|iPod/.test(ua) || (navigator.maxTouchPoints > 1 && /Mac/.test(ua));
|
|
158
|
-
|
|
159
|
-
if (isIOS) {
|
|
160
|
-
// iOS version floors device age (iOS 17 ⇒ XS/2018+; screen+DPR refine).
|
|
161
|
-
const m = ua.match(/OS (\d+)_/);
|
|
162
|
-
const major = m ? Number(m[1]) : 0;
|
|
163
|
-
if (major >= 17) return TIERS.phone;
|
|
164
|
-
return TIERS['phone-low'];
|
|
165
|
-
}
|
|
166
|
-
const am = ua.match(/Android (\d+)/);
|
|
167
|
-
const androidMajor = am ? Number(am[1]) : 0;
|
|
168
|
-
if (androidMajor >= 13 && (dpr >= 2.5 || androidGpuLooksStrong())) return TIERS.phone;
|
|
169
|
-
return TIERS['phone-low'];
|
|
170
|
-
}
|