Skip to content

spotCandidates: reversing generation order outside region (0,0) passes the entire spec #259

Description

@wormeyman

What

test/spotCandidates.spec.ts cannot see a candidate-generation reordering, in
almost every region.

The game fixture (test/fixtures/spot-candidates.game.json, 11 cases) is
sorted by (x, y), and its own note says so: "Sorted (x,y); generation order
is not recorded here". The spec sorts the port's output the same way before
comparing, so any permutation of the generated stream compares equal.

One test does grade order - the candidate_index_to_Vx_Vy stream from
docs/noise/spot-candidate-stream.seed123456.json - but it covers exactly
one key: seed0 = 123456, seed1 = 0, region (0, 0), regionSize 2**32.

Measured

Planted mutations against a scratchpad copy of spotCandidatePoints, 2026-08-18:

planted mutation game fixture (sorted) stream (indexed)
none 11/11 20/20
reverse generation order 11/11 - blind 0/20 caught
rotate by one 11/11 - blind 0/20 caught
swap first two candidates 11/11 - blind 18/20 caught
swap the x and y draw 0/11 caught 0/20 caught

And the hole the single-key stream test leaves, measured directly: a reversal
applied to every region except (0, 0) passes the game fixture 11/11, the
stream test 20/20, the clamp test and the bit-0 test. The whole spec is blind
to it.

Why it matters

Order is not cosmetic here. spotSelection consumes candidates in generation
order: the skip set is applied by index (skipSpan / skipOffset), and
randomPenalty is a batch op seeded from the first accepted spot and
streamed last-to-first, so a permutation changes every spot's quantity. See
docs/noise/spot-noise-NOTES.md and docs/noise/random-penalty-NOTES.md.

No defect is known to exist behind this gap. It is a missing guard, not a bug
report.

What would close it

Capture an ordered stream for a second key - ideally one with a non-zero region
and a non-zero seed1 - and grade it by index like the existing stream test.
test/oracle/ already has the machinery; docs/noise/spot-candidate-stream.seed123456.json
is the shape to copy.

Do NOT convert this spec under #256

Separate but related, and worth recording here because the two look alike: the
stream assertion compares raw u32 draws (up to 4,178,305,507), where f32's
spacing is 256. Scoring it with Math.fround(got) === expected returns 1/40
on values that are 40/40 strictly equal. The fround would be the
corruption, not the measurement. spotCandidates is integer end to end and a
Rust port must carry it as u32/u64 wrapping arithmetic - its pre-mod seed
sum reaches 3.4e13, and evaluating that in f32 disagrees on 4 of the 29 keys the
spec exercises.

Found while scoring the phase-1 primitives for #220.

Metadata

Metadata

Assignees

No one assigned

    Labels

    noise-reReverse-engineering Factorio's noise math against the game

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions