Stop discarding the render failure's cause, and refuse startingLakePositions (#227) - #365
Merged
Conversation
…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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
elevationRender.worker.tsserve()had notry, so it threw instead of postingRenderErrorMessageuseElevationPreview.tsonerrorwas declared with no argument and substituted a constantElevationPreviewPanel.vuecatchsubstituted"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
onmessagefires theworker's
errorevent, and the host reads that as a crashed worker: itterminates the slot and rejects every tile the slot was holding. One bad request
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 leavesits siblings running.
"Preview failed."is also the stringPreviewPanel.vueproduces from theunrelated Docker preview service, which #341 records having been confused by.
The startingLakePositions refusal
Now the first statement of
runRenderRequest, before the planet split. Thetwo checks it replaces sat inside leaves of the view/planet dispatch and missed
three cases between them:
findIslandspostsplanet: "fulgora", view: "landmask".view: "landmask"on Nauvis - in the outer view test, absent from theNauvis gate's allowlist.
It refuses rather than ignores because the type outlives every consumer:
eval/ctx.tsandexpressions/elevationIsland.tssurvive #227 while everymodule 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_POINTSclause deliberately stays. Its fallback is theTypeScript 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
serve()throws again instead of postingonerrorsubstitutes the constant againcarries the crash's own messageshows the render's OWN messagerefuses on every planet and viewlength > 0testrefuses an EMPTY list tooThe fourth is why there is a block sweeping Vulcanus, Fulgora and Nauvis
landmask: every other assertion in that describe is Nauvislakes, so a guardput 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
!== undefinedon the stated grounds that a truthinesstest would let
[]through. Planting truthiness came back GREEN:[]istruthy 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 lengthtest, 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.
!== undefinedstays, because it states the type's own distinction rather thanrelying 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 assertedthe opposite of what is now true and became the refusal's own coverage.
Gate
pnpm run verifygreen in 12m42s,VERIFY_RC=0read out of the log rather thanoff the pipe. 1606 tests, up 7.