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

Separate the engine from the game, so the next game can start where this one ended #22

Closed opened this has it
opened this

val is about to become the seed of a game template in the forge's scaffold: press New app, choose a game, and the first commit is already server-authoritative, predicted, interpolated and smooth. Your job is the half that has to happen here first — draw the line between the machinery and Valorant, in this repository, with every test still green.

Nothing about behaviour changes. This is a reorganisation, and if it changes a single number I want to know in the pull.

What is engine and what is game

Engine — nothing in it should know what a spike is:

  • glide.ts (collision and speed, shared by both sides), aim.ts (the reaction-and-settle model), the tile map's mechanics (solid tiles, cells of a letter, spawn-ish sets) but not this map's rows.
  • The client feel stack: interp.js, predict.js, presence.js, yaw.js, steer.js.
  • The renderer: raycast.js, scene.js, slices.js, resolution.js, budget.js, topdown.js, gun.js (a kick and a marker are generic).
  • The shape of a served match: the tick loop with its idle gate, seats rationed per address, the spectator snapshot with no positions in it, and per-viewer vision filtering. These are the two security holes we closed the hard way, and no future game should re-derive them.

Game — stays exactly where it is: agents and their kits, the economy, the spike and its timers, the round and match structure, the map's own rows, the bots' site-taking and flash discipline, the HUD's Valorant words.

How to do it

Move the engine pieces under src/engine/ and static/engine/, leave the game where it is, and have the game import the engine rather than the other way round — no engine module may import a game module, and a test should assert that, because it is the property that makes the folder copyable at all.

Where a piece is 90% general with a game-shaped hole in it, take the hole out as a parameter rather than splitting the file in two: the arena's vision filter wants a predicate, the snapshot wants a shape callback, the bot aim wants the weapon's numbers passed in. Where that turns out uglier than leaving it, leave it and say so in the pull — an honest "this one is not general" beats a hollow abstraction.

Keep every path the same from the outside: the routes, the wire, the readout, forge.json. And keep the tests where they belong — engine tests under the engine, game tests with the game, so copying the folder takes its tests with it.

The bar

287 tests green, and the same numbers as now: the per-frame step spread, the snapshot bytes, the blind fraction. Say in the pull which files the engine is, how many lines it is, and what a new game would still have to write for itself. That last sentence is the deliverable as much as the code.

commented

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

Sign in to join this conversation.

↑↓ navigate ↵ open esc close

Keyboard shortcuts