--- id: render-performance title: Input-to-pixels stays under the tail status: continuous repos: [arena] dependsOn: [] exitCriterion: > A player interacting with the board does not wait on the renderer, and any regression is measured rather than argued about. --- # render-performance **arena owns this one.** The task list is [arena's TODO](https://tangled.org/lance.blue/arena) under "Render performance", and the record of what shipped and what each fix measured is `PERFORMANCE.md` in that repo. This file is here so the register is complete and so the epic can be cited from a commit — it is not a second copy, and it should not become one. ## Why it is an epic Rendering was sluggish and the shipped fixes roughly halved median downpress-to-pixels, with the p90 tail falling from ~470ms to ~330ms. The proxy was ruled out early: the api host stayed under 5% CPU during a match. What makes it worth naming rather than leaving as a pile of arena tickets is that it is the one number the whole architecture is arranged to protect. `arena-image`'s SOCI option is argued against because lazy loading would move a delay into the frame path. The "Princess in its own process" proposal exists because bot search steals cores from the EDT. Task sizing is anchored on where a client JVM starts stalling. All three are render-performance decisions made in other epics. ## The state of it, as of 2026-08-07 The first real profile exists. async-profiler 4.5 ships in the image and loads into both JVMs when `ARENA_PROFILE=1`, dumping collapsed stacks that `exit/finalize.sh` uploads under a `diagnostics` key. Of the EDT samples that were running rather than waiting, 85% are under `BoardView` painting, 76% under DirectDraw and 72% under PNG encode and pixel hashing — `libzpng` alone is 43% of the EDT's whole working time. That confirms by measurement what had until then been deduced from source. Two caveats on that run, both recorded in arena: the match never left the lobby, so those are ambient rather than interaction costs, and the host was busy with an unrelated build. A run that plays is what settles the largest open items, and `arena.sh up --spectate` exists for exactly that. ## Where the work is Everything open lives in arena: the frame tearing discriminator, the 1008px tiled board background, DirectDraw's re-encode on every draw, capping the client canvas resolution, the game-summary images, server-side text rasterization, and the load-time batch. Each has its own evidence and its own argument, and several record things that were tried and rejected — repainting by hex, memoising by image identity — which is the part that would be lost by summarising them here. ## Done arena holds the detail; this is the shape of what landed. - [x] **The shipped render fixes.** Median downpress-to-pixels roughly halved and the p90 tail fell from ~470ms to ~330ms. `PERFORMANCE.md` records what each fix measured. The proxy was ruled out early — the api host stayed under 5% CPU during a match. - [x] **The first real profile.** async-profiler 4.5 ships in the image and loads into both JVMs under `ARENA_PROFILE=1`, dumping collapsed stacks that `exit/finalize.sh` uploads. It confirmed by measurement what had been deduced from source: of the EDT samples that were running rather than waiting, 85% are under `BoardView` painting and 72% under PNG encode and pixel hashing. - [x] **The per-layer latency split is reduced per match.** `exit/finalize.sh` writes n/mean/p50/p90/max per metric to `state/stats-summary.txt`, logs the table and uploads it. Suramadu had always logged the `stats,` lines; nobody had ever summed them.