arkaos 5.5.0 → 5.6.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.
Files changed (46) hide show
  1. package/README.md +2 -2
  2. package/THE-ARKAOS-GUIDE.md +1 -1
  3. package/VERSION +1 -1
  4. package/arka/SKILL.md +1 -1
  5. package/config/skills-provenance.yaml +9 -0
  6. package/departments/dev/skills/canvas-generative/SKILL.md +3 -3
  7. package/departments/dev/skills/threejs/SKILL.md +173 -0
  8. package/departments/dev/skills/threejs/references/genjutsu.LICENSE +21 -0
  9. package/departments/dev/skills/threejs/references/r3f.md +249 -0
  10. package/departments/dev/skills/threejs/references/scene-setup.md +404 -0
  11. package/departments/dev/skills/threejs/references/shaders.md +452 -0
  12. package/harness/codex/AGENTS.md +1 -1
  13. package/harness/copilot/copilot-instructions.md +1 -1
  14. package/harness/cursor/rules/arkaos.mdc +2 -2
  15. package/harness/gemini/GEMINI.md +1 -1
  16. package/harness/opencode/AGENTS.md +1 -1
  17. package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
  18. package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
  19. package/harness/opencode/agents/arka-cfo-helena.md +1 -1
  20. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
  21. package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
  22. package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
  23. package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
  24. package/harness/opencode/agents/arka-coo-sofia.md +1 -1
  25. package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
  26. package/harness/opencode/agents/arka-cqo-marta.md +1 -1
  27. package/harness/opencode/agents/arka-cto-marco.md +1 -1
  28. package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
  29. package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
  30. package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
  31. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
  32. package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
  33. package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
  34. package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
  35. package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
  36. package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
  37. package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
  38. package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
  39. package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
  40. package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
  41. package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
  42. package/harness/zed/.rules +1 -1
  43. package/knowledge/commands-registry.json +1 -1
  44. package/knowledge/skills-manifest.json +16 -1
  45. package/package.json +1 -1
  46. package/pyproject.toml +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **The Operating System for AI Agent Teams.**
6
6
 
7
- 89 agents. 17 departments. 337 skills. Enterprise frameworks. Multi-runtime. One install.
7
+ 89 agents. 17 departments. 338 skills. Enterprise frameworks. Multi-runtime. One install.
8
8
 
9
9
  ```bash
10
10
  npx arkaos install
@@ -97,7 +97,7 @@ npx arkaos doctor # Health check
97
97
 
98
98
  ### Skill packs, à la carte
99
99
 
100
- The default install ships a curated core so your context window stays lean. Everything else lives in the ArkaOS plugin marketplace: 16 department packs with 263 skills, generated straight from the same sources the core uses. Inside Claude Code:
100
+ The default install ships a curated core so your context window stays lean. Everything else lives in the ArkaOS plugin marketplace: 16 department packs with 264 skills, generated straight from the same sources the core uses. Inside Claude Code:
101
101
 
102
102
  ```
103
103
  /plugin marketplace add andreagroferreira/arka-os
@@ -1,6 +1,6 @@
1
1
  # The ArkaOS Guide
2
2
 
3
- > v5.5.0 — 89 agents, 17 departments, 337 skills, 299 commands, 20 ADRs.
3
+ > v5.6.0 — 89 agents, 17 departments, 338 skills, 299 commands, 20 ADRs.
4
4
  > One file, everything you need to start. Generated by `scripts/guide_gen.py` — never hand-edited.
5
5
 
6
6
  ## What it is
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.5.0
1
+ 5.6.0
package/arka/SKILL.md CHANGED
@@ -63,7 +63,7 @@ complementary, not alternatives.
63
63
  # ArkaOS — Main Orchestrator
64
64
 
65
65
  > **The Operating System for AI Agent Teams**
66
- > 89 agents. 17 departments. 337 skills. Multi-runtime. Dashboard. Knowledge RAG.
66
+ > 89 agents. 17 departments. 338 skills. Multi-runtime. Dashboard. Knowledge RAG.
67
67
 
68
68
  ## ⛔ Evidence flow — 4 gates (NON-NEGOTIABLE)
69
69
 
@@ -139,6 +139,15 @@ derived:
139
139
  origin: community
140
140
  source: https://github.com/AThevon/genjutsu
141
141
  license: MIT
142
+ # Animation/design absorption campaign PR-4 — dev/threejs. Dual-source:
143
+ # the SKILL.md core is ArkaOS-authored from the three.js project's own
144
+ # LLM guidance (docs/llms.txt, MIT — recorded in THIRD-PARTY-NOTICES);
145
+ # references/r3f.md, scene-setup.md and shaders.md derive from
146
+ # AThevon/genjutsu's threejs-r3f skill (MIT, primary source here).
147
+ departments/dev/skills/threejs:
148
+ origin: community
149
+ source: https://github.com/AThevon/genjutsu
150
+ license: MIT
142
151
  # Marketing skills integration campaign — derived from
143
152
  # coreyhaines31/marketingskills (MIT). Frontmatter metadata must match
144
153
  # each entry field-for-field (test_registry_matches_frontmatter_field_for_field).
@@ -8,8 +8,8 @@ description: >
8
8
  TRIGGER: "generative art", "arte generativa", "particles background",
9
9
  "fundo de partículas", "noise field", "flow field", "canvas animation",
10
10
  "fundo processual", "creative coding em canvas", "/dev canvas-generative".
11
- SKIP: real-time 3D or shader work -> no dedicated skill yet, use the
12
- frontend workflows; a pre-rendered 3D world scrubbed by scroll ->
11
+ SKIP: real-time 3D or shader work -> dev/threejs; a pre-rendered 3D
12
+ world scrubbed by scroll ->
13
13
  dev/scroll-world; turning an existing video into a scroll site ->
14
14
  dev/animated-website; charts and data graphics -> the dataviz skill.
15
15
  allowed-tools: [Read, Write, Edit, Bash, Grep, Glob]
@@ -352,7 +352,7 @@ function update() {
352
352
  |---|---|
353
353
  | Noise implementations, particle pool, flow field, L-system, attractors | `references/algorithms.md` |
354
354
  | Timing and easing for animated transitions | the brand guidelines' Motion System (§06), reached via the source-of-truth table in `departments/brand/references/uiux-knowledge-and-tools.md` |
355
- | 3D generative (shaders, GPU particles) | out of this skill's scope — Canvas 2D only; scope a WebGL approach with the frontend squad |
355
+ | 3D generative (shaders, GPU particles) | `departments/dev/skills/threejs/SKILL.md` |
356
356
 
357
357
  ## Output
358
358
 
@@ -0,0 +1,173 @@
1
+ ---
2
+ name: dev/threejs
3
+ description: >
4
+ Real-time 3D in the browser with three.js and React Three Fiber: modern
5
+ setup (import maps, WebGL vs WebGPU renderer choice, TSL), the
6
+ scene-camera-renderer wiring, geometries, materials and lights, loaders,
7
+ disposal discipline against GPU memory leaks, performance budgets — and
8
+ the R3F layer (hooks, drei, postprocessing) for React projects.
9
+ TRIGGER: "three.js", "threejs", "react three fiber", "r3f", "webgl",
10
+ "webgpu", "shader", "GLSL", "cena 3D", "3D interativo no browser",
11
+ "drei", "/dev threejs".
12
+ SKIP: a pre-rendered 3D world scrubbed by scroll (no real-time engine)
13
+ -> dev/scroll-world; generative 2D canvas -> dev/canvas-generative;
14
+ generating a 3D asset (GLB mesh) from an image -> the higgsfield-generate
15
+ skill; React motion without 3D -> dev/framer-motion.
16
+ allowed-tools: [Read, Write, Edit, Bash, Grep, Glob]
17
+ metadata:
18
+ origin: community
19
+ source: https://github.com/AThevon/genjutsu
20
+ license: MIT
21
+ ---
22
+
23
+ <!-- arka:kb-first-prefix begin -->
24
+ > **KB-first:** query `mcp__obsidian__search_notes` (and
25
+ > `mcp__graphify__query_graph` when configured) and cite `[[wikilinks]]`
26
+ > or graph nodes — or declare the gap — BEFORE any external research.
27
+ > Full doctrine: `arka/SKILL.md` (KB-First Research).
28
+ <!-- arka:kb-first-prefix end -->
29
+
30
+ # Three.js — `/dev threejs`
31
+
32
+ > **Agent:** Diana (Frontend Dev) | **Framework:** three.js (MIT) + React Three Fiber
33
+ > Core authored from the three.js project's own LLM guidance
34
+ > (`docs/llms.txt`, MIT); the R3F layer derives from genjutsu (MIT) — see
35
+ > `references/`.
36
+
37
+ ## Setup — the modern shape
38
+
39
+ Import maps, never legacy CDN script tags. Pin one version for `three`
40
+ and `three/addons/` together — mixing versions across the two specifiers
41
+ is the classic silent breaker:
42
+
43
+ ```html
44
+ <script type="importmap">
45
+ { "imports": {
46
+ "three": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js",
47
+ "three/webgpu": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.webgpu.js",
48
+ "three/tsl": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.tsl.js",
49
+ "three/addons/": "https://cdn.jsdelivr.net/npm/three@<version>/examples/jsm/" } }
50
+ </script>
51
+ ```
52
+
53
+ `WebGPURenderer` is not exported from `three` — it lives in the
54
+ `three/webgpu` entry, and it is async: `await renderer.init()` before the
55
+ first frame. The TSL node functions live in `three/tsl`, not in
56
+ `three/webgpu`. Omit either entry and the import fails at runtime.
57
+
58
+ In a bundled project, `npm i three` and import the same specifiers. Check
59
+ the installed version before writing code — the API moves, and the
60
+ project's lockfile outranks your memory of it.
61
+
62
+ **Renderer choice.** `WebGLRenderer` is the default and safe everywhere.
63
+ `WebGPURenderer` (with TSL node materials) is the forward path but needs
64
+ feature detection and a WebGL fallback; choose it only when the project
65
+ targets browsers you control or ships the fallback. Never mix GLSL
66
+ `ShaderMaterial` into a WebGPU pipeline — that renderer wants TSL
67
+ `NodeMaterial` classes.
68
+
69
+ ## The irreducible core
70
+
71
+ Scene, camera, renderer, loop — everything else hangs off these:
72
+
73
+ ```js
74
+ const scene = new THREE.Scene();
75
+ const camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 100);
76
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
77
+ renderer.setSize(w, h);
78
+ renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); // cap DPR: 3x buys nothing visible and burns fill rate
79
+ renderer.setAnimationLoop(tick); // not a hand-rolled rAF: in XR it swaps to the session's loop; outside XR it is rAF with correct teardown via setAnimationLoop(null)
80
+ ```
81
+
82
+ On resize: update `camera.aspect`, call `camera.updateProjectionMatrix()`,
83
+ then `renderer.setSize`. Forgetting the middle step is why "resize
84
+ stretches everything".
85
+
86
+ **Animate with elapsed time, not the frame count.** A per-frame delta
87
+ keeps speed identical at 30 and 144 fps; per-frame increments do not.
88
+ Use `THREE.Timer` (`timer.update(); timer.getDelta()`) — `THREE.Clock`
89
+ is deprecated since r183 and warns at runtime.
90
+
91
+ ## Disposal — GPU memory does not garbage-collect
92
+
93
+ Removing a mesh from the scene frees nothing on the GPU. Every geometry,
94
+ material, texture and render target you create, you `dispose()` when its
95
+ life ends — and a material's textures are disposed separately from the
96
+ material. For a whole subtree, collect first, dispose once: `traverse`
97
+ visits Groups and Lights that have no material, and shared resources must
98
+ not be disposed per-mesh:
99
+
100
+ ```js
101
+ function disposeSubtree(root) {
102
+ const geometries = new Set(), materials = new Set(), textures = new Set();
103
+ root.traverse((obj) => {
104
+ if (!obj.isMesh && !obj.isPoints && !obj.isLine && !obj.isSprite) return;
105
+ // Sprites share one engine-owned quad geometry app-wide: never dispose it
106
+ if (obj.geometry && !obj.isSprite) geometries.add(obj.geometry);
107
+ const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
108
+ for (const m of mats) {
109
+ if (!m) continue;
110
+ materials.add(m);
111
+ for (const v of Object.values(m)) if (v?.isTexture) textures.add(v);
112
+ // ShaderMaterial textures live in uniforms, not on the material —
113
+ // and a uniform's value can be an ARRAY of textures (atlas shape)
114
+ if (m.uniforms) for (const u of Object.values(m.uniforms)) {
115
+ const vals = Array.isArray(u?.value) ? u.value : [u?.value];
116
+ for (const v of vals) if (v?.isTexture) textures.add(v);
117
+ }
118
+ }
119
+ });
120
+ textures.forEach((t) => t.dispose());
121
+ materials.forEach((m) => m.dispose());
122
+ geometries.forEach((g) => g.dispose());
123
+ }
124
+ ```
125
+
126
+ This covers the shapes meshes ordinarily carry — geometries, material
127
+ maps, scalar and array texture uniforms. When a resource is shared
128
+ with meshes OUTSIDE the subtree — a texture atlas, a cached material —
129
+ exclude it: ownership is yours to model, and dispose belongs to the owner.
130
+
131
+ A single-page app that mounts and unmounts scenes without this leaks
132
+ until the tab dies. This is the number-one three.js production bug.
133
+
134
+ ## Performance budget
135
+
136
+ - **Draw calls first.** Hundreds of meshes sharing one geometry+material
137
+ belong in an `InstancedMesh`; thousands of distinct static meshes merge
138
+ via `BufferGeometryUtils.mergeGeometries`.
139
+ - Reuse geometries and materials across meshes; clone only when a
140
+ property must diverge.
141
+ - Textures: compressed formats (KTX2/Basis) for anything big, and never
142
+ larger than the screen area they cover; mipmaps come from the loader or
143
+ `generateMipmaps`, with no power-of-two constraint in WebGL 2.
144
+ - Lights are per-fragment cost: prefer one directional + ambient or an
145
+ environment map over a constellation of point lights. Bake what does
146
+ not move.
147
+ - Shadows are a render pass per caster: tight `shadow.camera` frustum,
148
+ modest map sizes, `castShadow` only where it is visible.
149
+
150
+ ## Loading models
151
+
152
+ `GLTFLoader` (with `DRACOLoader` or `KTX2Loader` when the asset uses
153
+ them) from `three/addons/`. Load asynchronously, add a loading state,
154
+ and dispose the loader's intermediates. glTF is the format; converting
155
+ from anything else happens in the asset pipeline, not at runtime.
156
+
157
+ ## React Three Fiber
158
+
159
+ For React projects, R3F replaces the imperative wiring: the component
160
+ tree IS the scene graph, `useFrame` is the loop, drei supplies the
161
+ controls/loaders/staging you would otherwise hand-write. The full layer —
162
+ hooks discipline, drei picks, postprocessing, and the Do-Nots (from
163
+ setState in useFrame to forgotten dispose) — lives
164
+ in [references/r3f.md](references/r3f.md), with canvas/scene wiring in
165
+ [references/scene-setup.md](references/scene-setup.md) and GLSL/TSL
166
+ patterns in [references/shaders.md](references/shaders.md).
167
+
168
+ ## Output
169
+
170
+ A working scene module (or R3F component tree) with the render loop,
171
+ resize handling and disposal wired, a stated performance budget (draw
172
+ calls, texture memory, DPR cap), and the renderer choice justified in one
173
+ line. Anything WebGPU/TSL states its fallback.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adrien Thevon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,249 @@
1
+ > Derived from genjutsu's `threejs-r3f` skill (MIT — `genjutsu.LICENSE`
2
+ > in this directory). The React Three Fiber layer of `dev/threejs`.
3
+
4
+ # Three.js & React Three Fiber
5
+
6
+ > 3D on the web. Three.js is the engine, R3F is the React renderer.
7
+ > Concise rules here. Deep-dive in `references/`.
8
+
9
+ ---
10
+
11
+ ## When to Use What
12
+
13
+ | Need | Tool | Why |
14
+ |---|---|---|
15
+ | Full 3D scene (models, lights, physics) | R3F + drei | Declarative, React-friendly, ecosystem |
16
+ | Vanilla 3D (no React) | Three.js direct | Lighter, no React overhead |
17
+ | Simple 3D transforms on UI | CSS `transform3d` | GPU-composited, no WebGL context |
18
+ | 2D particles / generative | Canvas 2D | Simpler API, less GPU overhead |
19
+ | Shader-only visuals (no scene graph) | Raw WebGL / ShaderMaterial | Maximum control, minimal abstraction |
20
+
21
+ ---
22
+
23
+ ## Scene Setup Patterns
24
+
25
+ ```tsx
26
+ import { Canvas } from '@react-three/fiber'
27
+ import { Environment, OrbitControls } from '@react-three/drei'
28
+ import { Suspense } from 'react'
29
+
30
+ <Canvas camera={{ position: [0, 2, 5], fov: 45 }} dpr={[1, 2]} gl={{ antialias: true }}>
31
+ <Suspense fallback={null}>
32
+ <Environment preset="studio" />
33
+ <OrbitControls makeDefault />
34
+ <Scene />
35
+ </Suspense>
36
+ </Canvas>
37
+ ```
38
+
39
+ **Rules:**
40
+ - Always wrap scene content in `<Suspense>` -- loaders (GLTF, textures, HDRI) need it
41
+ - Set `dpr={[1, 2]}` to clamp pixel ratio (Retina without melting GPUs)
42
+ - Keep the Canvas parent component minimal -- re-renders propagate into the scene
43
+
44
+ ---
45
+
46
+ ## R3F Hooks
47
+
48
+ | Hook | Purpose | Gotcha |
49
+ |---|---|---|
50
+ | `useFrame((state, delta) => {})` | Per-frame logic (animation, physics) | Never setState inside |
51
+ | `useThree()` | Access gl, scene, camera, size, viewport, pointer | Destructure only what you need |
52
+ | `useLoader(TextureLoader, url)` | Load any Three.js resource | Wrap parent in Suspense |
53
+ | `useGraph(scene)` | Extract nodes/materials from loaded scene | Useful after useGLTF |
54
+
55
+ ### useFrame Tips
56
+
57
+ ```tsx
58
+ useFrame((state, delta) => {
59
+ // Use delta for framerate-independent animation
60
+ meshRef.current.rotation.y += delta * 0.5
61
+ // Access clock for time-based effects
62
+ material.uniforms.uTime.value = state.clock.elapsedTime
63
+ })
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Drei Essentials
69
+
70
+ | Component | Use Case |
71
+ |---|---|
72
+ | `Environment` | HDRI lighting (presets: studio, sunset, city, forest, dawn) |
73
+ | `Float` | Idle floating animation (speed, rotationIntensity, floatIntensity) |
74
+ | `Text3D` | Extruded 3D text (needs JSON font from Facetype.js) |
75
+ | `useGLTF` | Load .glb/.gltf models (returns { nodes, materials, scene }) |
76
+ | `useGLTF.preload(url)` | Preload model before component mounts |
77
+ | `MeshTransmissionMaterial` | Glass/crystal/liquid refraction effects |
78
+ | `PresentationControls` | Drag-to-rotate for product showcases |
79
+ | `Center` | Auto-center any group of meshes |
80
+ | `Detailed` | LOD -- swap geometry by camera distance |
81
+ | `useTexture` | Load textures with Suspense support |
82
+ | `Instances` | Declarative instancing for repeated meshes |
83
+
84
+ ---
85
+
86
+ ## Postprocessing
87
+
88
+ ```tsx
89
+ import { EffectComposer, Bloom, ChromaticAberration } from '@react-three/postprocessing'
90
+ import { BlendFunction } from 'postprocessing'
91
+
92
+ <EffectComposer>
93
+ <Bloom
94
+ luminanceThreshold={1}
95
+ luminanceSmoothing={0.4}
96
+ intensity={0.6}
97
+ />
98
+ <ChromaticAberration
99
+ blendFunction={BlendFunction.NORMAL}
100
+ offset={[0.002, 0.002]}
101
+ />
102
+ </EffectComposer>
103
+ ```
104
+
105
+ **Rules:**
106
+ - Bloom is selective by default -- lift material color/emissive above 1.0 to make it glow
107
+ - `luminanceThreshold={1}` = nothing glows unless explicitly emissive
108
+ - Order matters inside EffectComposer
109
+ - Effects are merged into a single pass (performant by design)
110
+
111
+ ---
112
+
113
+ ## Performance Patterns
114
+
115
+ | Pattern | When |
116
+ |---|---|
117
+ | `<Instances>` / `InstancedMesh` | 100+ identical meshes (particles, trees, crowds) |
118
+ | `<Detailed distances={[0, 50, 100]}>` | LOD: swap hi/lo models by distance |
119
+ | `dispose={null}` on `<primitive>` | Prevent auto-dispose when reusing shared geometry |
120
+ | `useGLTF` + Draco | Compress .glb models (70-90% size reduction) |
121
+ | `useKTX2` (drei) | Compressed GPU textures (1/4 VRAM) — `useTexture` is TextureLoader-only and cannot decode .ktx2 |
122
+ | `frameloop="demand"` on Canvas | Only render when something changes (static scenes) |
123
+ | `invalidate()` from useThree | Trigger a render in demand mode |
124
+ | `<Canvas eventSource={ref}>` | Delegate pointer events to an ancestor element (overlay UIs); rendering stays on the main thread |
125
+
126
+ **Target metrics:** < 100 draw calls, < 1M triangles, 60fps on mid-range GPU.
127
+ Use `stats-gl` or `r3f-perf` to monitor.
128
+
129
+ ---
130
+
131
+ ## Do Not
132
+
133
+ ### 1. Never setState in useFrame
134
+
135
+ Causes full React re-render 60x/second. Mutate refs directly.
136
+
137
+ ```tsx
138
+ // BAD
139
+ useFrame(() => {
140
+ setRotation(prev => prev + 0.01) // React re-render every frame
141
+ })
142
+
143
+ // GOOD
144
+ useFrame((_, delta) => {
145
+ meshRef.current.rotation.y += delta * 0.5 // Direct mutation, zero re-renders
146
+ })
147
+ ```
148
+
149
+ ### 2. Never allocate in the render loop
150
+
151
+ `new Vector3()` per frame = GC spikes = stutter.
152
+
153
+ ```tsx
154
+ // BAD
155
+ useFrame((state) => {
156
+ const target = new THREE.Vector3(0, Math.sin(state.clock.elapsedTime), 0)
157
+ meshRef.current.position.copy(target)
158
+ })
159
+
160
+ // GOOD
161
+ const _target = useMemo(() => new THREE.Vector3(), [])
162
+ useFrame((state) => {
163
+ _target.set(0, Math.sin(state.clock.elapsedTime), 0)
164
+ meshRef.current.position.copy(_target)
165
+ })
166
+ ```
167
+
168
+ ### 3. Never forget dispose (memory leak)
169
+
170
+ Three.js textures, geometries, and materials live on the GPU. Unmounting a React component does NOT free them.
171
+
172
+ ```tsx
173
+ // BAD -- texture stays in VRAM after unmount
174
+ const texture = useLoader(TextureLoader, '/big-texture.jpg')
175
+
176
+ // GOOD -- R3F auto-disposes objects mounted as JSX primitives.
177
+ // useLoader results are CACHED globally: disposing the texture directly
178
+ // poisons the cache and the next consumer renders a disposed resource.
179
+ // Evict from the cache instead, then dispose:
180
+ useEffect(() => {
181
+ return () => {
182
+ useLoader.clear(TextureLoader, '/big-texture.jpg')
183
+ texture.dispose()
184
+ }
185
+ }, [texture])
186
+
187
+ // Resources YOU constructed (not via useLoader) are yours to dispose:
188
+ useEffect(() => () => { geometry.dispose(); material.dispose() }, [])
189
+ ```
190
+
191
+ ### 4. Never re-render the Canvas parent
192
+
193
+ Parent state changes re-render the Canvas subtree — the Canvas does not
194
+ remount, but every child re-renders, props reconcile against the scene
195
+ graph, and R3F work you meant to keep out of React's render path runs
196
+ again. Keep fast-changing state out of the Canvas parent (zustand or
197
+ refs).
198
+
199
+ ```tsx
200
+ // BAD
201
+ function App() {
202
+ const [uiState, setUiState] = useState(false) // fast-changing state here re-renders the whole Canvas subtree
203
+ return (
204
+ <>
205
+ <button onClick={() => setUiState(!uiState)}>Toggle</button>
206
+ <Canvas><Scene config={uiState} /></Canvas>
207
+ </>
208
+ )
209
+ }
210
+
211
+ // GOOD -- isolate Canvas in its own component
212
+ function App() {
213
+ return (
214
+ <>
215
+ <UI />
216
+ <SceneCanvas />
217
+ </>
218
+ )
219
+ }
220
+ ```
221
+
222
+ ### 5. Never load assets without Suspense
223
+
224
+ Loaders (useGLTF, useTexture, useLoader) throw promises. Without Suspense, you get crashes.
225
+
226
+ ```tsx
227
+ // BAD
228
+ <Canvas>
229
+ <Model /> {/* useGLTF inside -- will throw */}
230
+ </Canvas>
231
+
232
+ // GOOD
233
+ <Canvas>
234
+ <Suspense fallback={<Loader />}>
235
+ <Model />
236
+ </Suspense>
237
+ </Canvas>
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Quick Reference: Loading Sub-resources
243
+
244
+ | Need | Load |
245
+ |---|---|
246
+ | Scene boilerplate, lighting rigs, controls | `scene-setup.md` (this directory) |
247
+ | Custom shaders, GLSL patterns, uniforms | `shaders.md` (this directory) |
248
+ | Animation principles, easing, timing | the brand guidelines' Motion System (§06), reached via `departments/brand/references/uiux-knowledge-and-tools.md` |
249
+ | Scroll-scrubbed pre-rendered worlds (no live engine) | `departments/dev/skills/scroll-world/SKILL.md` |