Show the type an arithmetic shader node really puts out (#219)
The result of Add, Multiply, Mix and the other arithmetic nodes of a shader graph was drawn as any, whatever went in. The compiler already lifts a colour on any side to a colour result, so the pin said less than the code knew. A colour result could also be wired into a number input, and the shader then failed to compile. - liftedType in core/shader/nodes.ts is the one rule. The compiler reads it when it lifts two operands, and the editor reads it through liftedPortType for the ports it draws. - A SharedPortGroup may bring its own rule as agree. Without one it is agreedType, as before. The thirteen arithmetic nodes share their type by liftedPortType. - Only the any inputs decide. An input nothing feeds counts as the number typed into it. t, edge, exponent, min and max have no say. - The type travels through a chain of such nodes without code of its own, because #210 hands a shared type on. - In a ring the result stays any, unless a colour has settled it. - A number may feed a colour input. Without that a number result no longer fitted Output.color, although the compiler spreads it over three channels. The rule sits in portsCompatible, but only the shader graph has colour ports. - A colour result no longer fits a number input. An edge laid before the colour came in stays and is drawn in the danger colour, and the node at its end names the problem. - The nine shaders in the stored projects (108 edges) all stay valid under the new rule. Tests: - Gate on melektaus-atx: 212 files, 3260 tests (3240 + 20), exit 0. The gate right before this commit ran on the tree of this and the next commit together, with the same numbers. - e2e 28 on melektaus-atx: 81 checks, all pass. - e2e 1-31: 1059 pass, 1 fail in module 18 (the editor clock read 1.97 s after a stop). A rerun of 1-18 passed with 602 checks and 0.18 s. 1-31 was not run in full again. - No full run and no smokes. The sum of 1676 (1673 + 3) is reckoned. - Twenty unit tests in shader-port-types.test.ts, one of them runs all thirteen nodes through every mix of number and colour and compares what is drawn with the code made. - Two tests in shaders.test.ts now lay the edge first and bring the colour in after, the only order that still wires such a graph. - Three e2e checks in module 28 with the real mouse: a colour into Multiply, the red edge with its message, and the colour result refused at alpha and taken at color. - Fifteen unit counter-probes and two browser counter-probes all fail their tests. - Not checked in the browser: the one-input nodes, Mix and the ring, and a number wired straight into a colour input. Nothing was looked at after the build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9ca220a285
commit
f02adabcf9
9 files changed
+472
-10
No files matched your search
@@ -581,6 +581,94 @@ export async function shader(t) {
|
||||
const held = await pixel(-0.3, -0.3);
|
||||
check('Clamp at 0..0.25 holds full red down to a quarter', colourDistance(held, '#400000') < 6, held);
|
||||
|
||||
// 28k-vii. An arithmetic node shows the type that really comes out (#219): a number until a
|
||||
// colour is dragged into it, a colour from then on – along the chain behind it too. A result
|
||||
// that is a colour no longer lands on a number input, and an edge wired while it was a number
|
||||
// stays and turns red.
|
||||
await graph(
|
||||
[
|
||||
node('tex', 'ShaderTextureColor', {}, 0, 0),
|
||||
node('times', 'ShaderMultiply', {}, 0, 140),
|
||||
node('sum', 'ShaderAdd', {}, 0, 300),
|
||||
node('out', 'ShaderOutput', {}, 0, 460),
|
||||
],
|
||||
[edge('times', 'result', 'sum', 'a'), edge('times', 'result', 'out', 'alpha')],
|
||||
);
|
||||
await showTab('shader');
|
||||
await clickText('Fit');
|
||||
await sleep(150);
|
||||
const types = (id) => json(`__gm.shader.portTypes(${JSON.stringify(id)})`);
|
||||
const portOf = (id, port, isOutput) =>
|
||||
json(`__gm.shader.portAt(${JSON.stringify(id)}, ${JSON.stringify(port)}, ${isOutput})`);
|
||||
const wire = async (from, fromPort, to, toPort) => {
|
||||
const a = await portOf(from, fromPort, true);
|
||||
const b = await portOf(to, toPort, false);
|
||||
await drag(a.x, a.y, b.x, b.y, 16);
|
||||
};
|
||||
const edgeIds = () =>
|
||||
json(
|
||||
`__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(made.id)}).graph.edges.map((e) => e.from.node + '.' + e.from.port + '>' + e.to.node + '.' + e.to.port)`,
|
||||
);
|
||||
const edgeColor = (id) => json(`__gm.shader.edgeColor(${JSON.stringify(id)})`);
|
||||
const theme = (name) => json(`__gm.themeColor(${JSON.stringify(name)})`);
|
||||
const plain = {
|
||||
times: await types('times'),
|
||||
sum: await types('sum'),
|
||||
toAlpha: await edgeColor('times-out-alpha'),
|
||||
info: await info(),
|
||||
};
|
||||
await wire('tex', 'color', 'times', 'a');
|
||||
const lifted = {
|
||||
times: await types('times'),
|
||||
sum: await types('sum'),
|
||||
chain: await edgeColor('times-sum-a'),
|
||||
toAlpha: await edgeColor('times-out-alpha'),
|
||||
info: await info(),
|
||||
problem: await json("__gm.shader.nodeProblem('out')"),
|
||||
};
|
||||
check(
|
||||
'a Multiply is a number until a colour is dragged into it, then a colour, and so is the Add behind it',
|
||||
plain.times.outputs.result === 'number' &&
|
||||
plain.sum.outputs.result === 'number' &&
|
||||
lifted.times.outputs.result === 'vec3' &&
|
||||
lifted.times.inputs.a === 'any' &&
|
||||
lifted.times.paint.a === 'vec3' &&
|
||||
lifted.sum.outputs.result === 'vec3' &&
|
||||
lifted.sum.paint.a === 'vec3' &&
|
||||
lifted.chain === (await theme('portVec3')),
|
||||
JSON.stringify({ plain, lifted }),
|
||||
);
|
||||
check(
|
||||
'the edge wired to alpha while the result was a number stays, turns red and is the Output’s problem',
|
||||
plain.toAlpha === (await theme('portNumber')) &&
|
||||
plain.info === '0 properties' &&
|
||||
lifted.toAlpha === (await theme('danger')) &&
|
||||
lifted.problem === '"alpha" needs a number, not a color' &&
|
||||
lifted.info === lifted.problem &&
|
||||
(await edgeIds()).includes('times.result>out.alpha'),
|
||||
JSON.stringify({ plain, lifted, edges: await edgeIds() }),
|
||||
);
|
||||
await shot('28-lifted-result');
|
||||
await mutate(`
|
||||
const a = __gm.state.project.assets.find((x) => x.id === ${JSON.stringify(made.id)});
|
||||
a.graph.edges = a.graph.edges.filter((e) => e.to.node !== 'out');`);
|
||||
const before = await edgeIds();
|
||||
await wire('sum', 'result', 'out', 'alpha');
|
||||
const refused = await edgeIds();
|
||||
await press('Escape');
|
||||
await wire('sum', 'result', 'out', 'color');
|
||||
const taken = await edgeIds();
|
||||
check(
|
||||
'a result that is a colour does not land on the number input alpha, and does on color',
|
||||
before.length === 2 &&
|
||||
refused.length === 2 &&
|
||||
taken.length === 3 &&
|
||||
taken.includes('sum.result>out.color') &&
|
||||
(await info()) === '0 properties',
|
||||
JSON.stringify({ before, refused, taken, info: await info() }),
|
||||
);
|
||||
await showTab('scene');
|
||||
|
||||
// 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.
|
||||
|
||||
Reference in new issue
Block a user