Files
feuerwerk/src/core/graph/nodes
adminandClaude Opus 5.5 9d6a0aebd3
game-maker / gate (push) Successful in 3m4s
game-maker / suite (push) Failing after 9m47s
Let Compare and Equals treat float noise as equal numbers
A graph that checks "time - late == 6" after On timer (#224) failed,
because the difference comes out as 5.999999999999972. Both values are
sums of measured frame times, and they round along different paths.
The only workaround was a Round node in front of every comparison.

- New helper nearlyEqual in values.ts. Two numbers count as equal when
  |a - b| <= 1e-9 * max(1, |a|, |b|). The tolerance grows with the
  size of the values, so it holds for hours of summed dt.
- Compare uses the rule for all six operators. "<" and ">" only hold
  when the values are not nearly equal, "<=" and ">=" also hold when
  they are. So "==" and ">" are never true at the same time.
- Equals uses the same rule when both inputs are real numbers. Objects
  still compare by identity, everything else as text. The node
  description says so.
- The tolerance is fixed, there is no parameter on the node. Physics
  values come from float32 in Rapier and are not covered on purpose.
- The log display is unchanged and still prints the raw number.

Tests:
- ./check.sh: 156 files, 2311 tests, CHECK EXIT 0 (before 2300).
- 11 new cases in node-catalog.test.ts, including the value from the
  log against 6, growth with magnitude, 6.001 != 6 and 1e-6 > 0.
- Counter-check exact compare: exactly the 8 noise cases fail.
- Counter-check "<"/">" without the exclusion: exactly 2 cases fail.
- No e2e change, the check sum stays at 1374.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 08:30:45 +02:00
..
The new node "Raycast all" answers with every collider along the ray instead of only the nearest one: it carries the same inputs and settings as Raycast (Ignore owner, Hit triggers, Layers from #158) plus "max hits" where 0 means all, and it hands back parallel lists sorted by distance - objects, colliders, points x/y, normals x/y, distances - next to count, so index i of every list speaks about the same hit; a collider counts once no matter how many convex parts its shape is built from, while a tilemap counts per cell it is hit in, and the filter sits in Solids.raycastAll like the one of #158, the one path the editor run and Play both take, so all 12 new unit tests run through describe.each against both systems; the node needed the value system to learn something it could not do - a graph list carried numbers, text, booleans and bundles but no object handles, asList turned an object into its name, so objects/colliders would have been lists of names, which loses the object when two carry the same name, and collider children usually do; Value now knows a travelling list that may hold handles and "Item at" gives such an item out as an object, ready for Destroy, Apply force or Name of, while the single door into a stored file - writeTyped/asTypeOf - runs the list through the new storedList() so every object becomes its name again and nothing living reaches a project file, which is its own unit test over JSON.stringify; three list nodes follow the handles: "Index of" compares objects by identity because a name is not the object of that name, "Set item" and "Fill" write an object as an object, and "Join text" prints the names instead of the innards; this decision was mine to make because the question ran into the MCP timeout - the alternative was a list of names plus "Object by name", which is the smaller change but lossy; 4 new e2e checks in the new block 43e (shield and floor in one go reading 2 and "145, 280", the second object answering to its name, which only a real object in a list can do, "max hits" 1 stopping at the nearest, and the node showing the row) and 18 new unit tests, 12 in raycast.test.ts, 4 on the value system and 2 in the node catalog; two counter-checks hold: not counting tilemap cells one by one drops 2 unit tests, and answering with the nearest hit only drops module 43 with 2 FAILs; one finding on the way - the node read "maxHits" while every other input carries a spoken label, because int() took the key as its label, so it has a third parameter now, and the e2e check caught that, no unit test did (#157)
2026-09-20 18:54:12 +02:00