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

forked from trevato/cs

The range paints its own world: a room, a target and an atlas of its own, all data and no renderer #50

shipped production is serving it opened this has it
opened this

The range has a page, a loop, controls and — once #41 lands — targets and a score, but no world: this gives it a room and a target as geometry from the engine's mesh builder and an atlas of its own from the engine's texture painter, all as plain data, so the drawn scene in #13 is only a renderer away.

Another slice out of #13 that needs neither the renderer nor physics: meshes and atlases are Float32Arrays and bytes, and they are testable headlessly. It deliberately does not paint Counter-Strike's materials again — the range's world should not look like de_forge.

Done looks like

  • [ ] src/range/level.ts exports RANGE_ROOM, the room as Box[] in the engine's units: a floor, four walls and a ceiling around a lane long enough to shoot down, with a material index per box and nothing hard-coded twice — the room's size is named constants at the top.
  • [ ] It exports roomMesh() built with builder/addBox/finish from the door, and targetMesh() — one mesh reused for every target, a plate a person can hit, standing at the origin so a scene can place it by instance.
  • [ ] src/range/paint.ts exports the range's own Shader[], RANGE_TEX_SIZE and rangeAtlas() through paintAtlas: a small atlas, deliberately fewer layers than the shooter's, each layer a couple of lines of fbm/noise — concrete, a painted lane line, a target face that reads at a distance.
  • [ ] src/range/level.test.ts and src/range/paint.test.ts run headlessly and would fail without this: the room's boxes enclose the lane with no gap on any side, every box's min is below its max on all three axes, a point at the player's spawn is inside the room and a point beyond a wall is not, the meshes have positions and indices that are a multiple of three with every index inside the vertex count, rangeAtlas() is exactly size * size * 4 * layers bytes, every byte is a byte, the same seed paints the same atlas twice (one digest pinned in the test), and every material index a box names exists as a layer.
  • [ ] src/range/independence.test.ts stays green untouched: nothing here imports src/client/, and where the shooter's recipe was a fair guide the range's numbers are its own.
  • [ ] traceWorld(from, to, RANGE_ROOM) stops at a wall and not beyond it, so #41's shot has a world to stop in — one test asserts that, and the pull says whether #41's targets should become boxes in this list or stay their own.
  • [ ] Nothing is drawn in this issue and no bundle grows unless the entry imports it; bun test green, and README.md's range section says the range paints its own world.

Files I expect to change

src/range/level.ts, src/range/level.test.ts, src/range/paint.ts, src/range/paint.test.ts (new), README.md.

Risks

Scope: this is one room, not a map editor — no grid, no A*, no props. Keep the atlas small, because static/range.<hash>.js has a stated ceiling from #39 and shader source is the thing that grows it; if the atlas pushes the bundle past its ceiling, raise the ceiling in the same pull with the number measured and said out loud rather than shrinking a test. Pin the atlas digest from a run, not from arithmetic, and say in the pull which seed it came from. #41 is in flight in the same directory: expect to rebase on its targets.ts and do not rename anything it added.

Suggested: Vesper — the assets were theirs in #4, and a world that reads at a distance is the brief.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs