rook/val Public Public: anyone can read this; nobody can change it.

forked from benjivers/val

workbenchthis machine

A hold never outranks the answer, and the client test starts from a free seat

Two things the eight-run soak turned up.

The hold I added for a refused step could outrank the server's own position:
a barrier dropped on a player standing still leaves them inside it on the
server (movePlayer only pushes out of zones when moving), the client refused
that position, reverted to the last good place, and kept doing so every frame
— the drawn body a room away from the answer for as long as the barrier
lasted. The client test caught it as a 1,099-unit lead. A hold now gives way
to the answer past the snap distance, the same as it does inside a wall.

client.test.ts connected without freeing the shared loopback address, so a
socket another file had closed a moment earlier could still hold the one
rationed seat and the client was handed a spectator view — the coin-flip
failure the issue warned about. It now frees the address first, the way the
server's own seat tests do. Its first-person fill bound was the tile count,
which a view down a long corridor legitimately exceeds (580, 635 seen); the
bound is now the column count, which is the claim the test makes.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Fable committed 229dad7 Browse files
2 files changed +19 −2
src/client.test.ts +13 −1
@@ -187,6 +187,14 @@ const until = async (what: () => boolean, ms = 4000) => {
187187 // ------------------------------------------------------------------ the run
188188
189189 test("the bundled client connects, paints the HUD and draws a frame", async () => {
190 // Every test in this run reaches the server from the one loopback address,
191 // and a playing seat is rationed per address on purpose. A socket another
192 // file closed a moment ago may not have given its seat back yet, and this
193 // client would then be handed a spectator view and wait four seconds for a
194 // health readout that never comes — the coin-flip failure the suite had.
195 // Start from a clean slate, the way the server's own seat tests do.
196 const { releaseAddress } = await import("./arena.ts");
197 for (const address of ["127.0.0.1", "::1", "::ffff:127.0.0.1"]) releaseAddress(address);
190198 const first = await fetch(base + "/");
191199 sessionCookie = (first.headers.get("set-cookie") ?? "").split(";")[0] ?? "";
192200 const page = await first.text();
@@ -236,6 +244,10 @@ test("the bundled client connects, paints the HUD and draws a frame", async () =
236244 // blits it, with the minimap's tiles blitted too. It is a picture, but it
237245 // is not one fill a screen column any more, nor one a map tile: the map has
238246 // 570 tiles and the canvas 900 columns, and the frame used to pay for both.
247 // The minimap's tiles are the blit; the walls are a run a fill, and how
248 // many runs a view has depends on where the seat happens to be standing —
249 // a long corridor is many depths — so the bound is the columns, which no
250 // view can reach, rather than the tile count, which a corridor can.
239251 const tally = () => ({
240252 fills: drawn.filter((op) => op.startsWith("fillRect")).length,
241253 blits: drawn.filter((op) => op.startsWith("drawImage")).length,
@@ -246,7 +258,7 @@ test("the bundled client connects, paints the HUD and draws a frame", async () =
246258 const fp = tally();
247259 expect(fp.blits).toBeGreaterThanOrEqual(2); // the world, and the map's tiles
248260 expect(fp.fills).toBeGreaterThan(40);
249 expect(fp.fills).toBeLessThan(570);
261 expect(fp.fills).toBeLessThan(900);
250262
251263 // Tab swaps that frame for the whole map. Its tiles are drawn once into a
252264 // canvas of their own and blitted from then on: the first such frame pays
static/predict.js +6 −1
@@ -181,7 +181,12 @@ export function stepLocal(local, me, held, speed, age, dt, world, refused) {
181181 if (insideWall(local, world) || (refused !== null && refused(local.x, local.y))) {
182182 local.x = wasX;
183183 local.y = wasY;
184 if (insideWall(local, world)) anchor(local, me);
184 // Holding is for a step the server would refuse, never for the server's
185 // own answer: a barrier dropped on somebody standing still leaves them
186 // inside it on the server too, and a hold that outranked that would keep
187 // the drawn body wherever it last stood, a room away, for as long as the
188 // barrier lasted. Past the snap distance, or in a wall, the answer wins.
189 if (insideWall(local, world) || Math.hypot(local.x - me.x, local.y - me.y) > SNAP) anchor(local, me);
185190 }
186191 return local;
187192 }
↑↓ navigate ↵ open esc close

Keyboard shortcuts