@genex-ai/cli-demo 0.93.0 → 0.94.0-dev.250
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 +26 -5
- package/package.json +1 -1
- package/templates/skills/genex-ai-model/SKILL.md +3 -2
- package/templates/skills/genex-ai-skybox/SKILL.md +4 -4
- package/templates/skills/genex-ai-texture/SKILL.md +1 -1
- package/templates/skills/genex-ai-video/SKILL.md +1 -1
- package/templates/skills/genex-game-director/SKILL.md +321 -411
- package/templates/skills/genex-game-director/references/design-contract.md +130 -179
- package/templates/skills/genex-game-director/references/routing-map.md +242 -362
- package/templates/skills/genex-getting-started/SKILL.md +2 -2
- package/templates/skills/genex-threejs-exposure-color-grading/SKILL.md +3 -7
- package/templates/skills/genex-threejs-multiplayer/SKILL.md +19 -2
- package/templates/skills/genex-threejs-procedural-assets/SKILL.md +112 -0
- package/templates/skills/genex-threejs-procedural-materials/SKILL.md +3 -7
- package/templates/skills/genex-threejs-procedural-vfx/SKILL.md +6 -11
- package/templates/skills/genex-threejs-shadow-systems/SKILL.md +2 -3
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +0 -4
- package/templates/skills/genex-updates/SKILL.md +1 -1
- package/templates/skills/genex-threejs-atmosphere-aerial-perspective/SKILL.md +0 -46
- package/templates/skills/genex-threejs-atmosphere-aerial-perspective/references/atmosphere.md +0 -213
- package/templates/skills/genex-threejs-bloom/SKILL.md +0 -44
- package/templates/skills/genex-threejs-bloom/references/bloom.md +0 -187
- package/templates/skills/genex-threejs-exposure-color-grading/references/exposure-grading.md +0 -205
- package/templates/skills/genex-threejs-game-content/SKILL.md +0 -198
- package/templates/skills/genex-threejs-game-content/references/content-tables.md +0 -269
- package/templates/skills/genex-threejs-image-pipeline/SKILL.md +0 -56
- package/templates/skills/genex-threejs-image-pipeline/references/image-pipeline.md +0 -197
- package/templates/skills/genex-threejs-lighting-design/SKILL.md +0 -144
- package/templates/skills/genex-threejs-lighting-design/references/light-recipes.md +0 -137
- package/templates/skills/genex-threejs-open-world/SKILL.md +0 -149
- package/templates/skills/genex-threejs-open-world/references/terrain-streaming.md +0 -215
- package/templates/skills/genex-threejs-precipitation-surfaces/SKILL.md +0 -59
- package/templates/skills/genex-threejs-precipitation-surfaces/references/precipitation-surfaces.md +0 -181
- package/templates/skills/genex-threejs-procedural-architecture/SKILL.md +0 -50
- package/templates/skills/genex-threejs-procedural-architecture/references/architecture-systems.md +0 -509
- package/templates/skills/genex-threejs-procedural-fields/SKILL.md +0 -61
- package/templates/skills/genex-threejs-procedural-fields/references/field-systems.md +0 -232
- package/templates/skills/genex-threejs-procedural-geometry/SKILL.md +0 -39
- package/templates/skills/genex-threejs-procedural-geometry/references/mesh-systems.md +0 -202
- package/templates/skills/genex-threejs-procedural-materials/references/material-systems.md +0 -198
- package/templates/skills/genex-threejs-procedural-planets/SKILL.md +0 -48
- package/templates/skills/genex-threejs-procedural-planets/references/planet-systems.md +0 -498
- package/templates/skills/genex-threejs-procedural-vegetation/SKILL.md +0 -52
- package/templates/skills/genex-threejs-procedural-vegetation/references/vegetation-systems.md +0 -314
- package/templates/skills/genex-threejs-procedural-vfx/references/vfx-systems.md +0 -218
- package/templates/skills/genex-threejs-raymarched-space-effects/SKILL.md +0 -37
- package/templates/skills/genex-threejs-raymarched-space-effects/references/space-effects.md +0 -194
- package/templates/skills/genex-threejs-screen-space-ambient-occlusion/SKILL.md +0 -35
- package/templates/skills/genex-threejs-screen-space-ambient-occlusion/references/ambient-occlusion.md +0 -441
- package/templates/skills/genex-threejs-spectral-ocean/SKILL.md +0 -52
- package/templates/skills/genex-threejs-spectral-ocean/references/spectral-ocean.md +0 -473
- package/templates/skills/genex-threejs-temporal-surfaces/SKILL.md +0 -41
- package/templates/skills/genex-threejs-temporal-surfaces/references/temporal-surfaces.md +0 -207
- package/templates/skills/genex-threejs-visual-validation/references/visual-validation.md +0 -427
- package/templates/skills/genex-threejs-volumetric-clouds/SKILL.md +0 -49
- package/templates/skills/genex-threejs-volumetric-clouds/references/volumetric-clouds.md +0 -579
- package/templates/skills/genex-threejs-water-optics/SKILL.md +0 -57
- package/templates/skills/genex-threejs-water-optics/references/water-optics.md +0 -195
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
# Genex game content — copy-paste systems
|
|
2
|
-
|
|
3
|
-
Four small modules that turn contract lines into shipped content: an event
|
|
4
|
-
bus, a quest engine, a dialogue walker with quest hooks, and items/economy +
|
|
5
|
-
XP. Copy them, fill the tables, and keep content OUT of engine code — adding
|
|
6
|
-
a quest, an item, or a vendor must always be a table edit.
|
|
7
|
-
|
|
8
|
-
All snippets are plain vanilla-ts (no enums, no decorators) and assume a
|
|
9
|
-
single shared game-state object `G` you already have (player stats, wallet,
|
|
10
|
-
the scene). Adapt names, not shapes.
|
|
11
|
-
|
|
12
|
-
## 1. Event bus — the only coupling
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
// events.ts — combat, loot, and movement EMIT; quests and UI SUBSCRIBE.
|
|
16
|
-
type GameEvent =
|
|
17
|
-
| { type: "enemy:died"; enemyKind: string }
|
|
18
|
-
| { type: "item:pickup"; item: string; count: number }
|
|
19
|
-
| { type: "chest:opened"; chestId: string }
|
|
20
|
-
| { type: "npc:talked"; npc: string }
|
|
21
|
-
| { type: "zone:entered"; location: string };
|
|
22
|
-
|
|
23
|
-
type Handler = (e: GameEvent) => void;
|
|
24
|
-
const handlers: Handler[] = [];
|
|
25
|
-
|
|
26
|
-
export function onEvent(h: Handler): void {
|
|
27
|
-
handlers.push(h);
|
|
28
|
-
}
|
|
29
|
-
export function emit(e: GameEvent): void {
|
|
30
|
-
for (const h of handlers) h(e);
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## 2. Quest engine — defs are data, progress is events
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
// quests.ts
|
|
38
|
-
import { onEvent } from "./events.ts";
|
|
39
|
-
|
|
40
|
-
type Objective =
|
|
41
|
-
| { kind: "kill"; target: string; count: number }
|
|
42
|
-
| { kind: "collect"; item: string; count: number }
|
|
43
|
-
| { kind: "reach"; location: string }
|
|
44
|
-
| { kind: "talk"; npc: string };
|
|
45
|
-
|
|
46
|
-
type QuestDef = {
|
|
47
|
-
id: string;
|
|
48
|
-
giver: string;
|
|
49
|
-
prereq?: string;
|
|
50
|
-
title: string;
|
|
51
|
-
brief: string;
|
|
52
|
-
turnIn: string; // authored prose for the hand-in moment
|
|
53
|
-
objective: Objective;
|
|
54
|
-
rewards: { gold?: number; xp?: number; items?: string[] };
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// CONTENT — the whole quest list lives here. Main chain via prereq; the
|
|
58
|
-
// prose is authored, not generated filler. Counts must match the contract.
|
|
59
|
-
export const QUESTS: QuestDef[] = [
|
|
60
|
-
{
|
|
61
|
-
id: "q_wolves",
|
|
62
|
-
giver: "elder",
|
|
63
|
-
title: "Teeth in the Tall Grass",
|
|
64
|
-
brief:
|
|
65
|
-
"Wolves have taken the shepherd's flock and now they circle the palisade at dusk. Thin the pack before someone's child is next.",
|
|
66
|
-
turnIn: "Five pelts. The flock sleeps easy tonight — and so do we. Take this.",
|
|
67
|
-
objective: { kind: "kill", target: "wolf", count: 5 },
|
|
68
|
-
rewards: { gold: 40, xp: 60 },
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
id: "q_amulet",
|
|
72
|
-
giver: "elder",
|
|
73
|
-
prereq: "q_wolves",
|
|
74
|
-
title: "What the Crypt Keeps",
|
|
75
|
-
brief:
|
|
76
|
-
"Our founder's amulet lies in the old crypt east of the fields. The dead have grown restless around it — bring it back, and mind the narrow dark.",
|
|
77
|
-
turnIn: "The amulet… after all these years. You have the village's gratitude, and its coin.",
|
|
78
|
-
objective: { kind: "collect", item: "founder_amulet", count: 1 },
|
|
79
|
-
rewards: { gold: 80, xp: 120, items: ["potion_health"] },
|
|
80
|
-
},
|
|
81
|
-
// …side quests: no prereq, different givers, different objective kinds.
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
type QuestStatus = "locked" | "available" | "active" | "ready" | "done";
|
|
85
|
-
type QuestState = { status: QuestStatus; progress: number };
|
|
86
|
-
|
|
87
|
-
export const questState = new Map<string, QuestState>();
|
|
88
|
-
for (const q of QUESTS) {
|
|
89
|
-
questState.set(q.id, { status: q.prereq ? "locked" : "available", progress: 0 });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const def = (id: string): QuestDef => QUESTS.find((q) => q.id === id)!;
|
|
93
|
-
|
|
94
|
-
export function accept(id: string): void {
|
|
95
|
-
const s = questState.get(id)!;
|
|
96
|
-
if (s.status !== "available") return;
|
|
97
|
-
s.status = "active";
|
|
98
|
-
// Contract check "can't dead-end": count pre-acceptance progress where the
|
|
99
|
-
// fiction allows it (kills already made), or reset knowingly.
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function turnIn(id: string): { gold: number; xp: number; items: string[] } | null {
|
|
103
|
-
const s = questState.get(id)!;
|
|
104
|
-
if (s.status !== "ready") return null;
|
|
105
|
-
s.status = "done";
|
|
106
|
-
for (const q of QUESTS) {
|
|
107
|
-
if (q.prereq === id && questState.get(q.id)!.status === "locked") {
|
|
108
|
-
questState.get(q.id)!.status = "available"; // next chain link opens
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const r = def(id).rewards;
|
|
112
|
-
return { gold: r.gold ?? 0, xp: r.xp ?? 0, items: r.items ?? [] };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function bump(q: QuestDef, s: QuestState, amount = 1): void {
|
|
116
|
-
const needed = "count" in q.objective ? q.objective.count : 1;
|
|
117
|
-
s.progress = Math.min(needed, s.progress + amount);
|
|
118
|
-
if (s.progress >= needed) s.status = "ready"; // journal + marker flip to "return"
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
onEvent((e) => {
|
|
122
|
-
for (const q of QUESTS) {
|
|
123
|
-
const s = questState.get(q.id)!;
|
|
124
|
-
if (s.status !== "active") continue;
|
|
125
|
-
const o = q.objective;
|
|
126
|
-
if (o.kind === "kill" && e.type === "enemy:died" && e.enemyKind === o.target) bump(q, s);
|
|
127
|
-
if (o.kind === "collect" && e.type === "item:pickup" && e.item === o.item) bump(q, s, e.count);
|
|
128
|
-
if (o.kind === "reach" && e.type === "zone:entered" && e.location === o.location) bump(q, s);
|
|
129
|
-
if (o.kind === "talk" && e.type === "npc:talked" && e.npc === o.npc) bump(q, s);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Journal UI, the tracked-quest compass marker, and toasts subscribe to the
|
|
135
|
-
same state — inventory them as screen elements at `$genex-threejs-game-ui`'s
|
|
136
|
-
gate. Persist `[...questState]` (plus world side-effects like opened chests)
|
|
137
|
-
in the per-player slot via `$genex-threejs-embed-auth`.
|
|
138
|
-
|
|
139
|
-
## 3. Dialogue walker — quest states become options
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
// dialogue.ts
|
|
143
|
-
import { QUESTS, questState, accept, turnIn } from "./quests.ts";
|
|
144
|
-
|
|
145
|
-
type DialogueOption = { label: string; next?: DialogueNode; action?: () => void };
|
|
146
|
-
type DialogueNode = { text: string; options: DialogueOption[] };
|
|
147
|
-
|
|
148
|
-
// CONTENT — per-NPC roots: greeting + a couple of authored lore branches.
|
|
149
|
-
// Three real lines is the difference between a person and a signpost.
|
|
150
|
-
const ROOTS: Record<string, DialogueNode> = {
|
|
151
|
-
elder: {
|
|
152
|
-
text: "Maren watches the road as she talks. “Strangers used to mean trade. Lately they mean trouble.”",
|
|
153
|
-
options: [
|
|
154
|
-
{
|
|
155
|
-
label: "Tell me about this village.",
|
|
156
|
-
next: {
|
|
157
|
-
text: "“Three generations behind this palisade. The crypt east of here is older than all of it — and lately, louder.”",
|
|
158
|
-
options: [],
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
],
|
|
162
|
-
},
|
|
163
|
-
// blacksmith, herbalist… — every giver and merchant has a root.
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// The whole walker: render node.text + numbered options; a click (or the
|
|
167
|
-
// 1–9 key) runs option.action?.(), then shows option.next or closes. Emit
|
|
168
|
-
// { type: "npc:talked", npc } when a conversation OPENS — that's what "talk"
|
|
169
|
-
// objectives listen for.
|
|
170
|
-
export function pickOption(node: DialogueNode, index: number): DialogueNode | null {
|
|
171
|
-
const opt = node.options[index];
|
|
172
|
-
if (!opt) return node;
|
|
173
|
-
opt.action?.();
|
|
174
|
-
return opt.next ?? null; // null = close the dialogue panel
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function openDialogue(npc: string): DialogueNode {
|
|
178
|
-
const root = ROOTS[npc];
|
|
179
|
-
const options = [...root.options];
|
|
180
|
-
for (const q of QUESTS) {
|
|
181
|
-
if (q.giver !== npc) continue;
|
|
182
|
-
const s = questState.get(q.id)!;
|
|
183
|
-
if (s.status === "available") {
|
|
184
|
-
options.unshift({
|
|
185
|
-
label: `[Quest] ${q.title}`,
|
|
186
|
-
next: {
|
|
187
|
-
text: q.brief,
|
|
188
|
-
options: [
|
|
189
|
-
{ label: "I'll do it.", action: () => accept(q.id) },
|
|
190
|
-
{ label: "Not now." },
|
|
191
|
-
],
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
} else if (s.status === "active") {
|
|
195
|
-
options.unshift({ label: `[${q.title}] Remind me.`, next: { text: q.brief, options: [] } });
|
|
196
|
-
} else if (s.status === "ready") {
|
|
197
|
-
options.unshift({
|
|
198
|
-
label: `[Complete] ${q.title}`,
|
|
199
|
-
next: { text: q.turnIn, options: [] },
|
|
200
|
-
action: () => {
|
|
201
|
-
const r = turnIn(q.id);
|
|
202
|
-
// …grant r.gold / r.xp / r.items through your wallet + inventory.
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return { text: root.text, options };
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## 4. Items, merchants, XP — the economy that closes
|
|
212
|
-
|
|
213
|
-
```ts
|
|
214
|
-
// items.ts
|
|
215
|
-
type ItemDef = {
|
|
216
|
-
id: string;
|
|
217
|
-
kind: "weapon" | "armor" | "potion" | "quest" | "tome";
|
|
218
|
-
name: string;
|
|
219
|
-
price: number; // what merchants charge — the SINK side of the economy
|
|
220
|
-
stats?: { dmg?: number; armor?: number; heal?: number };
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
export const ITEMS: ItemDef[] = [
|
|
224
|
-
{ id: "sword_iron", kind: "weapon", name: "Iron Sword", price: 120, stats: { dmg: 18 } },
|
|
225
|
-
{ id: "potion_health", kind: "potion", name: "Health Draught", price: 25, stats: { heal: 40 } },
|
|
226
|
-
{ id: "founder_amulet", kind: "quest", name: "Founder's Amulet", price: 0 },
|
|
227
|
-
// …the catalog. Price the first upgrade to land right after the first quest's gold.
|
|
228
|
-
];
|
|
229
|
-
|
|
230
|
-
// Merchant stock is per-NPC data — a second vendor is one more entry.
|
|
231
|
-
export const STOCKS: Record<string, string[]> = {
|
|
232
|
-
blacksmith: ["sword_iron", "armor_leather"],
|
|
233
|
-
herbalist: ["potion_health", "potion_mana"],
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// XP curve + level rewards: one growth axis, wired to quest/enemy rewards.
|
|
237
|
-
export const xpNext = (level: number): number => Math.round(100 * level ** 1.4);
|
|
238
|
-
export function addXp(player: { level: number; xp: number; maxHp: number; hp: number }, amount: number): void {
|
|
239
|
-
player.xp += amount;
|
|
240
|
-
while (player.xp >= xpNext(player.level)) {
|
|
241
|
-
player.xp -= xpNext(player.level);
|
|
242
|
-
player.level += 1;
|
|
243
|
-
player.maxHp += 12;
|
|
244
|
-
player.hp = player.maxHp; // level-up heals — a reward the player FEELS
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
## Seeded placement — bulk content for free
|
|
250
|
-
|
|
251
|
-
Scatter the non-authored bulk (chests, camps, flavor spawns) with a seeded
|
|
252
|
-
RNG so the world is deterministic across loads and machines:
|
|
253
|
-
|
|
254
|
-
```ts
|
|
255
|
-
export function seededRng(seed: number): () => number {
|
|
256
|
-
let s = seed >>> 0;
|
|
257
|
-
return () => {
|
|
258
|
-
s = (s * 1664525 + 1013904223) >>> 0;
|
|
259
|
-
return s / 0xffffffff;
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
// const rng = seededRng(1337): place 10 chests at rng()-driven offsets around
|
|
263
|
-
// locations, skip water/steep slopes via the world's height/biome lookups
|
|
264
|
-
// ($genex-threejs-open-world), and hand-place only the authored few.
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
Hand-authored where it matters (quest prose, boss mechanics, unique loot),
|
|
268
|
-
tables for the bulk, seeds for the scatter — that split is what lets one
|
|
269
|
-
session ship the whole contract.
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: genex-threejs-image-pipeline
|
|
3
|
-
description: Design the final image pipeline for Genex Three.js games. Use for render-target ownership, depth and normal signals, albedo/history buffers, pass ordering, post-processing composition, AO, bloom, exposure, tone mapping, grading, diagnostics, and avoiding conflicting render effects.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Genex Three.js Image Pipeline
|
|
7
|
-
|
|
8
|
-
Use this skill only when composing several image-space systems or defining shared buffers. For one effect, load its atomic skill instead.
|
|
9
|
-
|
|
10
|
-
Load:
|
|
11
|
-
|
|
12
|
-
- `$genex-threejs-screen-space-ambient-occlusion` for GTAO, bent normals, denoising, or AO application;
|
|
13
|
-
- `$genex-threejs-bloom` for HDR extraction and bloom;
|
|
14
|
-
- `$genex-threejs-exposure-color-grading` for metering, adaptation, tone mapping, LUTs, and output conversion.
|
|
15
|
-
|
|
16
|
-
The pipeline must expose its signals and ordering. Do not install a pile of effects and tune the final frame blindly.
|
|
17
|
-
|
|
18
|
-
## Signal order
|
|
19
|
-
|
|
20
|
-
```text
|
|
21
|
-
scene HDR color + depth + normals + albedo where required
|
|
22
|
-
→ lighting-related screen effects
|
|
23
|
-
→ atmosphere/transparency composition
|
|
24
|
-
→ bloom
|
|
25
|
-
→ exposure
|
|
26
|
-
→ tone mapping
|
|
27
|
-
→ grading
|
|
28
|
-
→ lens/presentation effects
|
|
29
|
-
→ output conversion
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Read [references/image-pipeline.md](references/image-pipeline.md)
|
|
33
|
-
for four production pass graphs, their buffer/resolution contracts, and the
|
|
34
|
-
ownership boundaries between whole-scene and effect-local graphs.
|
|
35
|
-
|
|
36
|
-
## Rules
|
|
37
|
-
|
|
38
|
-
- Tone-map once.
|
|
39
|
-
- Keep HDR bloom before tone mapping.
|
|
40
|
-
- Meter exposure from a small luminance target, not the final 8-bit screen.
|
|
41
|
-
- Separate direct and indirect light before applying bent-normal ambient tint when possible.
|
|
42
|
-
- Upsample low-resolution effects with depth/normal-aware weights.
|
|
43
|
-
- Build pass toggles and effect-only views before tuning.
|
|
44
|
-
- UI rendered in the same target needs an explicit protection strategy.
|
|
45
|
-
- Do not load all atomic post skills by default. Route only the effects actually requested.
|
|
46
|
-
- Budget the pipeline per device tier (`$genex-threejs-adaptive-quality`):
|
|
47
|
-
phone pixel budget ≈ 1,000,000 px at DPR ≤ 1.25–1.5, desktop ≈ 1,650,000 px —
|
|
48
|
-
every full-res pass target multiplies that cost, so phone tiers run the light
|
|
49
|
-
post level and per-pass resolution scales (0.4–0.5 DPR blurs) are the norm,
|
|
50
|
-
not an optimization.
|
|
51
|
-
|
|
52
|
-
## Routing boundary
|
|
53
|
-
|
|
54
|
-
Use this skill when multiple image-space systems must share buffers, ordering,
|
|
55
|
-
or output ownership. For one isolated effect, use its atomic skill without
|
|
56
|
-
loading this coordinator.
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# Production image-pipeline contracts
|
|
2
|
-
|
|
3
|
-
Use this reference to compose shared scene buffers, lighting effects, atmosphere, bloom, exposure, tone mapping, grading, and feature-local render targets with explicit ownership.
|
|
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. Either way the effect obeys the device tier (`$genex-threejs-adaptive-quality`): expensive passes are desktop-tier, and on WebGPU the per-target MSAA sample count is a runtime knob the governor may drive.
|
|
6
|
-
|
|
7
|
-
## Contents
|
|
8
|
-
|
|
9
|
-
- production WebGPU pipeline WebGPU graph
|
|
10
|
-
- selective gallery pipeline selective gallery graph
|
|
11
|
-
- atlas-based renderer composer graph
|
|
12
|
-
- Temporal-surface effect-local graph
|
|
13
|
-
- Buffer and ownership rules
|
|
14
|
-
- Resolution policies
|
|
15
|
-
- Failure analysis
|
|
16
|
-
- Diagnostics
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## production WebGPU pipeline WebGPU graph
|
|
20
|
-
|
|
21
|
-
When GTAO is enabled, the scene pass writes MRT:
|
|
22
|
-
|
|
23
|
-
```text
|
|
24
|
-
output HDR color
|
|
25
|
-
view-space normal
|
|
26
|
-
diffuse albedo
|
|
27
|
-
depth
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Graph:
|
|
31
|
-
|
|
32
|
-
```text
|
|
33
|
-
scene MRT
|
|
34
|
-
-> reduced GTAO + bent normal
|
|
35
|
-
-> full-resolution bilateral/lighting composite
|
|
36
|
-
-> atmosphere
|
|
37
|
-
-> bloom
|
|
38
|
-
-> adapted exposure
|
|
39
|
-
-> renderOutput / tone map
|
|
40
|
-
-> 3D LUT
|
|
41
|
-
-> FXAA
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Near/far values, environment intensity, and environment texture remain
|
|
45
|
-
updateable inputs. AO is applied through its dedicated composite rather than
|
|
46
|
-
blindly multiplying the final image.
|
|
47
|
-
|
|
48
|
-
The atmosphere pass reconstructs view/world position from depth,
|
|
49
|
-
classifies sky, and owns aerial haze, height fog, sun disc/shaft, lens flare,
|
|
50
|
-
and distance grading. Its optional post-process cloud shadow is explicitly
|
|
51
|
-
disabled in its configuration, avoiding duplicate ownership with material
|
|
52
|
-
lighting.
|
|
53
|
-
|
|
54
|
-
## selective gallery pipeline selective gallery graph
|
|
55
|
-
|
|
56
|
-
The gallery owns three composers:
|
|
57
|
-
|
|
58
|
-
```text
|
|
59
|
-
neon selective bloom
|
|
60
|
-
chandelier selective bloom
|
|
61
|
-
base + final additive composite + OutputPass
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Selective renders use layer membership plus temporary black material
|
|
65
|
-
substitution with `try/finally` restoration. CSS3D content is rendered by a
|
|
66
|
-
separate renderer after WebGL when invalidated.
|
|
67
|
-
|
|
68
|
-
Shadows use VSM and manual invalidation:
|
|
69
|
-
|
|
70
|
-
```text
|
|
71
|
-
shadowMap.autoUpdate = false
|
|
72
|
-
shadowMap.needsUpdate = true only after relevant scene/light changes
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
This is a bounded-scene optimization. It depends on every moving caster and
|
|
76
|
-
light correctly invalidating the cache.
|
|
77
|
-
|
|
78
|
-
## atlas-based renderer composer graph
|
|
79
|
-
|
|
80
|
-
atlas-based renderer performs a separate depth prepass into a depth-stencil target before
|
|
81
|
-
the composer:
|
|
82
|
-
|
|
83
|
-
```text
|
|
84
|
-
depth prepass target
|
|
85
|
-
main render
|
|
86
|
-
SSAO
|
|
87
|
-
volumetric lighting
|
|
88
|
-
bloom
|
|
89
|
-
lens flare
|
|
90
|
-
fog/color grading
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
The depth target uses nearest filtering and a
|
|
94
|
-
`DepthStencilFormat`/`UnsignedInt248Type` depth texture. Every depth consumer
|
|
95
|
-
receives that same texture.
|
|
96
|
-
|
|
97
|
-
The composer recalculates effective pixel dimensions from renderer pixel ratio
|
|
98
|
-
and resizes the depth target and all passes together.
|
|
99
|
-
|
|
100
|
-
The composer can exist alongside another application post path. Verify the
|
|
101
|
-
actual render-loop call path before claiming that this graph owns runtime
|
|
102
|
-
output.
|
|
103
|
-
|
|
104
|
-
## Temporal-surface effect-local graph
|
|
105
|
-
|
|
106
|
-
The temporal frost graph is not a whole-scene post stack. It is a
|
|
107
|
-
self-contained material effect:
|
|
108
|
-
|
|
109
|
-
```text
|
|
110
|
-
scene at full resolution
|
|
111
|
-
-> vertical blur at 0.4 DPR
|
|
112
|
-
-> horizontal blur at 0.4 DPR
|
|
113
|
-
-> frost composite at full resolution
|
|
114
|
-
-> pointer history write/swap at full resolution
|
|
115
|
-
-> final normal/refraction output
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Three static procedural texture targets render once. This is a feature-local
|
|
119
|
-
example of mixing persistent, static, low-resolution, and full-resolution
|
|
120
|
-
signals in one feature.
|
|
121
|
-
|
|
122
|
-
## Buffer and ownership rules
|
|
123
|
-
|
|
124
|
-
Before implementation, write:
|
|
125
|
-
|
|
126
|
-
| Signal | Producer | Consumers | Space/format | Resolution | History |
|
|
127
|
-
| --- | --- | --- | --- | --- | --- |
|
|
128
|
-
| HDR scene | scene pass | AO/atmosphere/bloom | linear HDR | full | no |
|
|
129
|
-
| depth | scene or prepass | AO/fog/flare | renderer-defined | full | no |
|
|
130
|
-
| normal | MRT/geometry | AO composite | view space | full | no |
|
|
131
|
-
| albedo | MRT | indirect composite | linear | full | no |
|
|
132
|
-
| bloom contributions | selective passes | final composite | HDR | full/pyramid | no |
|
|
133
|
-
| exposure | meter | final color | scalar | 64x36 source | adapted |
|
|
134
|
-
| interaction | ping-pong pass | frost/output | half-float | full | yes |
|
|
135
|
-
|
|
136
|
-
Every signal has one producer. If a scene pass already owns depth and normal,
|
|
137
|
-
do not add an uncoordinated duplicate prepass without measuring the reason.
|
|
138
|
-
|
|
139
|
-
## Resolution policies
|
|
140
|
-
|
|
141
|
-
selective gallery pipeline caps DPR from both device and pixel budget:
|
|
142
|
-
|
|
143
|
-
```text
|
|
144
|
-
mobile budget = 1,000,000 pixels, max DPR 1.25
|
|
145
|
-
desktop budget = 1,650,000 pixels, max DPR 1.5
|
|
146
|
-
minimum DPR = 1
|
|
147
|
-
budget DPR = sqrt(pixelBudget / CSS pixel count)
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
All composers receive the same selected DPR and CSS size.
|
|
151
|
-
|
|
152
|
-
The temporal frost graph instead gives individual passes fixed roles:
|
|
153
|
-
|
|
154
|
-
```text
|
|
155
|
-
blur and coarse noise = 0.4 DPR
|
|
156
|
-
composite, history, output = display DPR
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Choose global DPR budgeting for scene cost and per-pass scaling for effect
|
|
160
|
-
bandwidth. They solve different problems.
|
|
161
|
-
|
|
162
|
-
## Failure analysis
|
|
163
|
-
|
|
164
|
-
- production WebGPU pipeline API names are version-sensitive; `PostProcessing` was renamed
|
|
165
|
-
and deprecated in favor of `RenderPipeline` in current Three.js history.
|
|
166
|
-
- selective gallery pipeline selective bloom renders the scene multiple times.
|
|
167
|
-
- selective gallery pipeline manual shadow invalidation can freeze unregistered motion.
|
|
168
|
-
- atlas-based renderer depth prepass renders regular scene materials, not an explicit depth
|
|
169
|
-
override; verify transparent and alpha-tested behavior.
|
|
170
|
-
- atlas-based renderer composer may not be the active runtime path.
|
|
171
|
-
- The frost blur has a zero-weight division risk and pointer decay is frame
|
|
172
|
-
based.
|
|
173
|
-
- None of these graphs provides a complete velocity/motion-vector
|
|
174
|
-
contract for general temporal effects.
|
|
175
|
-
- Do not advertise velocity ownership or TAA merely because a generic pipeline
|
|
176
|
-
could include them.
|
|
177
|
-
|
|
178
|
-
## Diagnostics
|
|
179
|
-
|
|
180
|
-
Expose a graph inspector or equivalent stable views:
|
|
181
|
-
|
|
182
|
-
```text
|
|
183
|
-
scene HDR
|
|
184
|
-
depth raw and reconstructed
|
|
185
|
-
normal/albedo MRT
|
|
186
|
-
GTAO and bent normal
|
|
187
|
-
atmosphere only
|
|
188
|
-
each selective bloom contribution
|
|
189
|
-
exposure meter and current exposure
|
|
190
|
-
pre/post tone map and LUT
|
|
191
|
-
frost static/history/composite targets
|
|
192
|
-
pass resolution, format, memory, and GPU time
|
|
193
|
-
manual invalidation state
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
The pipeline is accepted only when every enabled pass has a named input,
|
|
197
|
-
output, owner, resolution, and disable path.
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: genex-threejs-lighting-design
|
|
3
|
-
description: Light Genex Three.js games from visible causes, and decide WHERE light belongs — walk the scene's sources one by one, the way the visual-direction gate walks surfaces and the moment gate walks moments. Use for light rigs, sun/moon key lights, practical lights (campfire, torch, neon, lava), emissive-to-light coupling, light shafts and visible beams, fog mood, gameplay light signals, flicker, light budgets, and scenes that read flat or uniformly lit.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Genex Three.js Lighting Design
|
|
7
|
-
|
|
8
|
-
Light from causes — every light in the frame is the visible consequence of
|
|
9
|
-
something the player can point at. Avoid the uniform ambient wash that lights
|
|
10
|
-
a cave like an office.
|
|
11
|
-
|
|
12
|
-
## The source gate — run this BEFORE writing any lighting code
|
|
13
|
-
|
|
14
|
-
The visual-direction gate walks **surfaces**; the moment gate walks
|
|
15
|
-
**moments**. Light lives on neither — it lives on **sources**: what the sky
|
|
16
|
-
pours in and what the fiction burns, glows, or screens. Walk the sources the
|
|
17
|
-
same way, and decide each one.
|
|
18
|
-
|
|
19
|
-
List the sources the scene contract already names — never invent them. Three
|
|
20
|
-
places to look:
|
|
21
|
-
|
|
22
|
-
- **the sky** — day, night, overcast, underground, deep space? That one answer
|
|
23
|
-
sets the rig: a warm sun key with cool sky fill, a dim hard moon, or no sky
|
|
24
|
-
at all — a cave is keyed by its practicals;
|
|
25
|
-
- **the fiction's emitters** — campfire, torch, lava, neon sign, a monitor,
|
|
26
|
-
headlights, a portal, a crack of daylight in the roof;
|
|
27
|
-
- **the gameplay signals** — the objective beacon, the telegraph, the
|
|
28
|
-
checkpoint. Light is information the player navigates by, and it is the
|
|
29
|
-
cheapest wayfinding you own.
|
|
30
|
-
|
|
31
|
-
When gameplay needs light where the fiction names no source, add the cause
|
|
32
|
-
first — a lamp prop, embers, a fissure — and then light it; the light alone is
|
|
33
|
-
still a bug. Then judge each source:
|
|
34
|
-
|
|
35
|
-
> **Does it change what the player sees AROUND it — on the ground, the walls,
|
|
36
|
-
> the player — or does it only need to be seen itself?**
|
|
37
|
-
|
|
38
|
-
Four verdicts, all legitimate, one forbidden:
|
|
39
|
-
|
|
40
|
-
- **Seen itself** — an emissive material, no light object. Most sources land
|
|
41
|
-
here, and that is what keeps the frame cheap; how much it glows is
|
|
42
|
-
`$genex-threejs-bloom`'s question.
|
|
43
|
-
- **Lights its surroundings** — a real `PointLight` or `SpotLight`, coupled to
|
|
44
|
-
its emitter (below). A campfire that doesn't paint the ground orange is a
|
|
45
|
-
prop, not a fire.
|
|
46
|
-
- **Shapes the whole frame** — the key: a sun or moon `DirectionalLight`, the
|
|
47
|
-
arena floods. One or two per scene, and they own the shadows.
|
|
48
|
-
- **The air itself is lit** — the beam is the point: the cave crack, a dusty
|
|
49
|
-
window shaft, canopy rays. Build the shaft as geometry first — fog and
|
|
50
|
-
volumetric passes are scene-wide decisions, never per-beam tools (recipes
|
|
51
|
-
in the reference).
|
|
52
|
-
- **Not deciding — the only wrong answer.** One white `AmbientLight` over
|
|
53
|
-
everything is not a rig; it is the unlit look with extra steps, and it is
|
|
54
|
-
why shipped scenes read the same at noon and at midnight.
|
|
55
|
-
|
|
56
|
-
"The neon is emissive-only — it doesn't reach the street" is a real answer;
|
|
57
|
-
say it in one line and move on. Then check the inverse: **a light with no
|
|
58
|
-
visible cause reads as a bug**, not mood — the player asks why the floor
|
|
59
|
-
glows. And when the walk returns more real lights than the scene can afford,
|
|
60
|
-
demote the dimmest back to emissive-only: dynamic lights are the scarcest
|
|
61
|
-
resource in the frame, and the eye forgives an unlit distant torch far sooner
|
|
62
|
-
than a dropped frame.
|
|
63
|
-
|
|
64
|
-
## A practical is coupled, not placed
|
|
65
|
-
|
|
66
|
-
The light a source throws and the mesh that emits it are one thing. Drive both
|
|
67
|
-
from one envelope — separate flickers, or a light hovering near an unlit prop,
|
|
68
|
-
read instantly as fake:
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
// Values assume a locked renderer baseline — relationships, not mandates.
|
|
72
|
-
const fire = new THREE.PointLight(0xff9142, 14, 18, 2); // range-capped, physical falloff
|
|
73
|
-
fire.position.set(0, 0.9, 0); // inside the flame, above the fuel
|
|
74
|
-
campfire.add(fire);
|
|
75
|
-
const flameMat = flame.material as THREE.MeshStandardMaterial;
|
|
76
|
-
flameMat.emissive.set(0xff7a1e);
|
|
77
|
-
|
|
78
|
-
function flicker(t: number): number {
|
|
79
|
-
// two incommensurate sines — coherent, frame-rate independent
|
|
80
|
-
return 0.82 + 0.12 * Math.sin(t * 11.3) + 0.06 * Math.sin(t * 23.7 + 1.7);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function updateFire(elapsed: number): void {
|
|
84
|
-
const e = flicker(elapsed);
|
|
85
|
-
fire.intensity = 14 * e;
|
|
86
|
-
flameMat.emissiveIntensity = 2.4 * e; // mesh and light breathe together
|
|
87
|
-
fire.position.x = 0.05 * Math.sin(elapsed * 7.1); // the pool sways with the flame
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
The same couple serves a neon sign that hums, a monitor whose picture spills
|
|
92
|
-
onto the desk, a muzzle flash (one envelope drives light, emissive, and the
|
|
93
|
-
sound cue). And when `npx genex skybox` has set `scene.environment`, that
|
|
94
|
-
image IS the ambient fill — do not stack an `AmbientLight` on top of it.
|
|
95
|
-
|
|
96
|
-
Read [references/light-recipes.md](references/light-recipes.md) for rig
|
|
97
|
-
baselines per environment (day, night, interior, cave, space), the shaft and
|
|
98
|
-
fog recipes, light-as-information patterns, budget relationships, and the
|
|
99
|
-
kill-switch diagnostic.
|
|
100
|
-
|
|
101
|
-
## Rules
|
|
102
|
-
|
|
103
|
-
- Every light names its cause; a light nothing explains is a bug, not mood.
|
|
104
|
-
- **The light population is fixed at scene build — never add, remove, or hide
|
|
105
|
-
a light (or a group containing one) during play.** Three.js counts lights
|
|
106
|
-
into every lit material's shader; a changed count recompiles ALL of them
|
|
107
|
-
synchronously — a multi-second freeze on the exact frame a pickup, door, or
|
|
108
|
-
death "removes" a light (a real pilot game froze ~3 s per relic collected
|
|
109
|
-
this way). Turn a light off by driving `intensity` to 0 and leave it in the
|
|
110
|
-
graph; per-level swaps rebuild the whole scene anyway, which is the one
|
|
111
|
-
legitimate time the population changes.
|
|
112
|
-
- Couple a practical to its emitter: one color, one envelope, one on/off state.
|
|
113
|
-
- The key owns shadows; practicals cast none until a shot proves they must.
|
|
114
|
-
- **A shadow-casting practical that never moves gets a FROZEN map**: a
|
|
115
|
-
PointLight shadow is a 6-face cube render, re-drawn every frame by default —
|
|
116
|
-
four static lanterns cost 24 shadow passes/frame over your densest props for
|
|
117
|
-
zero visual change (a pilot game submitted 66M triangles/frame this way).
|
|
118
|
-
Set `light.shadow.autoUpdate = false; light.shadow.needsUpdate = true;` so
|
|
119
|
-
the cube renders once; keep only the key (which shadows the movers) dynamic,
|
|
120
|
-
and budget frozen practicals at ≤1024 map size.
|
|
121
|
-
- Tune intensities only after the renderer baseline is locked — retuning the
|
|
122
|
-
whole rig after a tone-mapping change is self-inflicted.
|
|
123
|
-
- Never repair unbalanced light ratios with exposure — fix the lights.
|
|
124
|
-
- Flicker from elapsed time, never per-frame randomness.
|
|
125
|
-
- Dispose lights and their shadow maps with the level that spawned them — and
|
|
126
|
-
the rest of the level with them: traverse the outgoing scene and dispose
|
|
127
|
-
geometry, materials, AND textures separately (three frees nothing; material
|
|
128
|
-
dispose does not touch textures). Leaked levels march phones toward the
|
|
129
|
-
OS memory kill — `$genex-threejs-adaptive-quality` owns the budget watch.
|
|
130
|
-
- The rig must read with post off: time of day and where-to-go, before bloom.
|
|
131
|
-
|
|
132
|
-
## Routing boundary
|
|
133
|
-
|
|
134
|
-
Tone mapping, exposure, and grading are
|
|
135
|
-
`$genex-threejs-exposure-color-grading`. How much an emissive glows is
|
|
136
|
-
`$genex-threejs-bloom`; whether it also illuminates is this skill. Authoring
|
|
137
|
-
the emissive surface itself — lava, hot rock, a screen's picture — is
|
|
138
|
-
`$genex-threejs-procedural-materials`. `$genex-threejs-shadow-systems` owns
|
|
139
|
-
large roaming-world shadows (cascades, clipmaps) — a bounded scene's one
|
|
140
|
-
shadow-casting key with a tight frustum lives here. Sky scattering, haze, and the sun disc are
|
|
141
|
-
`$genex-threejs-atmosphere-aerial-perspective`; shafts inside clouds are
|
|
142
|
-
`$genex-threejs-volumetric-clouds`; ordering a volumetric pass among other
|
|
143
|
-
post effects is `$genex-threejs-image-pipeline`; the environment map that
|
|
144
|
-
feeds the fill is `$genex-ai-skybox`.
|