Skip to content

Stop discarding the render failure's cause, and refuse startingLakePositions (#227) - #365

Merged
wormeyman merged 1 commit into
mainfrom
227-error-path
Aug 31, 2026
Merged

Stop discarding the render failure's cause, and refuse startingLakePositions (#227)#365
wormeyman merged 1 commit into
mainfrom
227-error-path

Conversation

@wormeyman

Copy link
Copy Markdown
Collaborator

Second of three PRs finishing #227's phase 7. PR 1 was #362; the deletion itself
follows this one.

Closes #341.

A render failure produced no usable message anywhere

The cause was discarded three separate times:

step file what it did
producer elevationRender.worker.ts serve() had no try, so it threw instead of posting RenderErrorMessage
transport useElevationPreview.ts onerror was declared with no argument and substituted a constant
consumer ElevationPreviewPanel.vue a bare catch substituted "Preview failed."

#341 covers only the consumer. Fixing any one alone still leaves a useless
message, so the three go together.

The producer is worse than a lost string. A throw out of onmessage fires the
worker's error event, and the host reads that as a crashed worker: it
terminates the slot and rejects every tile the slot was holding. One bad request

  • a spawn list over the ABI cap, a view the module refuses - therefore failed
    every good render in flight beside it, and reported six words that named none of
    them. serve() now catches, settles exactly the request that failed, and leaves
    its siblings running.

"Preview failed." is also the string PreviewPanel.vue produces from the
unrelated Docker preview service, which #341 records having been confused by.

The startingLakePositions refusal

Now the first statement of runRenderRequest, before the planet split. The
two checks it replaces sat inside leaves of the view/planet dispatch and missed
three cases between them:

  • Vulcanus - that branch returns before the Nauvis gate is ever evaluated.
  • Fulgora - likewise, and this one is live: findIslands posts
    planet: "fulgora", view: "landmask".
  • view: "landmask" on Nauvis - in the outer view test, absent from the
    Nauvis gate's allowlist.

It refuses rather than ignores because the type outlives every consumer:
eval/ctx.ts and expressions/elevationIsland.ts survive #227 while every
module that acted on the override does not. Accepting and ignoring it would
render a different map than the caller asked for and say nothing.

The NAUVIS_MAX_STARTING_POINTS clause deliberately stays. Its fallback is the
TypeScript path, so it goes with the deletion rather than here - and removing it
then is only safe because serve() is wrapped now.

Planted, not predicted

plant result
serve() throws again instead of posting RED - both worker error tests
onerror substitutes the constant again RED - carries the crash's own message
the panel discards the cause again RED - shows the render's OWN message
the guard moves into the elevation dispatch leaf RED - refuses on every planet and view
the guard gains a length > 0 test RED - refuses an EMPTY list too

The fourth is why there is a block sweeping Vulcanus, Fulgora and Nauvis
landmask: every other assertion in that describe is Nauvis lakes, so a guard
put back into a leaf would satisfy all of them and still let the three real holes
through.

One plant corrected a note rather than the code

The guard was written !== undefined on the stated grounds that a truthiness
test would let [] through. Planting truthiness came back GREEN: [] is
truthy in JavaScript, so both forms refuse it and the two agree on every value
the field can legally hold. The form that does let [] through is a length
test, which is the plausible mistake because it reads like a tidy-up. That is
what the empty-list block now guards against, and the comment says so.
!== undefined stays, because it states the type's own distinction rather than
relying on the coincidence.

Two tests strengthened rather than added

The panel's error test asserted only that an error element existed, which
stayed true the whole time the cause was being thrown away - so #341 was
invisible to it. wasmElevationRenderParity.spec.ts's carve-out block asserted
the opposite of what is now true and became the refusal's own coverage.

Gate

pnpm run verify green in 12m42s, VERIFY_RC=0 read out of the log rather than
off the pipe. 1606 tests, up 7.

…sitions (#227)

A render that failed inside the worker produced no usable message anywhere,
because the cause was thrown away three separate times:

| step | what it did |
| --- | --- |
| `elevationRender.worker.ts` | `serve()` had no `try`, so it threw instead of posting |
| `useElevationPreview.ts` | `onerror` took no argument and substituted a constant |
| `ElevationPreviewPanel.vue` | a bare `catch` substituted "Preview failed." |

Issue #341 covers the third. Fixing any one alone still leaves a useless
message, so all three go together.

The first one is worse than a lost string. A throw out of `onmessage` fires the
worker's `error` event, which the host reads as a crashed worker: it terminates
the slot and rejects every tile the slot was holding. So one bad request failed
every good one beside it, and reported six words that named none of them.
`serve()` now catches, settles exactly the request that failed, and leaves its
siblings alone.

"Preview failed." was also the string `PreviewPanel.vue` produces from the
unrelated Docker preview service, which #341 records having been confused by.

## The startingLakePositions refusal

A caller-supplied `startingLakePositions` is now refused as the FIRST statement
of `runRenderRequest`, before the planet split. The two checks it replaces sat
inside leaves of the view/planet dispatch and missed three cases between them:
the Vulcanus branch returns before the Nauvis gate is evaluated, the Fulgora
branch likewise - and that one is live, since `findIslands` posts
`planet: "fulgora", view: "landmask"` - and `"landmask"` on Nauvis is in the
outer view test but absent from the Nauvis gate's allowlist.

It refuses rather than ignores because the TYPE outlives every consumer:
`eval/ctx.ts` and `expressions/elevationIsland.ts` survive #227 while every
module that acted on the override does not. Accepting and ignoring it would
render a different map than the caller asked for and say nothing.

The `NAUVIS_MAX_STARTING_POINTS` clause deliberately stays. Its fallback is the
TypeScript path, so it goes with the deletion, not here - and it is safe to
remove then only because `serve()` is wrapped now.

## Planted, not predicted

| plant | result |
| --- | --- |
| `serve()` throws again instead of posting | RED - both worker error tests |
| `onerror` substitutes the constant again | RED - `carries the crash's own message` |
| the panel discards the cause again | RED - `shows the render's OWN message` |
| the guard moves into the elevation dispatch leaf | RED - `refuses on every planet and view` |
| the guard gains a `length > 0` test | RED - `refuses an EMPTY list too` |

One plant corrected a note rather than the code. The guard was written
`!== undefined` on the stated grounds that a truthiness test would let `[]`
through; planting truthiness came back GREEN, because `[]` is truthy in
JavaScript and both forms refuse it. The form that does let `[]` through is a
length test, so that is what the empty-list block now guards against, and the
comment says so. `!== undefined` stays, because it states the type's own
distinction rather than relying on that coincidence.

Two tests were strengthened rather than added. The panel's error test asserted
only that an error element EXISTED, which stayed true the whole time the cause
was being discarded.

Closes #341.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgrxV4zcQLSsWu4XymFMBV
@wormeyman
wormeyman merged commit ebee7fa into main Aug 31, 2026
9 checks passed
wormeyman added a commit that referenced this pull request Aug 31, 2026
Phase 7's deletion. The app has rendered from WASM since #362, so the
TypeScript math under `src/noise/` has no live caller left. This removes it and
rewires the twelve specs that were still reading it.

42 source files go, not the 43 in `deadlist.txt`:
`src/noise/resources/vulcanusResourceCatalog.ts` stays and is trimmed in place,
223 lines to 113. Its two math functions (`makeVulcanusOreFootprint`,
`sulfuricAcidGeyserProbability`), the placement threshold and the three
closure-valued fields are all owned by Rust now; what is left is the order and
the map colours, which `wasmVulcanusRenderParity` grades the engine's pixels
against. #227's done-when is that `src/noise/` holds orchestration and catalogs
only, and that file is now a catalog. It has no imports at all.

`src/noise/preview/elevationRenderRequest.ts` loses its TypeScript fallback
arms, 309 lines out and 97 in. The four views that used to fall through to
plain TypeScript terrain were normalised onto their planet's terrain code in
#362, and the error path and the `startingLakePositions` guard landed in #365,
so nothing reaches the fallback any more.

## The parity specs are converted, not deleted

`tier2Frozen.ts` and `tier3Frozen.ts` were built in #345 and #360 for exactly
this step: `expectFrozen` takes its reference arm as an optional last argument,
so a spec whose TypeScript is gone drops that argument and keeps grading the
engine against a value captured while the two ports demonstrably agreed.
Deleting the specs instead would take the port's arithmetic out of
`wasm32-unknown-unknown` altogether, which is the one thing `cargo test` cannot
cover and the reason the freeze exists.

Tier 2: 68 of Vulcanus's 74 fields keep both arms, and the six whose reference
implementation went (`geyserProbability`, `cliffinessBasic`,
`decorativeKnockout`, `rockHuge`, `rockBig`, `rockDensity`) drop to
frozen-only. `NO_TS_ARM` names them and a new test asserts the list matches what
`tsFields` actually withholds, so a seventh field losing its arm fails rather
than downgrades quietly. Nauvis loses its arm entirely - its whole expression
core was in the dead set.

Tier 3 needed the same treatment and the notes had cleared it. Those specs have
no dead-set imports, which is what was checked; they get their reference arm by
calling `runRenderRequest(req)` with the engine argument left off, which the
rewrite above now refuses. That was 76 failing tests across seven files, and
`tier3Frozen.ts` had written down the trap in advance: after the deletion the
no-engine call "is not a weaker arm, it is the SAME arm - so the comparison
would pass while grading nothing".

`test/tiledEquality.spec.ts` is the one that changes character rather than
shrinking. It passed no engine at all, so it graded the TypeScript renderers
and could not see the WASM gate; it now runs every render through the engine,
which closes a gap the #227 notes had already identified.

## Anti-vacuity guards were re-based, not dropped, wherever the claim survived

Vulcanus's slider guard (still 50 of 74 fields) and window guard (still all 74)
now read the engine; the counts are unchanged because every field with both
arms is pinned to the same frozen value. The off-grid sweep's "these really are
different points" check moved the same way. The routing tests in both render
specs became the sharper statement they always stood in for: with no fallback
left, a view that reaches the engine is exactly a view that REFUSES to render
without one.

Two of those rewrites failed on the first run, and both were findings rather
than test bugs. Recorded where they were measured:

- `checksum_pow` takes its exponent as an `f32`, so the 2.0000001 that used to
  prove the dispatcher leaves the squaring branch narrows to exactly 2. The
  perturbation is now one f32 ULP, asserted to survive the boundary.
- `checksum_distance_from_nearest_point` folds
  `f64::from(distance_from_nearest_point(...))`, and that function returns an
  `f32`, so an f64 ULP on the cap comes back as the same number.

Three tests flipped because they said in their own comments that they should.
`viewNormalisation`'s last block was labelled "the arm #227 deletes";
`elevationRenderRequest.spec`'s KNOWN HOLE row asked to "flip to asserting the
refusal" if `runRenderRequest` ever refused a Nauvis-only view on another
planet, which is what the rewrite does; and `wasmNauvisRenderParity`'s ABI-cap
row said it "belongs to the carve-out, and the #227 deletion removes both
together". The cap is now a refusal, with a companion case at eight points so
the refusal is about the list's length rather than its presence.

Seven Nauvis tier-2 guards could not be re-based: each counts how many swept
positions satisfy a predicate, and the export returns an order-sensitive fold
that cannot be decomposed back into counts. They are deleted here and their
frozen numbers recorded in a follow-up issue, so restoring them behind a
predicate-counting export re-measures rather than re-derives.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A bare catch in ElevationPreviewPanel discards every render failure's cause

1 participant