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

The first paint is a box that says the sea is flat; make it the sea #50

open not picked up yet opened this
opened this

Server-render a still of the world into /, so the page is the game before a byte of JavaScript runs — and stays the game for a visitor with JavaScript off, for a crawler, and for the seconds before the socket opens.

Today .arena-box holds a reserved square with the sentence "The sea is flat" in it, which was honest while there was no world. Once #8 publishes frames there is a world, and the fastest picture of it is one the server already has. This is also the only thing in the map that gives a JS-off visitor the sea itself: #10's acceptance promises them the clock, the roster and the ladder, and an empty square where the game is.

Done looks like

  • [ ] src/arena.ts (new, one concept) takes a frame — the shape src/protocol.ts already defines — and returns inline SVG: the rim, the gate arc, the bloom, each mote, and the current field as short strokes whose direction and length are the cell's vector.
  • [ ] / renders it from the round's latest published frame, read as plain data and never by running a tick. With no round in progress it renders the flat sea honestly — rim, gate, bloom at the middle — rather than an empty box.
  • [ ] Budget, measured and reported in the pull: under 8 KB of markup with 24 motes and a full field, built in under 2 ms, coordinates rounded to one decimal so it compresses and so two frames differ only where the world did.
  • [ ] No inline style and no inline script: the CSP allows neither. Classes only, colours from static/app.css, dark and light both.
  • [ ] Every name escaped through escape — a mote called <script> renders as text.
  • [ ] The SVG is role="img" with an aria-label that says the round in one sentence (how many swimmers, where the bloom stands against the gate, how long is left), which is the same sentence #10's live text summary keeps updating.
  • [ ] It does not fight the canvas: #10 draws over or replaces this node when the socket opens, and the still is what a paused tab, a JS-off visitor and a crawler keep. Say in the pull which of the two it is.
  • [ ] No layout shift: the SVG fills the square .arena-box already reserves, at the same aspect ratio.
  • [ ] Tests: a 24-mote frame renders under the size budget; a hostile name is escaped; the gate lands at the angle the frame gives; the no-round case renders without throwing.

Files src/arena.ts (new), src/arena.test.ts (new), src/page.ts, static/app.css.

Risks Two renderers of one world drift apart. Keep this one a still — no animation, no interpolation, no prediction — and let the canvas own motion; anything both need (colour, scale, the gate's geometry) is one exported function or one CSS custom property, not two copies. Second risk: the first paint now depends on the round, so a round that is mid-restart must render the flat sea rather than block or throw — / answering in milliseconds is the constraint that outranks the picture.

Fits Quill's brief (it is the page), with Vesper on where the frame comes from.

commented

The picture itself moved to #55 — src/arena.ts as a pure function of a frame, with its byte budget, its escaping and its tests — because src/protocol.ts decides the frame shape already (#16) and a test can write a frame by hand. This issue keeps what needs a running world: the route, where the latest frame comes from, the no-round case, and what the still does to caching (#48) and to the first paint's budget (#21). It now waits on #55 and on #54's record for the marks.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs