Commit 0a160df
* feat(noise): expose the stable integer cell index on Voronoi (#27)
* feat(islands): largest inscribed rectangle, checked against a brute-force reference (#27)
* feat(islands): survey Fulgora's Voronoi cells to enumerate candidate islands (#27)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6vHdLuzbrJ8ajbZUXDyB5
* refactor(preview): extract createWorkerHost so a second consumer can share the worker pool (#27)
* feat(islands): land mask and flood fill from a rendered terrain image (#27)
* feat(islands): proximity graph and chain components at big-pole reach (#27)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code
* fix(islands): unify minGapTiles and boxGap to same distance metric (#27)
minGapTiles was returning empty-tile count (distance - 1) while boxGap
returned raw Chebyshev distance. The prefilter checked distance against
reachTiles, but minGapTiles checked empty tiles, causing unit mismatch at
the boundary. Change minGapTiles to return distance (tile-centre to
tile-centre), matching how poles connect at exactly 30-tile reach.
Also update test expectations to reflect distance metric, and add test
verifying the metrics agree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code
* feat(islands): orchestrate survey, coarse measure, refine and chain (#27)
Two deviations from the brief's verbatim code, both required to make the
tests pass on real data at radius 600 / SEED0:
- A cell's Voronoi id can classify it non-ocean while the specific sampled
point (or its rounded pixel at a given tpp) still renders as ocean - cell
classification and per-tile land/water are different questions. Added
nearestLandPixel to search the rendered window for the closest actual land
pixel before flood-filling, and drop any candidate that still measures zero
land after that search.
- Fixed a unicorn/no-new-array lint warning (Array.from instead of
`new Array(n)`) and removed an unused `vi` import from the brief's test.
* fix(islands): dedupe overlapping coarse candidates and stop overcounting progress (#27)
Fix round 1 on Task 7's orchestrator, per review:
1. Two findings from review: several adjacent Voronoi cells can each
flood-fill an overlapping slice of the same physical island into
their own row (islandMask's flood fill deliberately crosses cell
boundaries, but surveyIslands enumerates one candidate per cell).
Deduplicate with chainComponents(masks, 0) immediately after the
coarse pass, keeping the highest-landTiles representative per group,
and rank/refine from the deduplicated set.
2. progress `total` assumed every candidate reaches refinement; the
zero-land filter and the new dedup pass both drop some first. `total`
now starts as the coarse-only count and is extended once the
deduplicated refine set is known, so `done` reaches `total` through
real ticks alone - the `if (done < total)` fallback stays as a
belt-and-braces guard but no longer needs to fire.
A necessary supporting fix, found while verifying (1): `windowFor`'s
origin was left unaligned (`c.minX - WINDOW_PAD_TILES` raw), so two
different candidates' coarse windows sampled incommensurable world-tile
grids and almost never shared an exact position - `chainComponents(_, 0)`
was measured to merge ZERO of 39 coarse survivors, including a pair
independently confirmed (via a full-resolution tpp=1 flood fill) to be
100% the same connected island. Snapping the window origin to a multiple
of `tpp` puts every candidate on one shared grid; re-measured after the
fix, the same pair collapses into one dedup group, and the known
duplicate the reviewer flagged - (2,-4) overlapping (1,-4)/(1,-3) - does
too. 39 coarse survivors now collapse to 35; the final radius-600 island
count drops from 39 to 35.
Added a covering test asserting no two returned rows share a land tile,
independently re-rendered on a fixed, grid-aligned resolution rather than
reusing findIslands's own internal masks (a first version reused each
row's own tpp/origin and produced a false negative for exactly this
reason). Strengthened the progress test to check every call's `done`
against its own call index, which a fallback-covered overcount fails but
a last-tuple-only check does not.
* feat(islands): results panel with ranked list and jump-to (#27)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6vHdLuzbrJ8ajbZUXDyB5
* fix(islands): real re-centering, radius bounds, stale jump clear, cancel test (#27)
Round 1 fixes on the island finder panel: ElevationPreviewPanel now takes
optional centerX/centerY props (default 0, a no-op for every existing
caller) that shift the render origin and auto re-render once a render has
already happened, so clicking a results row actually moves the client
preview instead of only printing a coordinate. App.vue feeds the jump
target through those props and clears it on a planet switch. The radius
input is clamped to [500, 20000] since FNumberInput has no bounds of its
own and search cost grows with the square of the radius. Adds a test for
the cancel path (findIslands resolves rather than rejects on abort).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6vHdLuzbrJ8ajbZUXDyB5
* test(islands): pin the survey cost, and record the milestone (#27)
* fix(islands): grow render window on border touch, thread island sliders, hoist chain extraction, group refined/unrefined sort, and stop the radius field snapping mid-edit (#27)
Applies the five findings from the final whole-branch review of the Fulgora
island finder (DO NOT MERGE verdict):
- Critical: the render window (candidate bbox + a flat 32-tile pad) clipped
multi-cell islands at its own edge, undercounting land/rectangle and
flipping which of two candidates ranks higher. `measure()` now re-renders
at a doubled pad whenever the isolated mask still touches the window
border, capped at 3 growths (32 -> 64 -> 128 -> 256). A result still
touching the border after the last growth is marked `clipped: true`,
surfaced in the panel the same way `refined` is (a `!` marker beside `~`).
- The preset's `fulgora_islands` frequency/size sliders were dropped -
IslandFinderPanel built `ctx: { seed0 }` only, so the finder always
surveyed and rendered the default grid even when the preview beside it
showed a different one. Now reads `elevationCtxFromPreset(preset)
.fulgoraIslandControls`, the same helper ElevationPreviewPanel already
uses.
- `chainComponents` re-extracted each mask's land-tile array on every
pairwise call instead of once per mask. Hoisted the extraction; kept
`minGapTiles`'s public signature unchanged for its own tests. Measured
real-data effect was smaller than expected (~200ms either way at radius
5000, since `boxGap` already discards 99.8%+ of pairs) but the fix removes
real redundant work with zero behavior change.
- Coarse (unrefined) and refined rows were sorted together by raw rectangle
area, letting an unrefined 51st-place row (biased upward by 8x8-tile
quantization) outrank a measured one. `compareResults` now groups refined
rows before unrefined ones, each sorted by area within its group.
- The radius field's setter clamped on every write, snapping "5" to "500"
mid-edit while typing toward "5000". Clamping moved to blur and to the
start of a search (the actual guarantee), not the setter.
Also updates docs/noise/client-preview-ROADMAP.md: records the window-growth
cap as a known limit, and replaces the design spec's "chains negligible"
framing with the measured combined cost (~365ms at radius 5000).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6vHdLuzbrJ8ajbZUXDyB5
* test(islands): assert the grouped rank order, and cut four tests to refineCount 3 (#27)
The final review's fix 4 made `compareResults` rank refined rows above
unrefined ones as a group. "sorts by rectangle area, largest first" still
asserted a flat area sort, which contradicts that - and it passed only
because radius 600 refines every one of its 35 rows at the default count of
50, leaving the unrefined group empty so the two orderings coincide.
The re-review said lowering the refine count would make that assertion go
red. It does not. The refine set is chosen BY coarse area, so the refined
group starts out holding the largest rows and the grouped order still comes
out identical to the flat one. Measured across the ladder, smallest refined
area against largest unrefined area in tiles:
count 1: 19,684 vs 17,920 - no crossing
count 2: 17,440 vs 17,280 - no crossing
count 3: 14,256 vs 16,640 - CROSSING
count 5: 13,800 vs 11,648 - no crossing
Only at 3 do the orderings disagree, when cell (-2,-3) refines from a
coarse 17,280 to a true 14,256 and falls under (-1,2) at 16,640 and (2,1)
at 14,976. The test now asserts the grouped order and carries an explicit
anti-vacuity check - a flat sort of the same rows must NOT equal the
shipped order - so this going vacuous again is a red line rather than a
silent pass. Confirmed by planting the break: dropping the
`a.refined !== b.refined` line fails it with "expected false to be true".
The same constant cuts the file's cost, which fix 4's grow-and-re-render
loop had made the heaviest in the suite. Per test, 50 -> 3: view-terrain
41.2s -> 15.4s, rect-vs-land 41.7s -> 15.4s, the rank test 42.1s -> 15.4s,
no-duplicate-island 57.7s -> 30.9s. The file goes 240.4s -> 134.6s (-44%),
against a binding CI shard of 389s and a 300s per-test budget.
"reports progress" keeps the default on purpose and cannot be cheapened
this way: the overcount it guards against was `candidates.length +
Math.min(refineCount, candidates.length)`, which differs from the correct
value only when refineCount exceeds the DEDUPED count (35, against 41
candidates). At 3 both arithmetics give 44 and the bug is invisible; at 36
or more the refine set is the whole deduped list, so there is nothing to
save.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114mWC72TAgZzNjMejBpdTt
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent c2d4297 commit 0a160df
23 files changed
Lines changed: 2520 additions & 29 deletions
File tree
- docs/noise
- src
- components
- noise
- expressions
- islands
- preview
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
566 | 566 | | |
567 | 567 | | |
568 | 568 | | |
569 | | - | |
570 | | - | |
571 | | - | |
572 | | - | |
573 | | - | |
574 | | - | |
575 | | - | |
576 | | - | |
577 | | - | |
578 | | - | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
579 | 650 | | |
580 | 651 | | |
581 | 652 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
22 | 43 | | |
23 | 44 | | |
24 | 45 | | |
| |||
75 | 96 | | |
76 | 97 | | |
77 | 98 | | |
78 | | - | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
79 | 110 | | |
80 | 111 | | |
81 | 112 | | |
| |||
188 | 219 | | |
189 | 220 | | |
190 | 221 | | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
191 | 242 | | |
192 | 243 | | |
193 | 244 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
21 | 34 | | |
22 | 35 | | |
23 | 36 | | |
| |||
196 | 209 | | |
197 | 210 | | |
198 | 211 | | |
199 | | - | |
200 | | - | |
| 212 | + | |
| 213 | + | |
201 | 214 | | |
202 | 215 | | |
203 | 216 | | |
| |||
241 | 254 | | |
242 | 255 | | |
243 | 256 | | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
244 | 268 | | |
245 | 269 | | |
246 | 270 | | |
| |||
0 commit comments