benjivers/val Public Public: anyone can read this; the crew can change it.

A zone's position is sent to people who cannot see it #25

Closed opened this has it
opened this

This is a third leak of the same kind we have now fixed twice, and it is still live. I proved it before filing; you do not need to reproduce it, but your fix has to make this test fail first and pass after.

The hole. snapshotShape in src/arena.ts filters BODIES through visible(), and sends zones filtered only by whether a trip is yours:

zones: state.zones
  .filter((z) => (z.kind === "trip" ? z.side === me.side : true))
  .map((z) => ({ k: z.kind, x: round2(z.x), y: round2(z.y), r: z.r, ... })),

So every smoke, wall, molly and flash position reaches every player regardless of line of sight. Measured, with a smoke placed on an enemy standing behind a wall from me:

zones in my snapshot: 11
a smoke on an enemy I cannot see: SENT at (1199.2, 647.7)
that enemy is in my player list: false      <- the body filter worked
enemy really is behind a wall: true
enemy x (1199.2) appears in the payload: true   <- and their position arrived anyway

Abilities land at or near where the thrower stands — a molly at their feet, a wall under them — so a client reading zones has a partial position oracle on people it cannot see. Same shape as the shot leak we just fixed in the forge's game template: a non-body field carrying a position, unfiltered, because the arena filters bodies and hands the game hook the whole state.

The rule to apply. A zone goes out when the viewer could actually know it is there:

  • the viewer is inside it or at its edge (distance <= z.r + BODY_R) — otherwise standing in your own smoke makes it vanish, and note sightBlocked counts zones, so do NOT filter with sightBlocked; and
  • otherwise, no wall between the viewer and the zone (wallBetween), which is the same test the shot filter uses.

Get this wrong in the other direction and a smoke you are standing in disappears, which is worse than the leak. Both cases need a test.

What I want back.

  1. The filter, with the two cases above.
  2. A generic test, not a specific one. Stringify the WHOLE snapshot for a viewer and assert that no id and no rounded coordinate of any body that viewer cannot see appears anywhere in it. That test would have caught all three of our leaks, including this one, and it will catch the fourth in whatever field someone adds next. The forge's game template has one of these now if you want the shape of it.
  3. Numbers: snapshot bytes before and after (11 zones a snapshot is not free either), and confirmation that the test count only went up.
  4. Say explicitly whether anything else in the snapshot carries a position that is not filtered. orbs are fixed map furniture and fine; spike.x/y is already gated on planted-or-attacker; board, feed and events carry names, not places. I believe that list is complete — check me.

Do not touch src/engine/. If the honest fix needs something from the engine, say so in the pull and pass it in instead.

commented

Done in #27 (merged 2026-09); closing — that merge predates closing-on-merge.

commented

The fix and the generic test are both already on main, so I made no changes and pushed nothing.

  • Filter: src/arena.ts:163 sends a zone only if the viewer is inside it (<= z.r + PLAYER_R) or no wall sits between them (wallBetween). It does not use sightBlocked.
  • Generic test: src/server.test.ts:162 stringifies the whole snapshot and checks that no id or coordinate of a body the viewer can't see appears in it.
  • Suite: all 298 tests pass on this branch.

I did not check the issue's other three asks: the in-smoke and behind-a-wall tests, the snapshot byte counts, and whether any other snapshot field carries an unfiltered position.

defe010, the commit that made the change, is already contained in main. This branch has no commits ahead of main. The branch is also behind main: its diff against main shows otel and README changes that main has and the branch lacks, not work of mine.

Sign in to join this conversation.

↑↓ navigate ↵ open esc close

Keyboard shortcuts