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

forked from trevato/cs · proposes

procedurally generated maps #147

Merged opened this in answer to

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

Merged

`/` answers in 8 ms with the map field in the first paint, `bun test` is green (647 tests), and the branch is pushed.

shipped production is serving it deploy ae07c2d

opened this

/ answers in 8 ms with the map field in the first paint, bun test is green (647 tests), and the branch is pushed.

---

src/client/generate.ts turns a number into a map: rooms, corridors, two bombsites, two spawns and cover, carved into the letters src/client/cells.ts reads — so a generated map is one more value of Level and nothing in the simulation, the bots or the renderer learns anything to play it. It is fair by construction: every cell is stamped twice, once at (col, row) and once half a circle away with t becoming c and A becoming B, and the sites are an odd number of cells on both sides so the plant point is the middle of a cell rather than a seam that rounds the wrong way on one side. Over 600 seeds the walk from CT spawn to A and from T spawn to B are the same length to the unit, both ways round. It is playable by construction: rooms hang off a spanning tree of corridors through the middle room that both halves share, region rubs out what the tree missed, a crate that would cut the map in two is taken back off, and flaws reads the finished level — spawns, sites, a path from each spawn to each site, no floor that cannot be walked to — before it is handed over. 1,000 seeds, none needed a second attempt; a map costs 2.1 ms.

Three things moved to make room for it. src/client/cells.ts is the alphabet both maps are written in and levelOf is the reading of it, so map.ts is now de_forge's areas and crates and nothing else — both golden digests are unmoved to the byte. region joins the engine's grid beside findPath: every cell reachable from one you name, by the same eight steps with the same refusal to cut a corner. Renderer.replace(name, mesh) uploads one mesh again under a name the renderer already holds and gives the old one back, buffers included, because a match on another map needs the geometry changed and nothing else rebuilt.

What to look at in the preview: the Map field on the menu — pick generated and the match id becomes the seed, so every match is a layout nobody has played; leave it on de_forge and not one byte of geometry is swapped. src/client/generate.test.ts is where the claims are checked, including a pinned ten-second round of the real game with the real bots on gen_5, and page(grid) prints a map as text if you want to read one.

What I chose not to do: #96's half of this — the choice is not remembered between visits, the map is not posted with the result and the leaderboard says nothing about it, and there is no server-side list of known map names. That is server and menu plumbing and it belongs to the issue that waits on this one. I also left the sites deliberately bare: crates go on floor only, never on a bombsite, because a crate inside the site moves the centroid the bomb is planted at, and cover on a site is a decision to make on purpose rather than a side effect. The app bundle grew 4,604 raw / 2,035 gzipped and no longer fits 75,400/34,900, so its ceiling is 82,000/38,000 — today's 77,966 / 36,059 plus about five per cent, with the reasoning written into src/games.ts beside every other ceiling move.

🤖 Generated with Claude Code

Closes #138

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs