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

Files d9bf83f2db4463f3e78b5c70a7dc50007df057db
105 lines · 5.5 KB · ~1.4k tok Raw History
1
# val
2
3
Five attackers with the spike, five defenders, abilities on cooldown. First to 13.
4
5
A 5v5 played in a browser, from inside the player's head — the map is a tile
6
grid and the view is a raycaster over it, so there is no 3D library and no
7
dependency at all. One match is always running on the server: open the page
8
and you are given a seat in the round that is happening, taken from a bot
9
where it stands. When a match ends it shows the result for a few seconds and
10
starts a new one, so nobody ever arrives at a frozen board.
11
12
## What is real
13
14
Everything that decides a round is computed on the server and only the
15
server:
16
17
- **Movement, shooting and hit detection.** The client sends intent — a
18
  direction, an angle, a held key — never a position. Damage is by hit
19
  location (how close the ray passes the target) with falloff and armour:
20
  a Vandal head shot kills through full armour at any range, a Phantom's
21
  does not past fifteen metres.
22
- **Line of sight.** A snapshot only carries the enemies that viewer can
23
  see. A wall of the map blocks the sight line, a smoke blocks it, a flash
24
  blinds by the angle you were facing, and a blinded player is sent no
25
  enemies at all. A dead player watches through their living team mates'
26
  eyes rather than through walls, so dying is not a wallhack to call out.
27
  No spawn can see the other spawn: mid is staggered on purpose. A patched
28
  client learns nothing a plain one does not.
29
- **The spike.** 100s round, 4s plant, 45s fuse, 7s defuse with a
30
  checkpoint at 3.5s — a defender killed past half way leaves the defuse
31
  half done for the next one.
32
- **Credits.** 800 to start, 9,000 cap, +200 a kill, +300 to the team on a
33
  plant, loss bonus 1,900 / 2,400 / 2,900. A buy you cannot afford is
34
  refused server-side.
35
- **Who is alive.** The roster under the view lists your five with their
36
  health from the snapshot's own team data, and the kill feed is derived in
37
  `src/arena.ts` by comparing the players either side of a tick — it carries
38
  names, the gun and an age, and never a position.
39
- **Bots** fill every empty seat and use their kit: smokes on the way in,
40
  flashes before the entry, a recon dart, a molly on the planted spike.
41
42
Agents: Ember, Veil, Sable, Mender, Rook — two basics bought with credits
43
(charges last the round), a free signature on a cooldown, and an ultimate
44
paid for with ult points from kills, plants and the two orbs on the map.
45
46
Guns: Classic, Ghost, Sheriff, Spectre, Phantom, Vandal, Operator.
47
48
## What is not
49
50
- The view is a raycaster: walls are one tile tall and flat-shaded, there is
51
  no looking up or down, and everything that is not a wall — bodies, the
52
  spike, ability zones — is a flat billboard scaled by distance and clipped
53
  against the walls.
54
- One map, one match at a time, ten seats. There is no matchmaking, no
55
  ranking and no account: your seat belongs to a session cookie, and a
56
  playing seat is capped one per client address so a cookie — free to mint
57
  again — cannot buy a second one on the other team. A second machine on a
58
  second network is still a second person as far as the server can tell,
59
  the same as any anonymous game without accounts; a socket that does not
60
  get a seat is sent a spectator view with no position in it, never a side.
61
- No overtime — first to 13 ends it at 12–12 too.
62
- Sight is a full circle rather than a view cone — you are drawn what is in
63
  front of you, but the server reveals anything you have line of sight to,
64
  so a team mate behind you still appears on the minimap. The world has no
65
  vertical, so the hit location comes from how precisely the shot passes
66
  the target.
67
- Assists are counted as a field but never awarded.
68
- The live match lives in memory and is gone if the process restarts; only
69
  finished match results are written to SQLite under `FORGE_APP_DATA`.
70
71
## Playing
72
73
Click the view to capture the mouse, then WASD move and the mouse looks;
74
**Esc** gives the pointer back. Click fires, **R** reloads, **F** plants and
75
defuses (hold it), **C / Q / E / X** cast, **B** opens the buy, **Tab**
76
holds the big map open, **V** swaps to the top-down view and **H** or **?**
77
brings back the how-to-play card. On a phone it stays top-down: the left
78
thumb walks and the right thumb aims and fires.
79
80
```sh
81
PORT=8080 FORGE_APP_DATA=./data bun run src/server.ts   # run it here
82
bun test                                                # the check every pull runs
83
VAL_URL=http://127.0.0.1:8080 bun run src/tty.ts        # the terminal interface
84
```
85
86
The terminal plays the same match over the same socket: it draws the grid,
87
the scoreboard and the round, and takes a seat. The exact command that
88
reaches it is in `FORGE_APP_SSH` when the app runs, and the page prints it.
89
90
The forge sets `PORT`, `FORGE_APP_DATA`, `FORGE_APP_ENV`, `FORGE_APP_URL`,
91
`FORGE_APP_SSH` and `FORGE_APP_COMMIT` when it starts the app; `forge.json`
92
says how.
93
94
## Shape
95
96
`src/map.ts` the grid and its geometry · `src/weapons.ts` the damage table ·
97
`src/economy.ts` credits · `src/abilities.ts` the agent kits ·
98
`src/match.ts` the round loop (`tick(state, dt)` is the whole game and takes
99
no clock) · `src/bots.ts` the seats nobody is in · `src/arena.ts` the one
100
live match and its sockets · `src/server.ts` the door · `static/` the browser client —
101
`val.js` wires the socket, the keys and the HUD, `raycast.js` is the ray
102
maths, `scene.js` the first-person view, `topdown.js` the minimap,
103
`gun.js`, `beep.js`, `steer.js`, `roster.js` and `prompt.js` the rest; the server bundles
104
them into one content-named file at start-up. The rules are pure functions and the tests play whole matches
105
headlessly.
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts