Skip to content

A malformed status crashed the lookup, and eight other quiet failures - #4

Merged
chntnm merged 1 commit into
mainfrom
readme-and-captures
Sep 6, 2026
Merged

A malformed status crashed the lookup, and eight other quiet failures#4
chntnm merged 1 commit into
mainfrom
readme-and-captures

Conversation

@chntnm

@chntnm chntnm commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Three of these are in src/server/lookup.ts, and all three arrived with the same change: the pinned node:https transport replaced fetch, and undici had been doing more than it looked.

new Response(body, { status }) takes a status of 200–599 and refuses a body on the four statuses defined not to carry one. A server is bound by neither. A 205 with an HTML body, or a 600, makes the constructor throw — and it throws inside the https.request response callback, where there is no promise for it to land in. That is an uncaught exception, which takes the dev server, or the serverless invocation, down with it. Construction moved into toResponse: an out-of-range status becomes 502, a bodyless one has its stream torn down, and the callback wraps the call so a throw destroys the socket and rejects instead.

A response dropped without being read — a redirect hop, an error page, a PDF — used to be collected along with its socket, because fetch has a GC backstop and a body timeout. The raw transport has neither, and the abort timer is cleared in finally, so a server that writes a 302 body and never ends it holds a socket and an IncomingMessage open after lookupProduct has returned. In dev that is one socket per request; in a function it is the invocation held open until the platform's hard timeout, which is the exact thing the size cap and the timer exist to prevent. Every early exit cancels the body first now.

And a timeout that lands mid-body no longer looks like one. fetch rejects with an AbortError; node:https destroys the IncomingMessage, so a body still being read fails with an aborted ECONNRESET, and the user was told "could not reach that page" about a page that had answered and then stalled. The catch asks controller.signal.aborted rather than the error's name. Nothing caught any of this because the 504 test stubs the transport with a rejecting AbortError and pinnedFetch is never exercised by a test at all — there are now four over toResponse, one driving the mid-body timeout on fake timers, and one asserting the body is cancelled on all three drop paths.

In walk.ts, the inside branch of contactNormal summed a unit vector toward every edge, and for a rectangle — every wall segment, most of the furniture — the four cancel to exactly zero. The branch always returned null while its comment said it pointed at open air. It takes the nearest edge alone now, which is what the comment always claimed. It was also re-running the broad phase isClear had just run and projecting every edge twice; slide collects the touching blockers once and hands them over.

A move squarely into a surface loses its whole length to the normal and leaves a 1e-13mm residue, which is a new position every frame for a walker standing still. slide returns from itself. The test that claimed to cover this passed for a different reason — its blocked point landed exactly on the hypotenuse, where no edge has a distance and no normal exists, so the axis fallback answered — and it now starts from a point where the projection is what runs. A second test covers a running step long enough to land its centre inside the polygon, which is where the nearest-edge branch earns its place.

scene.bench.ts never measured a collision. At the sparse pitch the walker started inside a placement, so slide took the free-move escape; at the other two the grid fills a corner of a 40×32m hall and the walker stood in open floor. The 0.011–0.028ms §10.4 quoted as the per-frame cost was the cost of a step that touches nothing, and the contact-normal pass it was quoted about was never in it. Two rows now, clear and blocked, seeded against the first row of the grid, with assertStride failing the bench if either walker is standing inside something or stepping where its label does not say. 0.029–0.032ms clear, 0.045–0.049ms blocked; §10.4 carries both and says why the old figure was wrong.

playwright.media.config.ts had reuseExistingServer: true, so a preview left running from an earlier build would be photographed and committed to docs/media as the current application — and every assert in that run checks state, not pixels, so all of them would pass. playwright.config.ts already sets it false, with a comment about the time that happened. And capture.spec.ts said the walker seeds facing east, which is why the clip has to look 90° first; it seeds facing north.

779 unit tests, 124 e2e, tsc and eslint clean.

Three of these are in `src/server/lookup.ts`, and all three arrived with the same
change: the pinned `node:https` transport replaced `fetch`, and undici had been
doing more than it looked.

`new Response(body, { status })` takes a status of 200–599 and refuses a body on
the four statuses defined not to carry one. A server is bound by neither. A 205
with an HTML body, or a 600, makes the constructor throw — and it throws inside
the `https.request` response callback, where there is no promise for it to land
in. That is an uncaught exception, which takes the dev server, or the serverless
invocation, down with it. Construction moved into `toResponse`: an out-of-range
status becomes 502, a bodyless one has its stream torn down, and the callback
wraps the call so a throw destroys the socket and rejects instead.

A response dropped without being read — a redirect hop, an error page, a PDF —
used to be collected along with its socket, because `fetch` has a GC backstop and
a body timeout. The raw transport has neither, and the abort timer is cleared in
`finally`, so a server that writes a 302 body and never ends it holds a socket
and an `IncomingMessage` open after `lookupProduct` has returned. In dev that is
one socket per request; in a function it is the invocation held open until the
platform's hard timeout, which is the exact thing the size cap and the timer
exist to prevent. Every early exit cancels the body first now.

And a timeout that lands mid-body no longer looks like one. `fetch` rejects with
an `AbortError`; `node:https` destroys the `IncomingMessage`, so a body still
being read fails with an `aborted` ECONNRESET, and the user was told "could not
reach that page" about a page that had answered and then stalled. The catch asks
`controller.signal.aborted` rather than the error's name. Nothing caught any of
this because the 504 test stubs the transport with a rejecting `AbortError` and
`pinnedFetch` is never exercised by a test at all — there are now four over
`toResponse`, one driving the mid-body timeout on fake timers, and one asserting
the body is cancelled on all three drop paths.

In `walk.ts`, the `inside` branch of `contactNormal` summed a unit vector toward
every edge, and for a rectangle — every wall segment, most of the furniture — the
four cancel to exactly zero. The branch always returned null while its comment
said it pointed at open air. It takes the nearest edge alone now, which is what
the comment always claimed. It was also re-running the broad phase `isClear` had
just run and projecting every edge twice; `slide` collects the touching blockers
once and hands them over.

A move squarely into a surface loses its whole length to the normal and leaves a
1e-13mm residue, which is a new position every frame for a walker standing still.
`slide` returns `from` itself. The test that claimed to cover this passed for a
different reason — its blocked point landed exactly on the hypotenuse, where no
edge has a distance and no normal exists, so the axis fallback answered — and it
now starts from a point where the projection is what runs. A second test covers a
running step long enough to land its centre inside the polygon, which is where
the nearest-edge branch earns its place.

`scene.bench.ts` never measured a collision. At the sparse pitch the walker
started inside a placement, so `slide` took the free-move escape; at the other
two the grid fills a corner of a 40×32m hall and the walker stood in open floor.
The 0.011–0.028ms §10.4 quoted as the per-frame cost was the cost of a step that
touches nothing, and the contact-normal pass it was quoted about was never in it.
Two rows now, clear and blocked, seeded against the first row of the grid, with
`assertStride` failing the bench if either walker is standing inside something or
stepping where its label does not say. 0.029–0.032ms clear, 0.045–0.049ms
blocked; §10.4 carries both and says why the old figure was wrong.

`playwright.media.config.ts` had `reuseExistingServer: true`, so a preview left
running from an earlier build would be photographed and committed to `docs/media`
as the current application — and every assert in that run checks state, not
pixels, so all of them would pass. `playwright.config.ts` already sets it false,
with a comment about the time that happened. And `capture.spec.ts` said the
walker seeds facing east, which is why the clip has to look 90° first; it seeds
facing north.

779 unit tests, 124 e2e, `tsc` and `eslint` clean.
@chntnm
chntnm merged commit 66c1170 into main Sep 6, 2026
1 check passed
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.

1 participant