claude-code-arcane 2.9.8 → 2.9.9
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/CHANGELOG.md +14 -0
- package/agents/business/business-ops-lead.md +1 -14
- package/agents/game/gameplay-programmer.md +3 -4
- package/agents/game/technical-artist.md +3 -4
- package/dist/cli.js +41 -6
- package/docs/USER-GUIDE.md +1 -1
- package/docs/coordination-rules.md +1 -1
- package/docs/directory-structure.md +93 -122
- package/package.json +1 -1
- package/profiles/backend-go.yaml +1 -0
- package/rules/gamedev/engine-code.md +1 -1
- package/rules/gamedev/shader-code.md +1 -2
- package/rules/gamedev/vn-code.md +3 -2
- package/rules/implementation-workflow.md +1 -1
- package/rules/{ai-code.md → llm-code.md} +5 -5
- package/skills/agent-designer/README.md +2 -1
- package/skills/agent-designer/agent_evaluator.py +0 -5
- package/skills/ai-sdk-setup/SKILL.md +4 -4
- package/skills/architecture-decision/SKILL.md +3 -4
- package/skills/architecture-review/SKILL.md +1 -1
- package/skills/asset-audit/SKILL.md +0 -1
- package/skills/balance-check/SKILL.md +1 -1
- package/skills/brainstorm/SKILL.md +3 -3
- package/skills/brainstorm/references/next-steps-pipeline.md +8 -9
- package/skills/consistency-check/SKILL.md +2 -2
- package/skills/design-system/SKILL.md +1 -1
- package/skills/design-system/references/post-design-validation.md +2 -3
- package/skills/frontend-dev/SKILL.md +27 -28
- package/skills/frontend-dev/references/asset-prompt-guide.md +1 -1
- package/skills/frontend-dev/references/motion-recipes.md +1 -1
- package/skills/help/SKILL.md +1 -2
- package/skills/hotfix/SKILL.md +2 -2
- package/skills/milestone-review/SKILL.md +1 -1
- package/skills/patch-notes/SKILL.md +1 -1
- package/skills/playtest-report/SKILL.md +2 -3
- package/skills/playwright-pro/CLAUDE.md +2 -2
- package/skills/playwright-pro/SKILL.md +13 -9
- package/skills/playwright-pro/agents/migration-planner.md +2 -2
- package/skills/playwright-pro/agents/test-architect.md +1 -2
- package/skills/playwright-pro/agents/test-debugger.md +1 -1
- package/skills/playwright-pro/hooks/detect-playwright.sh +1 -1
- package/skills/playwright-pro/skills/browserstack/SKILL.md +5 -5
- package/skills/playwright-pro/skills/migrate/SKILL.md +1 -1
- package/skills/playwright-pro/skills/report/SKILL.md +2 -2
- package/skills/playwright-pro/skills/testrail/SKILL.md +5 -5
- package/skills/playwright-pro/templates/README.md +2 -2
- package/skills/prototype/SKILL.md +1 -1
- package/skills/quick-design/SKILL.md +1 -1
- package/skills/release-checklist/SKILL.md +0 -1
- package/skills/security-audit/SKILL.md +1 -1
- package/skills/self-improving-agent/CLAUDE.md +12 -12
- package/skills/self-improving-agent/SKILL.md +16 -12
- package/skills/self-improving-agent/agents/memory-analyst.md +1 -1
- package/skills/self-improving-agent/hooks/error-capture.sh +2 -2
- package/skills/self-improving-agent/reference/memory-architecture.md +2 -2
- package/skills/self-improving-agent/reference/promotion-rules.md +3 -3
- package/skills/self-improving-agent/reference/rules-directory-patterns.md +1 -1
- package/skills/self-improving-agent/settings.json +5 -5
- package/skills/self-improving-agent/skills/extract/SKILL.md +7 -7
- package/skills/self-improving-agent/skills/promote/SKILL.md +5 -5
- package/skills/self-improving-agent/skills/remember/SKILL.md +9 -9
- package/skills/self-improving-agent/skills/review/SKILL.md +7 -7
- package/skills/self-improving-agent/skills/status/SKILL.md +8 -8
- package/skills/senior-ml-engineer/references/llm_integration_guide.md +14 -31
- package/skills/senior-prompt-engineer/SKILL.md +10 -10
- package/skills/senior-prompt-engineer/references/prompt_engineering_patterns.md +1 -1
- package/skills/senior-prompt-engineer/scripts/agent_orchestrator.py +43 -40
- package/skills/senior-prompt-engineer/scripts/prompt_optimizer.py +23 -66
- package/skills/shader-dev/reference/fluid-simulation.md +4 -4
- package/skills/shader-dev/reference/simulation-physics.md +6 -6
- package/skills/shader-dev/reference/voxel-rendering.md +1 -1
- package/skills/shader-dev/techniques/ambient-occlusion.md +1 -1
- package/skills/shader-dev/techniques/analytic-ray-tracing.md +2 -2
- package/skills/shader-dev/techniques/domain-warping.md +2 -2
- package/skills/shader-dev/techniques/fluid-simulation.md +45 -45
- package/skills/shader-dev/techniques/multipass-buffer.md +24 -24
- package/skills/shader-dev/techniques/normal-estimation.md +1 -1
- package/skills/shader-dev/techniques/particle-system.md +16 -16
- package/skills/shader-dev/techniques/polar-uv-manipulation.md +1 -1
- package/skills/shader-dev/techniques/post-processing.md +7 -7
- package/skills/shader-dev/techniques/sdf-3d.md +1 -1
- package/skills/shader-dev/techniques/simulation-physics.md +24 -24
- package/skills/shader-dev/techniques/sound-synthesis.md +2 -2
- package/skills/shader-dev/techniques/voronoi-cellular-noise.md +1 -1
- package/skills/shader-dev/techniques/voxel-rendering.md +60 -60
- package/skills/smoke-check/SKILL.md +1 -1
- package/skills/sprint-plan/SKILL.md +1 -2
- package/skills/start/SKILL.md +7 -7
- package/skills/story-done/SKILL.md +1 -1
- package/skills/test-helpers/SKILL.md +7 -2
- package/skills/test-setup/SKILL.md +1 -1
- package/skills/ue-actor-component-architecture/SKILL.md +0 -1
- package/skills/ue-ai-navigation/SKILL.md +0 -1
- package/skills/ue-animation-system/SKILL.md +0 -1
- package/skills/ue-async-threading/SKILL.md +0 -1
- package/skills/ue-audio-system/SKILL.md +0 -1
- package/skills/ue-blueprints/SKILL.md +0 -1
- package/skills/ue-character-movement/SKILL.md +0 -1
- package/skills/ue-cpp-foundations/SKILL.md +0 -1
- package/skills/ue-data-assets-tables/SKILL.md +0 -1
- package/skills/ue-editor-tools/SKILL.md +0 -1
- package/skills/ue-game-features/SKILL.md +0 -1
- package/skills/ue-gameplay-abilities/SKILL.md +0 -1
- package/skills/ue-gameplay-framework/SKILL.md +0 -1
- package/skills/ue-input-system/SKILL.md +0 -1
- package/skills/ue-mass-entity/SKILL.md +0 -1
- package/skills/ue-materials-rendering/SKILL.md +0 -1
- package/skills/ue-module-build-system/SKILL.md +0 -1
- package/skills/ue-naming-conventions/SKILL.md +0 -1
- package/skills/ue-networking-replication/SKILL.md +0 -1
- package/skills/ue-niagara-effects/SKILL.md +0 -1
- package/skills/ue-packaging/SKILL.md +0 -1
- package/skills/ue-physics-collision/SKILL.md +0 -1
- package/skills/ue-procedural-generation/SKILL.md +0 -1
- package/skills/ue-project-context/SKILL.md +0 -1
- package/skills/ue-sequencer-cinematics/SKILL.md +0 -1
- package/skills/ue-serialization-savegames/SKILL.md +0 -1
- package/skills/ue-state-trees/SKILL.md +0 -1
- package/skills/ue-testing-debugging/SKILL.md +0 -1
- package/skills/ue-ui-umg-slate/SKILL.md +0 -1
- package/skills/ue-world-level-streaming/SKILL.md +0 -1
- package/skills/ux-review/SKILL.md +1 -1
- package/docs/RELEASE-SETUP.md +0 -99
- package/docs/presentations/arcane-overview-12.pptx +0 -0
- package/docs/presentations/arcane-overview.pptx +0 -0
- package/docs/presentations/build_arcane_deck.py +0 -310
- package/docs/presentations/build_arcane_deck_12.py +0 -399
- package/docs/trusted-publishing-setup.md +0 -163
|
@@ -13,23 +13,23 @@ const source = document.getElementById('fs').textContent.trim();
|
|
|
13
13
|
// ...
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
###
|
|
16
|
+
### Float Texture Compatibility (Most Critical Issue for Fluid Simulation)
|
|
17
17
|
|
|
18
18
|
Fluid simulation requires float textures to store velocity (can be negative), pressure, and ink concentration (can exceed 1.0).
|
|
19
19
|
|
|
20
|
-
**
|
|
20
|
+
**Must use RGBA16F instead of RGBA32F**: Many environments (headless Chrome, SwiftShader, mobile) do not support `RGBA32F` render targets. Even when the `EXT_color_buffer_float` extension claims to be available, `RGBA32F` FBOs may silently fail (framebuffer reports complete but renders all zeros or all ones). `RGBA16F + HALF_FLOAT` has far better compatibility than `RGBA32F`, and its precision is more than sufficient for fluid simulation.
|
|
21
21
|
|
|
22
22
|
```javascript
|
|
23
23
|
const gl = canvas.getContext("webgl2");
|
|
24
24
|
if (!gl) { /* error handling */ }
|
|
25
25
|
|
|
26
26
|
const ext = gl.getExtension("EXT_color_buffer_float");
|
|
27
|
-
//
|
|
27
|
+
// Continue even if ext is null — some environments support RGBA16F without this extension
|
|
28
28
|
|
|
29
29
|
function createFloatTexture(w, h) {
|
|
30
30
|
const tex = gl.createTexture();
|
|
31
31
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
32
|
-
//
|
|
32
|
+
// Must use RGBA16F + HALF_FLOAT, do NOT use RGBA32F + FLOAT
|
|
33
33
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, w, h, 0, gl.RGBA, gl.HALF_FLOAT, null);
|
|
34
34
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
35
35
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
@@ -57,7 +57,7 @@ function createFBO(w, h) {
|
|
|
57
57
|
Fluid simulation requires tracking mouse position and drag direction. iMouse uniform convention: **xy=current mouse position, z=mouse down flag (>0 means pressed), w=unused**. Mouse velocity is calculated from the position difference between current and previous frames:
|
|
58
58
|
|
|
59
59
|
```javascript
|
|
60
|
-
//
|
|
60
|
+
// iMouse convention: xy=current position, z=pressed flag (1.0=down, 0.0=up), w=0
|
|
61
61
|
// Mouse velocity is computed via prevMouse on the JS side, passed through a separate uniform
|
|
62
62
|
let iMouse = [0, 0, 0, 0]; // [x, y, pressed, 0]
|
|
63
63
|
let prevMouse = [0, 0];
|
|
@@ -93,7 +93,7 @@ canvas.addEventListener('mouseup', () => {
|
|
|
93
93
|
// Pass uniforms in render loop
|
|
94
94
|
// iMouse: xy=position, z=pressed flag, w=0
|
|
95
95
|
gl.uniform4f(uMouse, iMouse[0], iMouse[1], iMouse[2], 0.0);
|
|
96
|
-
//
|
|
96
|
+
// Mouse velocity must be clamped, otherwise fast dragging produces huge velocity deltas causing NaN explosion
|
|
97
97
|
const mvx = Math.max(-50, Math.min(50, iMouse[0] - prevMouse[0]));
|
|
98
98
|
const mvy = Math.max(-50, Math.min(50, iMouse[1] - prevMouse[1]));
|
|
99
99
|
gl.uniform2f(uMouseVel, mvx, mvy);
|
|
@@ -211,9 +211,9 @@ c.z -= DT * (dx.z * c.x + dy.z * c.y + div * c.z);
|
|
|
211
211
|
|
|
212
212
|
### Step 6: Mouse Interaction
|
|
213
213
|
```glsl
|
|
214
|
-
//
|
|
214
|
+
// iMouse.z is the mouse-down flag (>0=pressed), not a position coordinate
|
|
215
215
|
// iMouseVel is mouse movement velocity, passed via a separate uniform
|
|
216
|
-
//
|
|
216
|
+
// Must clamp mouseVel to prevent NaN explosion
|
|
217
217
|
if (iMouse.z > 0.0) {
|
|
218
218
|
vec2 mouseVel = clamp(iMouseVel, vec2(-50.0), vec2(50.0));
|
|
219
219
|
float dist2 = dot(p - iMouse.xy, p - iMouse.xy);
|
|
@@ -225,7 +225,7 @@ if (iMouse.z > 0.0) {
|
|
|
225
225
|
|
|
226
226
|
### Step 6b: Vorticity Confinement (Required for Ink Curling Effects)
|
|
227
227
|
```glsl
|
|
228
|
-
//
|
|
228
|
+
// Ink diffusion/swirl effects require vorticity confinement, otherwise small vortices dissipate quickly leaving only smooth flow
|
|
229
229
|
// Vorticity confinement re-injects energy into small-scale vortices, producing characteristic curling textures
|
|
230
230
|
#define VORT_STR 0.035 // [0.01=subtle, 0.05=noticeable, 0.1=strong]
|
|
231
231
|
float curl_c = dx.y - dy.x;
|
|
@@ -243,9 +243,9 @@ c.xy += DT * VORT_STR * vec2(eta.y, -eta.x) * curl_c;
|
|
|
243
243
|
|
|
244
244
|
### Step 7: Automatic Ink Sources (Critical: Ensures Visible Output Without Interaction)
|
|
245
245
|
```glsl
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
246
|
+
// Must have automatic ink sources! Otherwise the screen is completely black without mouse interaction
|
|
247
|
+
// Ink injection and decay must be balanced! Too-strong injection or too-weak decay causes ink saturation across the entire screen → solid color with no features
|
|
248
|
+
// Gaussian denominator controls emitter radius — larger denominator means larger emitter!
|
|
249
249
|
// Denominator > 300 makes emitter cover most of the screen, ink saturates quickly
|
|
250
250
|
// Recommended 100~200, keeping it locally concentrated with visible gradient falloff at distance
|
|
251
251
|
float t = iTime;
|
|
@@ -277,7 +277,7 @@ if (p.x < 1.0 || p.y < 1.0 ||
|
|
|
277
277
|
c.xyw *= 0.0;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
//
|
|
280
|
+
// Ink decay — must use multiplicative decay (e.g., *= 0.99), NOT subtractive decay (-= constant)
|
|
281
281
|
// Subtractive decay zeros out quickly at small ink values and decays too slowly at large values, causing saturation
|
|
282
282
|
// Multiplicative decay scales proportionally, maintaining contrast at any concentration
|
|
283
283
|
c.w *= 0.99; // 1% decay per frame, adjustable [0.98=fast dissipation, 0.995=persistent]
|
|
@@ -291,19 +291,19 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
291
291
|
vec2 uv = fragCoord / iResolution.xy;
|
|
292
292
|
vec4 c = texture(iChannel0, uv);
|
|
293
293
|
|
|
294
|
-
//
|
|
294
|
+
// Color base must be bright enough! 0.5+0.5*cos produces [0,1] range bright colors
|
|
295
295
|
// Never use vec3(0.02, 0.01, 0.08) or similar near-zero base colors — they become invisible when multiplied by ink
|
|
296
296
|
float angle = atan(c.y, c.x);
|
|
297
297
|
vec3 col = 0.5 + 0.5 * cos(angle + vec3(0.0, 2.1, 4.2));
|
|
298
298
|
|
|
299
|
-
//
|
|
299
|
+
// Use smoothstep to map ink concentration; upper limit should exceed actual ink range to preserve gradients
|
|
300
300
|
float ink = smoothstep(0.0, 2.0, c.w);
|
|
301
301
|
col *= ink;
|
|
302
302
|
|
|
303
303
|
// Pressure highlights
|
|
304
304
|
col += vec3(0.05) * clamp(c.z - 1.0, 0.0, 1.0);
|
|
305
305
|
|
|
306
|
-
//
|
|
306
|
+
// Background color must be visible (RGB at least > 5/255 ≈ 0.02), otherwise users think the page is all black
|
|
307
307
|
col = max(col, vec3(0.02, 0.012, 0.035));
|
|
308
308
|
|
|
309
309
|
fragColor = vec4(col, 1.0);
|
|
@@ -315,7 +315,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
315
315
|
Lava/fire requires FBM noise for turbulent textures + temperature color band mapping. **Key: Must use FBM noise to distort UV coordinates and temperature values, otherwise the image is too smooth and looks like a plain gradient rather than lava.**
|
|
316
316
|
|
|
317
317
|
```glsl
|
|
318
|
-
//
|
|
318
|
+
// FBM noise is the core of lava/fire visualization! Without it the image is a smooth gradient with no lava texture
|
|
319
319
|
// These noise functions must be defined in the Image Pass
|
|
320
320
|
|
|
321
321
|
float hash(vec2 p) {
|
|
@@ -333,7 +333,7 @@ float noise(vec2 p) {
|
|
|
333
333
|
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
-
//
|
|
336
|
+
// octaves=4~6 produces sufficient detail; fewer than 3 gives too-coarse textures
|
|
337
337
|
float fbm(vec2 p, int octaves) {
|
|
338
338
|
float val = 0.0;
|
|
339
339
|
float amp = 0.5;
|
|
@@ -360,7 +360,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
360
360
|
float speed = length(c.xy);
|
|
361
361
|
|
|
362
362
|
// Temperature = ink concentration + noise perturbation + speed contribution
|
|
363
|
-
//
|
|
363
|
+
// Noise perturbation amplitude of 0.2~0.4 produces visible texture without becoming noisy
|
|
364
364
|
float temp = ink * 0.7 + n1 * 0.25 + speed * 0.1;
|
|
365
365
|
// Second noise layer for cracks/dark veins
|
|
366
366
|
temp -= (1.0 - n2) * 0.15 * ink;
|
|
@@ -399,14 +399,14 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
399
399
|
vec2 uv = fragCoord / iResolution.xy;
|
|
400
400
|
vec2 texel = 1.0 / iResolution.xy;
|
|
401
401
|
|
|
402
|
-
//
|
|
402
|
+
// Sample from wave height field: .g channel stores current frame height
|
|
403
403
|
float h = texture(iChannel0, uv).g;
|
|
404
404
|
float hn = texture(iChannel0, uv + vec2(0.0, texel.y)).g;
|
|
405
405
|
float hs = texture(iChannel0, uv - vec2(0.0, texel.y)).g;
|
|
406
406
|
float he = texture(iChannel0, uv + vec2(texel.x, 0.0)).g;
|
|
407
407
|
float hw = texture(iChannel0, uv - vec2(texel.x, 0.0)).g;
|
|
408
408
|
|
|
409
|
-
//
|
|
409
|
+
// Normal perturbation factor must be large enough (50~100), otherwise ripples are invisible
|
|
410
410
|
// If drop strength is 1.0 and radius is 8~15px, using 80.0 produces clearly visible ripples
|
|
411
411
|
vec3 normal = normalize(vec3((hw - he) * 80.0, (hs - hn) * 80.0, 1.0));
|
|
412
412
|
|
|
@@ -417,7 +417,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
417
417
|
float diffuse = max(dot(normal, lightDir), 0.0);
|
|
418
418
|
float specular = pow(max(dot(normal, halfVec), 0.0), 64.0);
|
|
419
419
|
|
|
420
|
-
//
|
|
420
|
+
// Water base color must be bright enough! Deep color no darker than vec3(0.02, 0.08, 0.2), shallow color use vec3(0.1, 0.3, 0.6)
|
|
421
421
|
vec3 deepColor = vec3(0.02, 0.08, 0.22);
|
|
422
422
|
vec3 shallowColor = vec3(0.1, 0.35, 0.65);
|
|
423
423
|
vec3 waterColor = mix(deepColor, shallowColor, 0.5 + h * 5.0);
|
|
@@ -439,7 +439,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
439
439
|
}
|
|
440
440
|
```
|
|
441
441
|
|
|
442
|
-
##
|
|
442
|
+
## Common Fatal Errors
|
|
443
443
|
|
|
444
444
|
1. **RGBA32F silently fails in headless/SwiftShader environments**: Must use `RGBA16F + HALF_FLOAT`
|
|
445
445
|
2. **Ink saturates entire screen**: Gaussian denominator too large (>300) or decay too weak (>0.995). Fix: denominator 100~200, decay `*= 0.99`
|
|
@@ -464,7 +464,7 @@ Fluid simulation requires framebuffer self-feedback + float textures. The follow
|
|
|
464
464
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
465
465
|
<title>Fluid Simulation</title>
|
|
466
466
|
<style>
|
|
467
|
-
/*
|
|
467
|
+
/* canvas must fill the viewport, otherwise it may be invisible or clipped */
|
|
468
468
|
*{margin:0;padding:0}
|
|
469
469
|
html,body{width:100%;height:100%;overflow:hidden;background:#000}
|
|
470
470
|
canvas{display:block;width:100%;height:100%}
|
|
@@ -474,7 +474,7 @@ canvas{display:block;width:100%;height:100%}
|
|
|
474
474
|
<canvas id="c"></canvas>
|
|
475
475
|
<script>
|
|
476
476
|
let frameCount = 0;
|
|
477
|
-
//
|
|
477
|
+
// iMouse convention: [x, y, pressedFlag, 0] — z is the pressed flag (1 or 0), not a coordinate
|
|
478
478
|
let mouse = [0, 0, 0, 0];
|
|
479
479
|
let prevMouse = [0, 0];
|
|
480
480
|
let mouseDown = false;
|
|
@@ -510,7 +510,7 @@ in vec2 pos;
|
|
|
510
510
|
void main(){ gl_Position=vec4(pos,0,1); }`;
|
|
511
511
|
|
|
512
512
|
// fsBuffer / fsImage: adapt from the Buffer A / Image templates below
|
|
513
|
-
//
|
|
513
|
+
// Fragment shaders must declare these uniforms:
|
|
514
514
|
// uniform sampler2D iChannel0;
|
|
515
515
|
// uniform vec2 iResolution;
|
|
516
516
|
// uniform float iTime;
|
|
@@ -521,7 +521,7 @@ void main(){ gl_Position=vec4(pos,0,1); }`;
|
|
|
521
521
|
const progBuf = createProgram(vsSource, fsBuffer);
|
|
522
522
|
const progImg = createProgram(vsSource, fsImage);
|
|
523
523
|
|
|
524
|
-
//
|
|
524
|
+
// Must use RGBA16F + HALF_FLOAT, do NOT use RGBA32F + FLOAT
|
|
525
525
|
// RGBA32F may render all zeros in headless Chrome / SwiftShader even when framebuffer reports complete
|
|
526
526
|
function createFBO(w, h) {
|
|
527
527
|
const tex = gl.createTexture();
|
|
@@ -573,12 +573,12 @@ canvas.addEventListener('mousedown', e => {
|
|
|
573
573
|
const dpr = Math.min(devicePixelRatio, 1.5);
|
|
574
574
|
mouse[0] = e.clientX * dpr;
|
|
575
575
|
mouse[1] = H - e.clientY * dpr;
|
|
576
|
-
mouse[2] = 1.0; //
|
|
576
|
+
mouse[2] = 1.0; // Pressed flag, not a coordinate
|
|
577
577
|
prevMouse[0] = mouse[0]; prevMouse[1] = mouse[1];
|
|
578
578
|
});
|
|
579
579
|
canvas.addEventListener('mouseup', () => {
|
|
580
580
|
mouseDown = false;
|
|
581
|
-
mouse[2] = 0.0; //
|
|
581
|
+
mouse[2] = 0.0; // Released flag
|
|
582
582
|
});
|
|
583
583
|
|
|
584
584
|
// Touch events (mobile)
|
|
@@ -628,7 +628,7 @@ function render(t) {
|
|
|
628
628
|
gl.uniform1f(uBuf.time, t);
|
|
629
629
|
gl.uniform1i(uBuf.frame, frameCount);
|
|
630
630
|
gl.uniform4f(uBuf.mouse, mouse[0], mouse[1], mouse[2], 0.0);
|
|
631
|
-
//
|
|
631
|
+
// Must clamp mouse velocity! Fast movement or first-frame clicks can produce huge velocity values,
|
|
632
632
|
// causing shader velocity explosion → NaN propagation → page crash
|
|
633
633
|
const mvx = Math.max(-50, Math.min(50, mouse[0] - prevMouse[0]));
|
|
634
634
|
const mvy = Math.max(-50, Math.min(50, mouse[1] - prevMouse[1]));
|
|
@@ -699,7 +699,7 @@ void mainImage(out vec4 fragColor, in vec2 p) {
|
|
|
699
699
|
c.xy -= K * vec2(dx.z, dy.z);
|
|
700
700
|
|
|
701
701
|
// Mouse interaction: iMouse.z is the pressed flag (>0), velocity obtained via iMouseVel uniform
|
|
702
|
-
//
|
|
702
|
+
// mouseVel must be clamped to prevent NaN explosion (JS side should also clamp — double safety)
|
|
703
703
|
if (iMouse.z > 0.0) {
|
|
704
704
|
vec2 mouseVel = clamp(iMouseVel, vec2(-50.0), vec2(50.0));
|
|
705
705
|
float dist2 = dot(p - iMouse.xy, p - iMouse.xy);
|
|
@@ -709,7 +709,7 @@ void mainImage(out vec4 fragColor, in vec2 p) {
|
|
|
709
709
|
}
|
|
710
710
|
|
|
711
711
|
// Vorticity confinement: prevents small vortices from dissipating too quickly, producing curling textures
|
|
712
|
-
//
|
|
712
|
+
// Ink diffusion/swirl effects (e.g., ink diffusing in water) require vorticity confinement, otherwise curl dissipates quickly leaving only smooth flow
|
|
713
713
|
float curl_c = dx.y - dy.x;
|
|
714
714
|
float curl_n = (T(p + vec2(1,1)).y - T(p + vec2(-1,1)).y) / 2.0
|
|
715
715
|
- (T(p + vec2(0,2)).x - T(p).x) / 2.0;
|
|
@@ -723,7 +723,7 @@ void mainImage(out vec4 fragColor, in vec2 p) {
|
|
|
723
723
|
eta = normalize(eta + vec2(1e-5));
|
|
724
724
|
c.xy += DT * 0.035 * vec2(eta.y, -eta.x) * curl_c;
|
|
725
725
|
|
|
726
|
-
//
|
|
726
|
+
// Automatic ink sources: ensure visible fluid motion without mouse interaction
|
|
727
727
|
// Emitter positions must move over time, and Gaussian radius must be small enough to maintain locality
|
|
728
728
|
float t = iTime;
|
|
729
729
|
vec2 em1 = iResolution.xy * vec2(0.25, 0.5 + 0.2 * sin(t * 0.7));
|
|
@@ -739,7 +739,7 @@ void mainImage(out vec4 fragColor, in vec2 p) {
|
|
|
739
739
|
c.xy += DT * r3 * vec2(sin(t * 1.1), -cos(t)) * 2.0;
|
|
740
740
|
c.w += DT * (r1 + r2 + r3) * 2.0;
|
|
741
741
|
|
|
742
|
-
//
|
|
742
|
+
// Ink decay: must use multiplicative decay, do NOT use subtractive (subtractive causes saturation)
|
|
743
743
|
c.w *= 0.99;
|
|
744
744
|
|
|
745
745
|
c = clamp(c, vec4(-5, -5, 0.5, 0), vec4(5, 5, 3, 5));
|
|
@@ -762,19 +762,19 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
762
762
|
vec2 uv = fragCoord / iResolution.xy;
|
|
763
763
|
vec4 c = texture(iChannel0, uv);
|
|
764
764
|
|
|
765
|
-
//
|
|
765
|
+
// Color base must be bright enough! 0.5+0.5*cos produces [0,1] range bright colors
|
|
766
766
|
// Never use vec3(0.02, 0.01, 0.08) or similar extremely dark base colors — they become invisible when multiplied by ink
|
|
767
767
|
float angle = atan(c.y, c.x);
|
|
768
768
|
vec3 col = 0.5 + 0.5 * cos(angle + vec3(0.0, 2.1, 4.2));
|
|
769
769
|
|
|
770
|
-
//
|
|
770
|
+
// smoothstep upper limit should cover actual ink range to preserve gradient variation
|
|
771
771
|
float ink = smoothstep(0.0, 2.0, c.w);
|
|
772
772
|
col *= ink;
|
|
773
773
|
|
|
774
774
|
// Pressure highlights
|
|
775
775
|
col += vec3(0.05) * clamp(c.z - 1.0, 0.0, 1.0);
|
|
776
776
|
|
|
777
|
-
//
|
|
777
|
+
// Background color must be visible (RGB at least > 5/255 ≈ 0.02), otherwise users think the page is all black
|
|
778
778
|
col = max(col, vec3(0.02, 0.012, 0.035));
|
|
779
779
|
|
|
780
780
|
fragColor = vec4(col, 1.0);
|
|
@@ -785,7 +785,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
785
785
|
|
|
786
786
|
Water surface ripples use the wave equation rather than Navier-Stokes. Clicks/touches generate concentric ripples that interfere with each other and gradually decay.
|
|
787
787
|
|
|
788
|
-
**
|
|
788
|
+
**Water ripple drop injection must be implemented directly in the shader using iMouse**, do not use custom uniform arrays to pass click positions — that adds complexity on both JS/GLSL sides and is error-prone (uniform location not found, array length mismatch, etc.).
|
|
789
789
|
|
|
790
790
|
### Water Ripple Buffer Pass (Wave Equation Solver)
|
|
791
791
|
|
|
@@ -793,7 +793,7 @@ Water surface ripples use the wave equation rather than Navier-Stokes. Clicks/to
|
|
|
793
793
|
// Water Ripple — Buffer Pass (Wave Equation Solver)
|
|
794
794
|
// Data encoding: .r = previous frame height (prev), .g = current frame height (curr)
|
|
795
795
|
// iChannel0 = self-feedback (ping-pong)
|
|
796
|
-
//
|
|
796
|
+
// Drop injection is done directly in the shader via iMouse, no custom uniforms needed
|
|
797
797
|
|
|
798
798
|
void main() {
|
|
799
799
|
vec2 p = gl_FragCoord.xy;
|
|
@@ -823,7 +823,7 @@ void main() {
|
|
|
823
823
|
float damping = 0.996;
|
|
824
824
|
next *= damping;
|
|
825
825
|
|
|
826
|
-
//
|
|
826
|
+
// Mouse click drop injection — directly using iMouse, simple and reliable
|
|
827
827
|
// iMouse.z > 0 indicates mouse is pressed
|
|
828
828
|
if (iMouse.z > 0.0) {
|
|
829
829
|
float dist = length(p - iMouse.xy);
|
|
@@ -832,7 +832,7 @@ void main() {
|
|
|
832
832
|
next += strength * exp(-dist * dist / (2.0 * radius * radius));
|
|
833
833
|
}
|
|
834
834
|
|
|
835
|
-
//
|
|
835
|
+
// Automatic ripples: ensure visible ripples even without interaction
|
|
836
836
|
// Use periodic functions of iTime to control auto-drop position and timing
|
|
837
837
|
float autoPhase = iTime * 0.5;
|
|
838
838
|
float autoPeriod = fract(autoPhase);
|
|
@@ -854,7 +854,7 @@ void main() {
|
|
|
854
854
|
next *= 0.0;
|
|
855
855
|
}
|
|
856
856
|
|
|
857
|
-
//
|
|
857
|
+
// Output: .r = current frame (becomes next frame's prev), .g = newly computed (becomes next frame's curr)
|
|
858
858
|
fragColor = vec4(curr, next, 0.0, 1.0);
|
|
859
859
|
}
|
|
860
860
|
```
|
|
@@ -877,7 +877,7 @@ See Step 9c above.
|
|
|
877
877
|
|
|
878
878
|
When multiple ink colors need to interpenetrate and blend, a single scalar `c.w` is insufficient. You need **two Buffers**: Buffer A stores velocity/pressure (same as above), Buffer B stores RGB three-channel ink concentration, sharing the same velocity field for advection.
|
|
879
879
|
|
|
880
|
-
**
|
|
880
|
+
**Key for multi-color ink: Buffer B's RGB channels independently store the concentration of each ink color, using Buffer A's velocity field for semi-Lagrangian advection. Different ink colors naturally blend during advection and diffusion.**
|
|
881
881
|
|
|
882
882
|
### JS Side Changes (Three Buffer Ping-Pong)
|
|
883
883
|
|
|
@@ -1020,7 +1020,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
1020
1020
|
vec3 mapped = smoothstep(vec3(0.0), vec3(2.5), ink);
|
|
1021
1021
|
|
|
1022
1022
|
// Color mapping: map RGB concentrations to actual visible colors
|
|
1023
|
-
//
|
|
1023
|
+
// Base colors must be bright, do not use extremely dark values
|
|
1024
1024
|
vec3 col1 = vec3(0.9, 0.15, 0.2); // red ink
|
|
1025
1025
|
vec3 col2 = vec3(0.1, 0.8, 0.3); // green ink
|
|
1026
1026
|
vec3 col3 = vec3(0.15, 0.3, 0.95); // blue ink
|
|
@@ -1040,7 +1040,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
1040
1040
|
}
|
|
1041
1041
|
```
|
|
1042
1042
|
|
|
1043
|
-
**
|
|
1043
|
+
**The multi-color ink Buffer A velocity field template is identical to the single-color version**, except `c.w` is no longer used for ink (ink is in Buffer B). Buffer A only handles velocity + pressure.
|
|
1044
1044
|
|
|
1045
1045
|
## Common Variants
|
|
1046
1046
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
### Standalone HTML Complete Shader Template (Must Be Strictly Followed)
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**The following template can be copied directly; every line must be strictly followed**:
|
|
4
4
|
|
|
5
5
|
**Vertex Shader** (common to all shaders):
|
|
6
6
|
```glsl
|
|
@@ -16,13 +16,13 @@ void main() {
|
|
|
16
16
|
#version 300 es
|
|
17
17
|
precision highp float;
|
|
18
18
|
|
|
19
|
-
//
|
|
19
|
+
// uniforms must be declared; ShaderToy's iTime/iResolution etc. are global variables
|
|
20
20
|
uniform float iTime;
|
|
21
21
|
uniform vec2 iResolution;
|
|
22
22
|
uniform int iFrame;
|
|
23
23
|
uniform vec4 iMouse;
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// mainImage parameters need manual extraction
|
|
26
26
|
// ShaderToy: void mainImage(out vec4 fragColor, in vec2 fragCoord)
|
|
27
27
|
// Adapted to:
|
|
28
28
|
out vec4 fragColor;
|
|
@@ -30,7 +30,7 @@ void main() {
|
|
|
30
30
|
vec2 fragCoord = gl_FragCoord.xy;
|
|
31
31
|
vec2 uv = fragCoord / iResolution;
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// texture2D → texture
|
|
34
34
|
vec4 prev = texture(iChannel0, uv);
|
|
35
35
|
|
|
36
36
|
// ... particle physics logic ...
|
|
@@ -56,7 +56,7 @@ void main() {
|
|
|
56
56
|
vec2 fragCoord = gl_FragCoord.xy;
|
|
57
57
|
vec2 uv = fragCoord / iResolution;
|
|
58
58
|
|
|
59
|
-
//
|
|
59
|
+
// texture2D → texture, mainImage → standard main
|
|
60
60
|
vec4 col = texture(iChannel0, uv);
|
|
61
61
|
|
|
62
62
|
// Rendering logic
|
|
@@ -66,7 +66,7 @@ void main() {
|
|
|
66
66
|
}
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
**
|
|
69
|
+
**Common GLSL ES 3.00 Errors** (must be avoided):
|
|
70
70
|
1. **#version must be on the first line** - Any comments/blank lines will cause "version directive must occur on the first line" error
|
|
71
71
|
2. **in/out qualifiers** - WebGL1's attribute/varying must be changed to in/out in ES3
|
|
72
72
|
3. **texture function** - ES3 uses `texture(sampler, uv)`, not `texture2D(sampler, uv)`
|
|
@@ -74,7 +74,7 @@ void main() {
|
|
|
74
74
|
|
|
75
75
|
## Standalone HTML Multi-Channel Framebuffer Implementation
|
|
76
76
|
|
|
77
|
-
**
|
|
77
|
+
**Multi-Channel Rendering Pipeline Core Pitfalls**: ShaderToy code requires manual Framebuffer rendering pipeline implementation. The following template demonstrates the correct approach:
|
|
78
78
|
|
|
79
79
|
```javascript
|
|
80
80
|
// Correct multi-channel Framebuffer creation
|
|
@@ -91,7 +91,7 @@ for (let i = 0; i < NUM_BUFFERS; i++) {
|
|
|
91
91
|
const texture = gl.createTexture();
|
|
92
92
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
93
93
|
|
|
94
|
-
//
|
|
94
|
+
// Must use UNSIGNED_BYTE format without EXT_color_buffer_float extension!
|
|
95
95
|
// RGBA16F/RGBA32F require the extension, otherwise GL_INVALID_OPERATION
|
|
96
96
|
// Float textures need EXT_color_buffer_float; RGBA16F supports HDR data
|
|
97
97
|
if (ext) {
|
|
@@ -100,14 +100,14 @@ for (let i = 0; i < NUM_BUFFERS; i++) {
|
|
|
100
100
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
103
|
+
// Texture parameters must be set, otherwise GL_INVALID_FRAMEBUFFER
|
|
104
|
+
// Float textures use NEAREST, or require OES_texture_float_linear extension for LINEAR
|
|
105
|
+
// Float textures must use CLAMP_TO_EDGE wrap mode; REPEAT is not supported for float textures
|
|
106
|
+
// Must fall back to UNSIGNED_BYTE format without EXT_color_buffer_float extension
|
|
107
107
|
const filterMode = (ext && floatLinear) ? gl.LINEAR : gl.NEAREST;
|
|
108
108
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filterMode);
|
|
109
109
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filterMode);
|
|
110
|
-
//
|
|
110
|
+
// Must use CLAMP_TO_EDGE: float textures do not support REPEAT
|
|
111
111
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
112
112
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
113
113
|
|
|
@@ -115,7 +115,7 @@ for (let i = 0; i < NUM_BUFFERS; i++) {
|
|
|
115
115
|
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
116
116
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
117
117
|
|
|
118
|
-
//
|
|
118
|
+
// Check Framebuffer completeness
|
|
119
119
|
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
120
120
|
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
121
121
|
console.error("Framebuffer incomplete:", status);
|
|
@@ -138,7 +138,7 @@ function render() {
|
|
|
138
138
|
// Execute shader rendering
|
|
139
139
|
|
|
140
140
|
// 2. Swap Buffers (simulate self-feedback)
|
|
141
|
-
//
|
|
141
|
+
// Must swap textures for next frame reading; FBO handles remain unchanged
|
|
142
142
|
[textures[0], textures[1]] = [textures[1], textures[0]];
|
|
143
143
|
|
|
144
144
|
// 3. Render to screen
|
|
@@ -150,7 +150,7 @@ function render() {
|
|
|
150
150
|
}
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
**
|
|
153
|
+
**Common Errors** (JavaScript/WebGL side):
|
|
154
154
|
1. **Missing texture parameters** - Must set `TEXTURE_MIN_FILTER`, `TEXTURE_MAG_FILTER`, `TEXTURE_WRAP_S/T`
|
|
155
155
|
2. **Missing Framebuffer completeness check** - `gl.checkFramebufferStatus()` must return `FRAMEBUFFER_COMPLETE` before use
|
|
156
156
|
3. **Float texture extension** - `gl.RGBA16F` requires `EXT_color_buffer_float` extension, otherwise fall back to `gl.UNSIGNED_BYTE`
|
|
@@ -620,13 +620,13 @@ for (int i = 0; i < NUM_SCALES; i++) {
|
|
|
620
620
|
|
|
621
621
|
### Variant 6: Particle System (Position-Velocity Storage)
|
|
622
622
|
|
|
623
|
-
**
|
|
623
|
+
**Particle System Implementation Key**: Particle state is stored in texture pixels, one particle per pixel. Rendering must iterate over the particle texture for sampling.
|
|
624
624
|
|
|
625
625
|
**Buffer A (Particle Physics Simulation)**:
|
|
626
626
|
```glsl
|
|
627
627
|
// Each texture pixel stores one particle: xy=position, zw=velocity
|
|
628
628
|
|
|
629
|
-
//
|
|
629
|
+
// hash function must return vec2! Returning float causes type mismatch errors
|
|
630
630
|
vec2 hash2(vec2 p) {
|
|
631
631
|
return fract(sin(mat2(127.1, 311.7, 269.5, 183.3) * p) * 43758.5453);
|
|
632
632
|
}
|
|
@@ -638,10 +638,10 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
638
638
|
vec2 pos = prev.xy;
|
|
639
639
|
vec2 vel = prev.zw;
|
|
640
640
|
|
|
641
|
-
//
|
|
641
|
+
// Initialization guard: use integer comparison + pixel-coordinate-based random (avoids particle overlap when time is too small)
|
|
642
642
|
if (iFrame < 3) {
|
|
643
643
|
// Use fragCoord (pixel coordinates) to ensure each particle has a unique position, independent of time
|
|
644
|
-
//
|
|
644
|
+
// hash2 returns vec2, assign directly to pos/vel
|
|
645
645
|
pos = hash2(fragCoord * 0.01 + vec2(1.7, 9.3));
|
|
646
646
|
vel = (hash2(fragCoord * 0.01 + vec2(5.3, 2.8)) - 0.5) * 0.02;
|
|
647
647
|
fragColor = vec4(pos, vel);
|
|
@@ -676,7 +676,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
676
676
|
|
|
677
677
|
// Iterate over particle texture for sampling (performance-sensitive, balance sample count)
|
|
678
678
|
float glow = 0.0;
|
|
679
|
-
for (float y = 0.0; y < 1.0; y += 0.02) { //
|
|
679
|
+
for (float y = 0.0; y < 1.0; y += 0.02) { // Step size determines sampling density
|
|
680
680
|
for (float x = 0.0; x < 1.0; x += 0.02) {
|
|
681
681
|
vec4 particle = texture(iChannel0, vec2(x, y));
|
|
682
682
|
vec2 pPos = particle.xy;
|
|
@@ -712,7 +712,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
712
712
|
// Buffer B: Density accumulation (reads Buffer A) - uses FBO 2/3 ping-pong
|
|
713
713
|
// Image: Final rendering (reads Buffer A + Buffer B)
|
|
714
714
|
|
|
715
|
-
//
|
|
715
|
+
// Must use 2 FBOs for ping-pong! Single FBO + texture swap causes
|
|
716
716
|
// "Feedback loop formed between Framebuffer and active Texture" error
|
|
717
717
|
const buffers = [null, null, null, null]; // [A_FBO0, A_FBO1, B_FBO0, B_FBO1]
|
|
718
718
|
const textures = [null, null, null, null]; // [A_tex0, A_tex1, B_tex0, B_tex1]
|
|
@@ -741,7 +741,7 @@ function createBuffers() {
|
|
|
741
741
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
742
742
|
}
|
|
743
743
|
|
|
744
|
-
//
|
|
744
|
+
// Initialization pre-rendering - must execute before the first frame!
|
|
745
745
|
// Empty textures cause particle initialization failure (reading 0,0,0,0 makes all particles overlap)
|
|
746
746
|
let aReadIdx = 0; // Current read FBO index (0 or 1)
|
|
747
747
|
let bReadIdx = 0; // Buffer B current read FBO index (0 or 1)
|
|
@@ -893,7 +893,7 @@ function render() {
|
|
|
893
893
|
}
|
|
894
894
|
```
|
|
895
895
|
|
|
896
|
-
**
|
|
896
|
+
**Key Points**:
|
|
897
897
|
- **Must use 2 FBOs for ping-pong**: Each Buffer needs two independent FBOs (read FBO + write FBO); a single FBO + texture swap causes "Feedback loop" error
|
|
898
898
|
- Use FBO index switching (not texture swapping): bind target FBO when writing, bind source texture when reading
|
|
899
899
|
- Image pass binds the latest Buffer results (obtained via read index)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## WebGL2 Adaptation Requirements
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**GLSL Type Strictness Warning**:
|
|
4
4
|
- GLSL is a strongly typed language with **no `string` type**; using string types is forbidden
|
|
5
5
|
- Common illegal types: `string`, `int` (can only use `int` literals, cannot declare variable types as `int`)
|
|
6
6
|
- vec2/vec3/vec4 cannot be implicitly converted between each other; explicit construction is required
|