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:
adminandClaude Opus 5.5 committed 2026-10-02 16:50:50 +02:00
1 parent 9ca220a285
commit f02adabcf9
9 files changed
+472 -10

No files matched your search

+6 -4
View File
@@ -320,13 +320,14 @@ describe('compileShader', () => {
expect(fragment).toContain('float alpha = clamp(n2_result, 0.0, 1.0);');
expect(fragment).toContain('finalColor = vec4(clamp(rgb, 0.0, 1.0) * alpha, alpha);');
// A colour reaching the alpha port through an `any` result is the Output's problem.
// A result wired to the alpha port while it was a number, and fed a colour afterwards, is the
// Output's problem.
const wrong = shaderEditor(f, 'Wrong');
const texel = wrong.add(SHADER_TEXTURE_COLOR);
const doubled = wrong.add('ShaderMultiply', { b: 2 });
const out = wrong.add(SHADER_OUTPUT);
wrong.link(texel, 'color', doubled, 'a');
wrong.link(doubled, 'result', out, 'alpha');
wrong.link(texel, 'color', doubled, 'a');
const build = compileShader(wrong.asset().graph);
expect(build.compiled).toBeNull();
expect(build.problems).toEqual({ [out]: '"alpha" needs a number, not a color' });
@@ -335,13 +336,14 @@ describe('compileShader', () => {
it('names the problem on the node, and compiles nothing while there is one', () => {
const f = makeProject();
const s = shaderEditor(f);
// A colour cannot be wired to a number port; a Multiply's `any` result can, and carries one.
// A colour cannot be wired to a number port; a Multiply's result can while it is a number, and
// the edge stays when a colour feeds the Multiply afterwards.
const texel = s.add(SHADER_TEXTURE_COLOR);
const doubled = s.add('ShaderMultiply', { b: 2 });
const mixed = s.add('ShaderMix');
const output = s.add(SHADER_OUTPUT);
s.link(texel, 'color', doubled, 'a');
s.link(doubled, 'result', mixed, 't');
s.link(texel, 'color', doubled, 'a');
s.link(mixed, 'result', output, 'color');
let build = compileShader(s.asset().graph);
expect(build.compiled).toBeNull();