Shader graph: Pow node, base held at 0, exponent broadcast for colours (#80)
game-maker / suite (push) Failing after 8m13s

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-13 18:29:04 +02:00
1 parent 102a649a35
commit a399795772
4 files changed
+70 -1

No files matched your search

+1 -1
View File
@@ -37,7 +37,7 @@ npm run build # writes dist/ and regenerates index.html; the folder is wh
The browser suite and the three example games need a machine with a GPU:
```bash
node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 760 checks (GM_STEPS=20 for one step)
node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 762 checks (GM_STEPS=20 for one step)
node tools/seed-jumprun.mjs && node tools/smoke-jumprun.mjs
node tools/seed-tetris.mjs && node tools/smoke-tetris.mjs
node tools/seed-actor.mjs && node tools/smoke-actor.mjs
+17
View File
@@ -355,6 +355,22 @@ export const ShaderStep: ShaderNodeDef = {
}),
};
/** GLSL leaves `pow` undefined below 0, so the base is held at 0 there. */
export const ShaderPow: ShaderNodeDef = {
type: 'ShaderPow',
label: 'Pow',
category: 'shader',
description:
'value to the power of exponent: 2 squares it, 0.5 is the square root. Below 0 the value counts as 0. Per channel for colours.',
inputs: [anyIn('value', 0), floatIn('exponent', 2)],
outputs: [anyOut('result')],
params: [],
emit: ({ value, exponent }) => {
const power = value!.type === 'vec3' ? `vec3(${exponent!.code})` : exponent!.code;
return { result: { code: `pow(max(${value!.code}, 0.0), ${power})`, type: value!.type } };
},
};
/** Every node a shader graph offers. Registered by `graph/nodes`, offered by no behavior palette. */
export const SHADER_NODES: readonly ShaderNodeDef[] = [
ShaderTextureColor,
@@ -377,6 +393,7 @@ export const SHADER_NODES: readonly ShaderNodeDef[] = [
ShaderFloor,
ShaderFract,
ShaderStep,
ShaderPow,
];
/** A GLSL float literal: always with a decimal point, so `1` does not become an int. */
+19
View File
@@ -451,6 +451,25 @@ describe('compileShader', () => {
expect(fragment).toMatch(/vec3 n\d+_result = floor\(n\d+_color\);/);
expect(fragment).toMatch(/float alpha = clamp\(n\d+_result, 0\.0, 1\.0\);/);
});
it('writes Pow as the GLSL call with the base held at 0, the exponent broadcast for a colour', () => {
const f = makeProject();
const s = shaderEditor(f, 'Gamma');
const uv = s.add(SHADER_UV);
const eased = s.add('ShaderPow', { exponent: 2 });
const tint = s.add('ShaderColor', { value: 0xff8000 });
const gamma = s.add('ShaderPow', { exponent: 2.2 });
const output = s.add(SHADER_OUTPUT);
s.link(uv, 'u', eased, 'value');
s.link(eased, 'result', output, 'alpha');
s.link(tint, 'color', gamma, 'value');
s.link(gamma, 'result', output, 'color');
const build = compileShader(s.asset().graph);
expect(build.problems).toEqual({});
const { fragment } = build.compiled!;
expect(fragment).toMatch(/float n\d+_result = pow\(max\(n\d+_u, 0\.0\), 2\.0\);/);
expect(fragment).toMatch(/vec3 n\d+_result = pow\(max\(n\d+_color, 0\.0\), vec3\(2\.2\)\);/);
});
});
describe('a sprite wearing a shader', () => {
+33
View File
@@ -470,6 +470,39 @@ export async function shader(t) {
{ vTop, vBottom },
);
// 28k-ii. Pow bends the cut: u squared crosses 0.5 at u = 0.707, its square root at u = 0.25.
const cutAfterPow = (exponent) =>
redOrBlue(
[
node('uv', 'ShaderUV'),
node('pow', 'ShaderPow', { exponent }, 260, 0),
node('cut', 'ShaderStep', { edge: 0.5 }, 260, 120),
],
[
edge('uv', 'u', 'pow', 'value'),
edge('pow', 'result', 'cut', 'value'),
edge('cut', 'result', 'mix', 't'),
],
);
await cutAfterPow(2);
await sleep(400);
const squaredNear = await pixel(0.1, -0.3);
const squaredFar = await pixel(0.3, -0.3);
check(
'Pow 2 moves the cut to u = 0.707: still red at u = 0.6, blue at 0.8',
colourDistance(squaredNear, '#ff0000') < 6 && colourDistance(squaredFar, '#0000ff') < 6,
{ squaredNear, squaredFar },
);
await cutAfterPow(0.5);
await sleep(400);
const rootNear = await pixel(-0.3, -0.3);
const rootFar = await pixel(-0.15, -0.3);
check(
'Pow 0.5 pulls it to u = 0.25: red at u = 0.2, blue at 0.35',
colourDistance(rootNear, '#ff0000') < 6 && colourDistance(rootFar, '#0000ff') < 6,
{ rootNear, rootFar },
);
// 28l. Sample texture reads the picture where the graph says. On a pasted two-colour picture:
// flipped u swaps the halves; u + 0.5 past the border shows the Output's Edge – Repeat continues
// with the far half, Mirror with the near one.