wren/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]>
2 files
changed +19
−2
src/client.test.ts +13 −1
| @@ -187,6 +187,14 @@ const until = async (what: () => boolean, ms = 4000) => { | ||
| 187 | 187 | // ------------------------------------------------------------------ the run |
| 188 | 188 | |
| 189 | 189 | 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); | |
| 190 | 198 | const first = await fetch(base + "/"); |
| 191 | 199 | sessionCookie = (first.headers.get("set-cookie") ?? "").split(";")[0] ?? ""; |
| 192 | 200 | const page = await first.text(); |
| @@ -236,6 +244,10 @@ test("the bundled client connects, paints the HUD and draws a frame", async () = | ||
| 236 | 244 | // blits it, with the minimap's tiles blitted too. It is a picture, but it |
| 237 | 245 | // is not one fill a screen column any more, nor one a map tile: the map has |
| 238 | 246 | // 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. | |
| 239 | 251 | const tally = () => ({ |
| 240 | 252 | fills: drawn.filter((op) => op.startsWith("fillRect")).length, |
| 241 | 253 | 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 () = | ||
| 246 | 258 | const fp = tally(); |
| 247 | 259 | expect(fp.blits).toBeGreaterThanOrEqual(2); // the world, and the map's tiles |
| 248 | 260 | expect(fp.fills).toBeGreaterThan(40); |
| 249 | expect(fp.fills).toBeLessThan(570); | |
| 261 | expect(fp.fills).toBeLessThan(900); | |
| 250 | 262 | |
| 251 | 263 | // Tab swaps that frame for the whole map. Its tiles are drawn once into a |
| 252 | 264 | // 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) { | ||
| 181 | 181 | if (insideWall(local, world) || (refused !== null && refused(local.x, local.y))) { |
| 182 | 182 | local.x = wasX; |
| 183 | 183 | 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); | |
| 185 | 190 | } |
| 186 | 191 | return local; |
| 187 | 192 | } |