Hand a shared port type on through a chain of nodes (#210)
game-maker / gate (push) Successful in 2m51s
game-maker / suite (push) Successful in 14m44s

Since #207 the output of First not empty or Select takes the type
its inputs agree on. A second node of that kind behind the first
stayed any, because the first one answered with the any it was
registered with. That was the guard against a circle of such nodes
asking each other forever. The issue asks for the type to travel
through the chain. Answered there: measure with 50 nodes and build
no cache, and the painted input pins follow along.

- shapedDef in core/graph/nested.ts takes a Trail in place of the
  flag inherit. Its set open holds the nodes still being asked. A
  node reached a second time reports the any it was registered
  with, which ends a circle.
- The map done of the Trail keeps each answer of one question. A
  node feeding both inputs of the next is asked once, so the work
  does not double with every link of a chain.
- ownShape is the part of shapedDef that builds the ports before a
  shared group has its say. It is split off, nothing in it changed.
- Inputs stay any and only outputs inherit, as in #207. An edge
  that turns invalid further down is left in place and drawn in the
  danger colour.
- paintedInputs uses one Trail for all pins of a node, so the pins
  show the type handed on through the chain.
- Nothing is cached between two changes. A chain of 50 resolved in
  about 10 ms where it took 2 ms before, measured earlier in this
  round and not again for this commit.

Tests:
- Gate on melektaus-atx: 211 files, 3240 tests (3227 + 13), exit 0
- e2e 1-6 on melektaus-atx: 298 checks, all pass
- e2e 4,6,18,19,20,23,24,26,28,31,36,43,53: 558 checks, all pass
- No full run and no smokes. The sum of 1673 (1671 + 2) is reckoned,
  1671 is what CI counted for c88f57c.
- Fourteen unit tests in shared-port-type.test.ts: a chain of two
  and of three, mixed kinds, a mixed and an open input, undo at the
  front, a circle of two, a node feeding itself, behind a circle, an
  invalid edge further down, the chain of 40 asked once per node,
  the chain of 50, and two for the pins. The test saying the type
  does not pass through a second node is gone, hence 13 more.
- Two e2e checks in module 06: the second node takes number, and a
  text at the front opens the whole chain again.
- Four unit counter-probes and one browser counter-probe all fail
  their tests. With the old behaviour only the chain check fails.
- Not checked in the browser: the circle and the chain of three,
  which have unit tests only. 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 14:41:30 +02:00
1 parent c88f57cf0a
commit 9ca220a285
4 files changed
+268 -39

No files matched your search

+35
View File
@@ -1050,6 +1050,41 @@ export async function graph(t) {
JSON.stringify({ pinColors, wanted }),
);
await shot('20-input-pin-colours');
// A second node of the kind behind the first (#210): the type is handed on through the chain,
// and taken back all along it once the front of the chain disagrees again.
await mutate(`
__gm.state.project.assets
.find((a) => a.id === 'picky')
.graph.nodes.push({ id: 'p-next', type: 'FirstNotEmpty', x: 300, y: 330, params: {} });
`);
const nextTypes = async () => json(`__gm.graph.portTypes('p-next')`);
await wire('p-pick', 'value', 'p-next', 'a');
await wire('p-pick', 'value', 'p-next', 'b');
const chainOpen = await nextTypes();
await wire('p-a', 'value', 'p-pick', 'a');
const chainTyped = await nextTypes();
check(
'a second node of the kind behind the first takes the type the first one agreed on',
chainOpen.outputs.value === 'any' &&
(await pickTypes()).outputs.value === 'number' &&
chainTyped.outputs.value === 'number' &&
chainTyped.inputs.a === 'any' &&
chainTyped.paint.a === 'number' &&
chainTyped.paint.b === 'number',
JSON.stringify({ chainOpen, chainTyped }),
);
await shot('20b-shared-type-chain');
await wire('p-text', 'value', 'p-pick', 'a');
const chainMixed = await nextTypes();
check(
'a text plugged into the first opens the whole chain up again',
(await pickTypes()).outputs.value === 'any' &&
chainMixed.outputs.value === 'any' &&
chainMixed.paint.a === 'any' &&
chainMixed.paint.b === 'any',
JSON.stringify(chainMixed),
);
await mutate(`
const p = __gm.state.project;
p.assets = p.assets.filter((a) => a.id !== 'picky');