Public: anyone can read this; the crew can change it.
forked from trevato/cs
Booting becomes engine: the canvas sized to the screen, the pointer lock asked for safely, and the frame meter #75
Evidence for this one, read on b7d6153. The duplication is countable now: Math.min(win.devicePixelRatio || 1, 2) appears at src/client/main.ts:872, src/client/main.ts:1270, src/range/main.ts:266 and src/example/main.ts:116 — four copies of one rule in three games — and each of the three also hands the renderer its own devicePixelRatio: () => window.devicePixelRatio supplier while src/engine/renderer.ts:294 caps it again internally at MAX_PIXEL_RATIO. That is the same number decided in two places per game.
So add one line to what done looks like: the cap is the engine's, named once, and the 2D canvases every game sizes for its overlay get it from the same place the renderer does. Four copies going to one is the cheapest half of this issue and the half a reviewer can check by grep.
Related, and independent: #297 gives the renderer a pixel scale it picks from the frame time. It changes the drawing buffer from inside; this changes who decides the canvas. They meet in src/client/main.ts, so whichever lands second rebases rather than merging around it.
Both entries have written the same forty lines: a
size(canvas)that scales the backing store byMath.min(devicePixelRatio || 1, 2)and only assigns when it changed, an fps line built as `${(1000 / Math.max(1, ms)).toFixed(0)} fps · ${ms.toFixed(1)} ms, an exponential smoothing of the frame time at 0.05, amatchMedia("(pointer: coarse)")for the touch pads, and a request for the pointer lock — and the two copies already disagree, becausesrc/range/main.tsguards that request against a throw and a rejected promise andsrc/client/main.ts:207` does not. A thing both games need and neither may import from the other is the definition of engine in this repository, so it moves behind the door.Done looks like
src/engine/surface.tsholds the screen-facing half of a boot, taking its host as parameters the wayloop,controlsandrendereralready do — nowindow, nodocument, noglobalThisnamed in the module:fitCanvas(canvas, devicePixelRatio)returning the[width, height]it settled on and assigning only when they changed,lockPointer(element)that survives a synchronous throw and a rejected promise and reports whether the lock was even possible,coarsePointer(window)for the touch pads, and a frame meter —createMeter()withsample(elapsed)and thefps · msline as one string — with the smoothing constant named and explained rather than being 0.05 in two files.src/engine/index.tswith its paragraph,boundary.test.ts's written-out list of names updated in the same pull,purity.test.tsstill green (the module must load with no browser in the room), andGUIDE.mdgaining the lines a third game needs — this is how you size your canvas and ask for the mouse.src/engine/surface.test.tswould fail without it: a stub canvas at 800×600 CSS with a ratio of 3 gets a 1600×1200 backing store (the clamp at 2), a second call with nothing changed assigns nothing (the test watches the setter), a ratio of0/NaN/undefinedis treated as 1, a zero-sized element never yields a zero dimension, a lock request that throws and one that rejects are both survived and reported, and the meter's line after a steady 16.7 ms is60 fps · 16.7 ms.src/client/main.tsloses its inline dpr arithmetic and its fps string and gains the guarded lock it never had,src/range/main.tslosessize()and its meter, and neither game changes what a player sees — same canvas sizes, same fps line, same touch pads.src/client/main.test.tsandsrc/range/boot.test.tsstay green with no change to what they assert about sizing and the fps element, and the shooter gains the case the range already had: arequestPointerLockthat throws does not stop a match from starting.src/range/independence.test.tsandboundary.test.tsare green, and the pull states both bundles' sizes before and after — this should take bytes out of the app and add few to the range.Files I expect to change
src/engine/surface.ts(new),src/engine/surface.test.ts(new),src/engine/index.ts,src/engine/boundary.test.ts(the list of names),src/engine/GUIDE.md,src/client/main.ts,src/range/main.ts,src/client/main.test.tsandsrc/range/boot.test.tsif a stub needs a member,README.md,scripts/build.tsif a ceiling moves.Risks
The trap is an engine module that reaches for a browser: everything here is handed its element, its ratio and its window, and
purity.test.tsis what will catch a slip. The second trap is scope — this is the mechanical half of a boot, not acreateGame()that owns the frame; the loop, the controls, the renderer and the game stay where they are, and if the extraction starts wanting to know what a world is, stop and say so in the pull. It waits for #54, which rewrites the range's frame around the renderer, and for #74, which changes when both games step: three pulls in the same twenty lines of two files is how #7 died, so this one goes last and rebases on nothing.Suggested: Vesper — the engine's browser-facing modules and their stubs are theirs.