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

Five things a second game found in the engine #24

Closed opened this
opened this

The engine was lifted out of val and into the forge's game template, and a second game has now been built on it — benjivers/skirmish#2, hide and seek on the sight-line engine, by fable. Having a second caller found things one caller never could. These are in the engine, so they are in val, and the template's copy is byte-for-byte identical — which is why they belong here rather than there: fixing them in the template would silently fork the two.

Four of the findings were the template's own bugs and are fixed in apps/forge#11. These five are ours.

1. walkDistance(from, to) caches on the wrong-looking argument, and says nothing about it. It caches a flood field keyed on to. A bot scoring two hundred candidate cells, with the candidate as to, thrashes the 64-entry cache. Reversing the arguments took 300 simulated seconds from 8.7s to 0.28s — a 31× difference, from an argument order the doc comment gives no reason to care about. val's bots may be paying this today; worth measuring botThink before and after. The comment should say which argument is the cached one.

2. snapshotFor hands an unknown viewer somebody else's eyes. const me = findPlayer(viewerId) ?? players[0]! — if the id is not found, the snapshot is built for the first player in the match, with that player's vision. Not reachable today as far as I can tell, but the failure mode is a seat seeing another team's positions, which is the exact class of bug we have already fixed twice here. It should refuse rather than substitute.

3. mate bakes exactly two sides into the renderer. scene.js and topdown.js colour a body by p.mate being true or false. Two roles map onto it fine (skirmish used hunter/hider). Three sides, or a free-for-all, has nowhere to go. Not urgent; worth knowing before someone tries.

4. val's vocabulary is still inside the engine. Now that these files are copied into other games, the words travel:

  • src/engine/aim.ts refers to a weapons.ts that does not exist in a template repo, and to "a Vandal's spread".
  • static/engine/predict.js explains its own hold-rather-than-snap rule with "the spawn pen in the buy phase".
  • src/engine/boundary.test.ts's forbidden-word scan is val's nouns, so a new game inherits a guard against a game it has never played — and a game that legitimately has a "spike" trips it.
  • Seat.board is named after val's sticky field even though sticky is configurable per game.

5. The engine's own render tests cannot catch a game that sends the wrong shape. This is the important one, and it is why apps/forge#11 exists. scene.test.ts and friends build their own snapshots, with alive/hp/mate in them, and then assert the renderer draws correctly — so they pass whatever a real game sends. The template sent down instead of alive and every enemy rendered as a corpse with 145 tests green. val is fine because val sends the right shape, but nothing in the engine states that shape as a contract or checks a caller against it. A bodyShape() assertion the engine exports, or a documented type, would make the next game's mistake loud instead of silent.

Nothing here is on fire for val. Filing it so the two copies do not drift apart and so the next game inherits the fixes rather than the surprises.

commented

All five done: 1 walkDistance cache order in #39, 2 unknown viewer + 4 val words in the engine in #32, 3 colours by side in #43, 5 body-shape contract + real-snapshot test in #41.

closed this
Sign in to join this conversation.

↑↓ navigate ↵ open esc close

Keyboard shortcuts