rook/val Public Public: anyone can read this; nobody can change it.
forked from benjivers/val
workbenchthis machine
Files f06c94705a19e63a135dcb573ff98fcaae80d78f
| 1 | # val |
| 2 | |
| 3 | Five attackers with the spike, five defenders, abilities on cooldown. First to 13. |
| 4 | |
| 5 | A top-down 5v5 played in a browser. One match is always running on the |
| 6 | server: open the page and you are given a seat in the round that is |
| 7 | happening, taken from a bot where it stands. When a match ends it shows the |
| 8 | result for a few seconds and starts a new one, so nobody ever arrives at a |
| 9 | frozen board. |
| 10 | |
| 11 | ## What is real |
| 12 | |
| 13 | Everything that decides a round is computed on the server and only the |
| 14 | server: |
| 15 | |
| 16 | - **Movement, shooting and hit detection.** The client sends intent — a |
| 17 | direction, an angle, a held key — never a position. Damage is by hit |
| 18 | location (how close the ray passes the target) with falloff and armour: |
| 19 | a Vandal head shot kills through full armour at any range, a Phantom's |
| 20 | does not past fifteen metres. |
| 21 | - **Line of sight.** A snapshot only carries the enemies that viewer can |
| 22 | see. A wall of the map blocks the sight line, a smoke blocks it, a flash |
| 23 | blinds by the angle you were facing, and a blinded player is sent no |
| 24 | enemies at all. A dead player watches through their living team mates' |
| 25 | eyes rather than through walls, so dying is not a wallhack to call out. |
| 26 | No spawn can see the other spawn: mid is staggered on purpose. A patched |
| 27 | client learns nothing a plain one does not. |
| 28 | - **The spike.** 100s round, 4s plant, 45s fuse, 7s defuse with a |
| 29 | checkpoint at 3.5s — a defender killed past half way leaves the defuse |
| 30 | half done for the next one. |
| 31 | - **Credits.** 800 to start, 9,000 cap, +200 a kill, +300 to the team on a |
| 32 | plant, loss bonus 1,900 / 2,400 / 2,900. A buy you cannot afford is |
| 33 | refused server-side. |
| 34 | - **Bots** fill every empty seat and use their kit: smokes on the way in, |
| 35 | flashes before the entry, a recon dart, a molly on the planted spike. |
| 36 | |
| 37 | Agents: Ember, Veil, Sable, Mender, Rook — two basics bought with credits |
| 38 | (charges last the round), a free signature on a cooldown, and an ultimate |
| 39 | paid for with ult points from kills, plants and the two orbs on the map. |
| 40 | |
| 41 | Guns: Classic, Ghost, Sheriff, Spectre, Phantom, Vandal, Operator. |
| 42 | |
| 43 | ## What is not |
| 44 | |
| 45 | - One map, one match at a time, ten seats. There is no matchmaking, no |
| 46 | ranking and no account: your seat belongs to a session cookie. |
| 47 | - No overtime — first to 13 ends it at 12–12 too. |
| 48 | - Sight is a full circle rather than a view cone; top-down has no vertical, |
| 49 | so the hit location comes from how precisely the shot passes the target. |
| 50 | - Assists are counted as a field but never awarded. |
| 51 | - The live match lives in memory and is gone if the process restarts; only |
| 52 | finished match results are written to SQLite under `FORGE_APP_DATA`. |
| 53 | |
| 54 | ## Playing |
| 55 | |
| 56 | WASD move, the mouse aims, click fires, **R** reloads, **F** plants and |
| 57 | defuses (hold it), **C / Q / E / X** cast, **B** opens the buy. On a phone |
| 58 | the left thumb walks and the right thumb aims and fires. |
| 59 | |
| 60 | sh |
| 61 | PORT=8080 FORGE_APP_DATA=./data bun run src/server.ts # run it here |
| 62 | bun test # the check every pull runs |
| 63 | VAL_URL=http://127.0.0.1:8080 bun run src/tty.ts # the terminal interface |
| 64 | |
| 65 | |
| 66 | The terminal plays the same match over the same socket: it draws the grid, |
| 67 | the scoreboard and the round, and takes a seat. The exact command that |
| 68 | reaches it is in `FORGE_APP_SSH` when the app runs, and the page prints it. |
| 69 | |
| 70 | The forge sets `PORT`, `FORGE_APP_DATA`, `FORGE_APP_ENV`, `FORGE_APP_URL`, |
| 71 | `FORGE_APP_SSH` and `FORGE_APP_COMMIT` when it starts the app; `forge.json` |
| 72 | says how. |
| 73 | |
| 74 | ## Shape |
| 75 | |
| 76 | `src/map.ts` the grid and its geometry · `src/weapons.ts` the damage table · |
| 77 | `src/economy.ts` credits · `src/abilities.ts` the agent kits · |
| 78 | `src/match.ts` the round loop (`tick(state, dt)` is the whole game and takes |
| 79 | no clock) · `src/bots.ts` the seats nobody is in · `src/arena.ts` the one |
| 80 | live match and its sockets · `src/server.ts` the door · `static/val.js` the |
| 81 | browser. The rules are pure functions and the tests play whole matches |
| 82 | headlessly. |