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

forked from trevato/cs · proposes · 4 behind

The grid becomes engine: cells, meshing and A*; de_forge stays the game's #16

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`src/engine/grid.ts` now holds the grid as plain data — `{ width, height, cell, cells: T[] }` for any cell type — along with `inside`, `cellAt`, `worldOf`, `colOf`, `rowOf`, `mergeSolid(grid, match, z0, z1, mat)` and `findPath(grid, passable, from, to)`. Every rule about what is solid or walkable arrives as a predicate, so no `"#"`, no `Cell` and no `MAT` crosses into the engine, and the grid is a…

shipped production is serving it deploy ae07c2d

opened this

src/engine/grid.ts now holds the grid as plain data — { width, height, cell, cells: T[] } for any cell type — along with inside, cellAt, worldOf, colOf, rowOf, mergeSolid(grid, match, z0, z1, mat) and findPath(grid, passable, from, to). Every rule about what is solid or walkable arrives as a predicate, so no "#", no Cell and no MAT crosses into the engine, and the grid is always an argument rather than a module-level one, so a second game meshes its own level. src/client/map.ts keeps everything that is Counter-Strike — AREAS, CRATES, the alphabet, MAT, the site slabs, the floor, spawns, sites, siteAt — and exports the same names with the same signatures (plus a new passable(cell), the predicate it hands the engine), so world.ts, bots.ts, observe.ts, models.ts and src/rl/ are untouched; grid is now the Grid<Cell> object rather than the bare array, and nothing imported it.

de_forge does not move. I diffed the new map against the old implementation directly: the 50 boxes, the spawns, the sites and 1000 seeded random paths are byte-identical, and cellAt/walkable agree over the grid and two cells past every edge. map.test.ts keeps its assertions and gains two that pin numbers — boxes.length at 50 with the floor at (-2816,-2816,-32)…(2816,2816,0) and the last crate at (1920,-640,0)…(2048,-512,128), and the T-spawn-to-A route at 33 waypoints, 4506.04 units walked against 4141.87 as the crow flies. src/engine/grid.test.ts works only on grids drawn in the test: a 5×5 with a wall down the middle (the six-step route round the top, hand-checked at 4 + 2 × 1.414 = 6.83 against 4 straight through), a diagonal refused both when both orthogonal neighbours are solid and when one is, an unreachable goal, a goal in the cell you stand in, out-of-bounds at either end, a 3×2 block merging to exactly (-15,-10,0)…(15,10,32), an L splitting into two boxes in reading order, two grids meshing independently, and the corner round-trip of worldOf/colOf/rowOf including the 0.1-unit either side of a cell edge.

I did not replace the linear scan of the open set: the algorithm and its tie-breaking are unchanged, which is why the paths are identical, so no equivalence test was needed. What did change is open.includes(ni) becoming a flag array and the passability predicate being cached one answer per cell per search. 100 searches between random walkable cells, warmed: 0.107 ms each before, 0.104 ms after; cold first pass is 0.19 ms. bun test is 166 pass / 0 fail (151 before, 15 new), boundary.test.ts green with grid.ts inside the engine, and the bundle was rebuilt to static/app.876e3fcdf5.js. On the preview, worth checking that bots still walk out of T spawn to both sites and that the level looks unchanged — those are the two things a half-cell of drift would break.

🤖 Generated with Claude Code

Closes #11

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs