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

forked from trevato/cs

The support line moves behind the door, and the panel becomes the shell's #373

Merged opened this

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

Merged

torque/cs-engine:torque/344-the-support-line-moves-behind-the-door-a → main 1 commit fork

  • Merged. torque/344-the-support-line-moves-behind-the-door-a was merged into main as 4c98f7f.

Review

Sign in to review this change.
16 files changed +185 −35
README.md +1 −1
@@ -503,7 +503,7 @@ the picture a machine whose driver refused the shaders actually gets.
503503 browser and the platform, what the WebGL2 driver calls itself — on Windows the
504504 ANGLE string naming the Direct3D device — the stage's size, the pixel ratio,
505505 whether the pointer lock is raw and whether the keyboard lock was granted.
506 `src/client/support.ts` is a pure function of those facts; every one of them
506 `src/engine/support.ts` is a pure function of those facts; every one of them
507507 comes from outside the page, so every one is flattened of control characters,
508508 stripped of the separator and cut to 120 characters, and it reaches the DOM
509509 through `textContent`. When the picture cannot be built at all — ANGLE on D3D11
src/client/main.test.ts +1 −1
@@ -32,7 +32,7 @@ import { expect, test } from "bun:test";
3232 import { generateLevel, nameOf, seedFrom, seedOf } from "./generate.ts";
3333 import type { Level } from "./level.ts";
3434 import { boot, COPIED, gameMeshes, NOT_AN_ID, NOT_A_MAP, NOT_COPIED, type Frame } from "./main.ts";
35 import { NO_PICTURE } from "./support.ts";
35 import { NO_PICTURE } from "../engine/index.ts";
3636 import { HINT } from "./buy.ts";
3737 import { DE_FORGE } from "./map.ts";
3838 import { endRound, SHORT } from "./match.ts";
src/client/main.ts +3 −3
@@ -69,13 +69,13 @@
6969 import { createAudio, type Audio } from "./audio.ts";
7070 import { CS_KEYS, CS_MOUSE, CS_TAP, CS_WHEEL, readIntent, slotIndex } from "./binding.ts";
7171 import { drawHud, type Paint } from "./hud.ts";
72 import { NO_PICTURE, NO_PICTURE_SAYS, supportLine, type Facts, type Running } from "./support.ts";
7372 import {
7473 attachButtons, backing, clamp, createControls, createEscape, createFullscreen, createLoop,
7574 createRenderer,
7675 effectAt,
7776 painter,
78 type Button, type Camera, type KeyboardLock, type Listens, type Mesh,
77 NO_PICTURE, NO_PICTURE_SAYS, supportLine,
78 type Button, type Camera, type Facts, type KeyboardLock, type Listens, type Mesh, type Running,
7979 } from "../engine/index.ts";
8080 import { generateLevel, seedFrom } from "./generate.ts";
8181 import { mapFrom, mapLink, nameOf, seedOf, type MapChoice } from "../mapname.ts";
@@ -304,7 +304,7 @@ export type Host = {
304304 * What this browser is, for the one line a bug report pastes: the user agent,
305305 * the name the WebGL2 driver gives itself, the stage's size and the pixel
306306 * ratio. Gathered by the host because every one of those is a browser's, and
307 * turned into a sentence by `./support.ts`, which is a pure function.
307 * turned into a sentence by the engine's `supportLine`, which is a pure function.
308308 */
309309 facts(canvas: Stage): Facts;
310310 createAudio(): Audio;
src/engine/GUIDE.md +36 −1
@@ -150,7 +150,7 @@ const SPEED = 4, TURN = 120, EYE = 1.6, REACH = 40;
150150
151151 ## What is behind the door
152152
153 **Weight**: 47.4 kB raw, 18.9 kB gzipped — every group at once, which is the most any game can pay.
153 **Weight**: 50.1 kB raw, 20.1 kB gzipped — every group at once, which is the most any game can pay.
154154
155155 Every section below opens with what that group costs a bundle, because "what
156156 does it cost to use?" is a question a page can answer with a number. The number
@@ -892,6 +892,40 @@ animates itself: a bob, a fade or a tint is a decision, and decisions are the
892892 game's. One line a frame draws it, and `src/client/main.ts` and
893893 `src/range/main.ts` are where those two lines live.
894894
895 ### support
896
897 **Weight**: 2.0 kB raw, 1.1 kB gzipped — this module alone.
898
899 One line of what this browser is doing, for the person who is about to say your
900 game is buggy. "Buggy on Windows in Edge" is true and unanswerable: a refused
901 shader, a driver that paints nothing, a pointer that was never granted and a
902 display at 125% all read the same from outside. `supportLine` is the sentence
903 that tells them apart, and it is a pure function of plain data your entry
904 gathers when somebody opens the panel.
905
906 `Facts` is the browser's half: `Facts.userAgent`, `Facts.renderer` — what the
907 WebGL2 driver calls itself, or null where the browser will not say — the stage's
908 size in CSS pixels as `Facts.stage`, `Facts.dpr`, and `Facts.commit`, the build
909 the page was served from. `Running` is the game's half, and every member of it
910 is optional because every one of them is some game's and not another's:
911 `Running.pointer` is a `PointerMode` or null, `Running.keys` whether Escape was
912 handed over, `Running.fullscreen`, `Running.hz`, `Running.frameMs`, and
913 `Running.failure`, which is what the boot threw. A member you leave out is left
914 out of the line rather than guessed at — a game with no pointer does not claim
915 one — and a game with a word of its own to add writes it after the line.
916
917 Every piece comes from outside the page, so every piece is trimmed, stripped of
918 control characters and of the separator, and cut to 120 characters before it is
919 joined. Write it into the page as text, never as markup. `NO_PICTURE` and
920 `NO_PICTURE_SAYS` are the heading and the paragraph for the menu of a game whose
921 picture could not be built, with the line underneath them.
922
923 The panel the line goes in is not the engine's — the engine renders nothing —
924 but it is not one game's either: `src/pages/shell.ts` writes it, closed, at the
925 foot of whichever overlay asks with `support: true`, with the ids `#support`,
926 `#supportline` and `#copysupport`. The shooter's menu asks; `src/client/main.ts`
927 fills the line on the panel's toggle event and copies it on the button.
928
895929 ### page
896930
897931 **Weight**: 3.6 kB raw, 1.8 kB gzipped — this module alone.
@@ -1163,6 +1197,7 @@ rows, an entry aside, and issue #130 is where it gets one.
11631197 | what casts a contact shadow | every living player, the bomb and every grenade, traced against the level's boxes — 24 of them, reaching 96 units | every plate, from its feet, traced against the room — one per instance the pool holds, reaching 128 units |
11641198 | a pinned run | `src/client/replay.ts` — a script of held inputs, a round and a duel | `src/range/pin.ts` — a script of trigger pulls, one digest per drill |
11651199 | no WebGL2 | a sentence in the menu, and nothing starts | the 2D horizon in `src/range/readout.ts`, and it still plays |
1200 | a support line | `Facts` and `Running` gathered in `src/client/main.ts`, into the panel the shell renders in the menu | none yet — the range asks the shell for no panel |
11661201 | the mover's tuning and hull | `movement.CS_MOVE` and `movement.CS_HULL` in `src/client/movement.ts` | `movement.RANGE_MOVE` and `movement.RANGE_HULL` in `src/range/movement.ts` — its own numbers, the same solver |
11671202 | the rest of the tuning | `src/client/weapons.ts`, and `movement.fallDamage` | its own: `targets.STANDARD`, `targets.EYE` and the drills beside them in `src/range/targets.ts` |
11681203 | a policy's observation, heads and weights | `src/client/observe.ts`, `src/client/action.ts`, `src/client/weights.ts` | none — the range has no agent and bundles none of the net |
src/engine/boundary.test.ts +3
@@ -294,6 +294,9 @@ test("the example imports the door and nothing else, which is what it is for", (
294294 * effects — `Effects`, `createEffects`, `pushEffect`, `expireEffects`,
295295 * `effectAt`, `resetEffects`.
296296 *
297 * support — `Facts`, `Running`, `supportLine`, `NO_PICTURE`,
298 * `NO_PICTURE_SAYS`: moved from the shooter by #344, not written anew.
299 *
297300 * The list itself is `surface.json` beside this file — data, sorted, one name
298301 * to a line, so a name that moves is a line in a diff — and it is committed
299302 * with the *version* that surface belongs to, which is the version
src/engine/index.ts +12
@@ -136,6 +136,18 @@ export type { Member, MemberCodec, MemberOptions, MemberSocket, Sentences } from
136136 export { createRenderer } from "./renderer.ts";
137137 export type { Renderer, RendererAssets } from "./renderer.ts";
138138
139 /**
140 * One line of what this browser is doing, for a bug report: the browser, the
141 * driver behind the WebGL2 context, the stage's size and pixel ratio, then what
142 * the game says it is doing — each piece trimmed, stripped and cut short, since
143 * all of it comes from outside the page. A pure function of plain `Facts` and an
144 * optional `Running`, every member of which is optional and none of which names
145 * a game's concept; and the heading and paragraph a menu shows when the picture
146 * could not be built at all. The panel it is written into is the shell's.
147 */
148 export { NO_PICTURE, NO_PICTURE_SAYS, supportLine } from "./support.ts";
149 export type { Facts, Running } from "./support.ts";
150
139151 /**
140152 * A headless page: the host the parameters above already describe, made of
141153 * plain objects. A package that asks a game for a `surface`, a `window` and a
src/client/support.test.ts → src/engine/support.test.ts renamed
No content changes.
src/client/support.ts → src/engine/support.ts renamed +14 −9
@@ -12,8 +12,11 @@
1212 * a frame costs and the build it is all running on — short enough to paste into
1313 * an issue and boring enough that pasting one costs nothing.
1414 *
15 * It is a pure function of plain facts: `main.ts` gathers them from the
16 * browser, this builds the sentence, and a test holds the sentence. Every piece
15 * It is a pure function of plain facts: a game's entry gathers them from the
16 * browser — the shooter's is `src/client/main.ts` — this builds the sentence,
17 * and a test holds the sentence. It names no weapon, no round and no map, which
18 * is why it is the engine's: the panel it fills is the shell's, `src/pages/shell.ts`,
19 * and any game can ask for one. Every piece
1720 * of it comes from outside the page — a driver's own string, a user agent — so
1821 * every piece is trimmed, stripped of control characters and cut to a length
1922 * before it goes anywhere near the DOM.
@@ -52,9 +55,9 @@ export type Facts = {
5255 export type Running = {
5356 /**
5457 * How the locked pointer is sending counts — the engine's `PointerMode` — or
55 * null while the game has no pointer. This is the field that separates "the
56 * mouse feels wrong on Windows" from "Play did nothing": `adjusted` is the
57 * first, `null` after pressing Play is the second.
58 * null while the game has no pointer. This is the field that tells two reports
59 * apart — "the mouse feels wrong on Windows" and "Play did nothing": `adjusted`
60 * is the first, `null` after pressing Play is the second.
5861 */
5962 pointer?: "raw" | "adjusted" | "unknown" | null;
6063 /**
@@ -167,9 +170,11 @@ export const NO_PICTURE = "The picture could not be built";
167170 /**
168171 * And the paragraph under that heading: what happened, what still works, and
169172 * what to do with the line below it. Text, escaped where it is rendered.
173 *
174 * One literal, on one long line, because this is the engine now: a sum of
175 * literals at module scope is an expression the minifier keeps, and every
176 * bundle through the door — the range's, which says none of this — would carry
177 * it. `src/range/bundle.test.ts` reads that bundle to say so.
170178 */
171179 export const NO_PICTURE_SAYS =
172 "This browser has WebGL2 and then refused the game’s shaders — which is what a driver " +
173 "does when it is asked for something it will not compile, and is far more common on Windows, " +
174 "where the browser draws through Direct3D. Nothing else on the page is affected. The line " +
175 "below says which browser and which driver: paste it into a bug report.";
180 "This browser has WebGL2 and then refused the game’s shaders — which is what a driver does when it is asked for something it will not compile, and is far more common on Windows, where the browser draws through Direct3D. Nothing else on the page is affected. The line below says which browser and which driver: paste it into a bug report.";
src/engine/surface.json +6 −1
@@ -1,5 +1,5 @@
11 {
2 "version": "0.10.0",
2 "version": "0.11.0",
33 "names": [
44 "Activations",
55 "Adam",
@@ -23,6 +23,7 @@
2323 "Effects",
2424 "Escape",
2525 "EscapeHost",
26 "Facts",
2627 "FakeAudioNode",
2728 "FakeCanvas",
2829 "FakeDocument",
@@ -66,6 +67,8 @@
6667 "Mesh",
6768 "MouseEventish",
6869 "MoveTuning",
70 "NO_PICTURE",
71 "NO_PICTURE_SAYS",
6972 "Options",
7073 "Overlay",
7174 "Packed",
@@ -80,6 +83,7 @@
8083 "RendererAssets",
8184 "Rollout",
8285 "RoomGame",
86 "Running",
8387 "Scene",
8488 "SceneCapacity",
8589 "Screen",
@@ -199,6 +203,7 @@
199203 "softmax",
200204 "stuck",
201205 "sub",
206 "supportLine",
202207 "tensorsOf",
203208 "traceWorld",
204209 "unpack",
src/example/bundle.test.ts +13
@@ -17,6 +17,7 @@
1717
1818 import { afterAll, expect, test } from "bun:test";
1919 import { readFileSync } from "node:fs";
20 import { NO_PICTURE, NO_PICTURE_SAYS } from "../engine/index.ts";
2021 import { bundlePath, discardBundleCopies, importBundle } from "../../scripts/bundle.test-helper.ts";
2122 import { page, type ExamplePage } from "./stage.test-helper.ts";
2223
@@ -107,6 +108,18 @@ test("the example bundles none of the engine it does not use", () => {
107108 expect(code).not.toContain(name);
108109 });
109110
111 test("the example asks for no support line, so the door's support group costs it nothing", () => {
112 // The shooter ships `supportLine` and the sentences beside it; the example imports
113 // the same door and none of those names, so not one byte of them may come
114 // with it. The paragraph is the one to watch: written as a sum of literals at
115 // module scope it is an expression the minifier keeps, and every bundle
116 // through the door paid its four hundred bytes for nothing.
117 const code = readFileSync(bundlePath("example"), "utf8");
118 expect(code).toContain("webgl2"); // the reader is alive
119 for (const text of [NO_PICTURE, NO_PICTURE_SAYS.slice(0, 40), "driver not named"])
120 expect(code).not.toContain(text);
121 });
122
110123 test("the stub page leaves nothing of itself behind", () => {
111124 // Every boot above undid its install, so no other test inherits a document.
112125 expect("document" in globalThis).toBe(HAD_DOCUMENT);
src/pages/game.test.ts +14 −1
@@ -36,7 +36,7 @@ import { GAMES } from "../games.ts";
3636 import { digitsIn, proseOf, unsourced } from "./digits.test-helper.ts";
3737 import { gamePage, policyHtml, shareHref, statsHtml, STYLE, BAD_ID, BAD_MAP, type PageOptions } from "./game.ts";
3838 import { WEAPONS_HREF, WEAPONS_SECTION, WEAPONS_SUMMARY } from "./weapons.ts";
39 import { escape } from "./shell.ts";
39 import { escape, SUPPORT } from "./shell.ts";
4040
4141 const db = (): ReturnType<typeof openDatabase> => openDatabase(mkdtempSync(join(tmpdir(), "game-")));
4242
@@ -91,6 +91,19 @@ test("the leaderboard and the menu are in the first paint, not fetched", () => {
9191 expect(html).toContain(`<button type="button" class="primary" id="play" disabled>Play</button>`);
9292 });
9393
94 test("the menu asks the shell for the support panel, and it lands inside the menu", () => {
95 const html = gamePage(db(), options);
96 const menuAt = html.indexOf(`<div class="overlay" id="menu">`);
97 const pauseAt = html.indexOf(`<div class="overlay" id="pause"`);
98 // The same ids `src/client/main.ts` finds, once each, between the menu and the pause.
99 for (const id of ["support", "supportline", "copysupport"]) {
100 expect(html.split(`id="${id}"`).length - 1).toBe(1);
101 expect(html.indexOf(`id="${id}"`)).toBeGreaterThan(menuAt);
102 expect(html.indexOf(`id="${id}"`)).toBeLessThan(pauseAt);
103 }
104 expect(html).toContain(SUPPORT);
105 });
106
94107 test("the menu has a Motion control, its first option the game's default for the client to move", () => {
95108 // No `selected`: a select picks its first option, and the page's one
96109 // `selected` word stays the map's.
src/pages/game.ts +2 −12
@@ -410,17 +410,7 @@ const menu = (visitor: Who, asked: Asked, daily: Daily): string => `
410410 <p class="hint">Every weapon's numbers — what it costs, what it fires and what one shot of it
411411 does to a person — are on <a href="${WEAPONS_HREF}">the weapons page</a>, rendered from the
412412 table the fight is tuned by. It is a page of its own so it is a link you can send.</p>
413 <details id="support">
414 <summary>What this browser is doing</summary>
415 <p class="hint">One line for a bug report: the browser, the graphics driver behind the
416 picture and the size it is drawn at. "Buggy on Windows" and this line are two different
417 reports, and only one of them can be answered. Nothing is sent anywhere — it is text on
418 this page until you copy it.</p>
419 <p class="line" id="supportline">This line is filled in by the game, so a browser with
420 scripts off has nothing to paste.</p>
421 <button type="button" id="copysupport" hidden>Copy this line</button>
422 </details>
423 `;
413 `;
424414
425415 /**
426416 * What the page says about the bots' policy — the only paragraph on it made of
@@ -575,7 +565,7 @@ export function gamePage(
575565 ],
576566 ],
577567 overlays: [
578 { id: "menu", html: menu(visitor, asked, daily) },
568 { id: "menu", html: menu(visitor, asked, daily), support: true },
579569 { id: "pause", html: PAUSE, hidden: true },
580570 // After the pause, so a match that ends while it is up is read over it
581571 // rather than under it. `src/client/main.ts` hides the pause anyway.
src/pages/shell.test.ts +31 −1
@@ -6,7 +6,8 @@
66 */
77
88 import { expect, test } from "bun:test";
9 import { escape, nav, noscript, page, stage, styleFor, terminal, where } from "./shell.ts";
9 import { escape, nav, noscript, page, stage, styleFor, SUPPORT, terminal, where } from "./shell.ts";
10 import { errorPage, notFoundPage } from "./miss.ts";
1011 import { GAMES as REGISTRY } from "../games.ts";
1112 import { GAMES, navFor, noscriptFor } from "./games.ts";
1213
@@ -296,6 +297,35 @@ test("the stage carries the full screen button, hidden, over everything else", (
296297 expect(document).toContain("aspect-ratio:auto");
297298 });
298299
300 test("an overlay that asks for the support panel gets it, with the ids a game finds it by", () => {
301 const html = stage({
302 touch: [],
303 overlays: [{ id: "menu", html: "<h2>go</h2>", support: true }, { id: "pause", html: "", hidden: true }],
304 });
305 // At the foot of the overlay that asked, and only that one.
306 expect(html).toContain(`<div class="overlay" id="menu"><h2>go</h2>${SUPPORT}</div>`);
307 expect(html).toContain(`<div class="overlay" id="pause" hidden></div>`);
308 expect(html.match(/<details/g)?.length).toBe(1);
309 // The three ids are the contract: the toggle, the line and the button.
310 expect(html).toContain(`<details id="support">`);
311 expect(html).toContain(`<p class="line" id="supportline">`);
312 expect(html).toContain(`<button type="button" id="copysupport" hidden>Copy this line</button>`);
313 // Closed in the first paint, and the button hidden until a script can copy.
314 expect(html).not.toContain(`<details id="support" open`);
315 });
316
317 test("a page that asks for no support panel gets no element of it", () => {
318 const html = stage({ touch: [], overlays: [{ id: "menu", html: "<h2>go</h2>" }] });
319 for (const id of ["support", "supportline", "copysupport"]) expect(html).not.toContain(`id="${id}"`);
320 expect(html).not.toContain("<details");
321 // And the pages that run nothing ask for none: no line to fill, no panel.
322 const miss = { env: "production", pull: undefined, commit: "abc", ssh: undefined };
323 for (const document of [notFoundPage(miss), errorPage(miss)]) {
324 expect(document).not.toContain(`id="support`);
325 expect(document).not.toContain("copysupport");
326 }
327 });
328
299329 test("a touch label and an action are text, not markup", () => {
300330 const html = stage({ touch: [[{ action: XSS, label: XSS }]], overlays: [{ id: XSS, html: "" }] });
301331 expect(html).not.toContain("<script>");
src/pages/shell.ts +35 −4
@@ -184,8 +184,38 @@ export const endpoints = (posts: readonly Linked[] = []): string =>
184184 /** One touch button: the action `src/engine/controls.ts` holds, and its label. */
185185 export type TouchButton = { action: string; label: string };
186186
187 /** One overlay over the stage — a menu, a pause screen — by the id the client finds it with. */
188 export type Overlay = { id: string; html: string; hidden?: boolean };
187 /**
188 * One overlay over the stage — a menu, a pause screen — by the id the client
189 * finds it with. `support` asks for the support panel at its foot: see `SUPPORT`.
190 */
191 export type Overlay = { id: string; html: string; hidden?: boolean; support?: boolean };
192
193 /**
194 * The support panel: a closed `<details>` a person opens to get one line for a
195 * bug report, and the button that copies it. The line itself is the engine's
196 * `supportLine`, written in by a game's entry through `textContent` — so what
197 * is rendered here is only what is true before a script has run, and it is the
198 * shell's because nothing in it names a game. The ids are the contract a game's
199 * entry finds it by: `support` for the toggle, `supportline` for the line and
200 * `copysupport` for the button, which is `hidden` because it does nothing
201 * without a script.
202 *
203 * A page asks for it on the overlay it wants it in, with `support: true`, and a
204 * page that does not ask gets no element — the 404 and the 500 run nothing, so
205 * they have no line to fill and no panel to show. Constant markup: there is no
206 * text in it from anywhere but this file.
207 */
208 export const SUPPORT = ` <details id="support">
209 <summary>What this browser is doing</summary>
210 <p class="hint">One line for a bug report: the browser, the graphics driver behind the
211 picture and the size it is drawn at. "Buggy on Windows" and this line are two different
212 reports, and only one of them can be answered. Nothing is sent anywhere — it is text on
213 this page until you copy it.</p>
214 <p class="line" id="supportline">This line is filled in by the game, so a browser with
215 scripts off has nothing to paste.</p>
216 <button type="button" id="copysupport" hidden>Copy this line</button>
217 </details>
218 `;
189219
190220 export type StageOptions = {
191221 overlays: Overlay[];
@@ -197,7 +227,8 @@ export type StageOptions = {
197227 * The stage: the two canvases the renderer draws into, the fps readout, the
198228 * touch pads, the overlays and the full screen button. Every id in here is
199229 * found by `getElementById` in a game's entry — `stage`, `scene`, `hud`, `fps`,
200 * `touch`, `full` and each overlay's — so they are the contract, not decoration.
230 * `touch`, `full`, each overlay's, and the support panel's where an overlay
231 * asked for it — so they are the contract, not decoration.
201232 *
202233 * `#full` is rendered last, so it paints over the overlays rather than under
203234 * them, and `hidden`, because it is the one control on this page that does
@@ -220,7 +251,7 @@ ${pad.map((b) => ` <button type="button" data-action="${escape(b.action)}
220251 <div id="touch" hidden>
221252 ${pads}
222253 </div>
223 ${overlays.map((o) => ` <div class="overlay" id="${escape(o.id)}"${o.hidden === true ? " hidden" : ""}>${o.html}</div>`).join("\n")}
254 ${overlays.map((o) => ` <div class="overlay" id="${escape(o.id)}"${o.hidden === true ? " hidden" : ""}>${o.html}${o.support === true ? SUPPORT : ""}</div>`).join("\n")}
224255 <button type="button" id="full" hidden aria-pressed="false" aria-label="Full screen" title="Full screen">⛶</button>
225256 </div>`;
226257 }
src/range/bundle.test.ts +13
@@ -16,6 +16,7 @@
1616
1717 import { afterAll, expect, test } from "bun:test";
1818 import { readFileSync } from "node:fs";
19 import { NO_PICTURE, NO_PICTURE_SAYS } from "../engine/index.ts";
1920 import { bundlePath, discardBundleCopies, importBundle } from "../../scripts/bundle.test-helper.ts";
2021 import { RANGE_MOVE } from "./movement.ts";
2122 import type { GlRecorder } from "../engine/index.ts";
@@ -152,6 +153,18 @@ test("the range bundles the engine's mover, on the range's own numbers", () => {
152153 expect(code).not.toContain("maxSpeed:250");
153154 });
154155
156 test("the range asks for no support line, so the door's support group costs it nothing", () => {
157 // The shooter ships `supportLine` and the sentences beside it; the range imports
158 // the same door and none of those names, so not one byte of them may come
159 // with it. The paragraph is the one to watch: written as a sum of literals at
160 // module scope it is an expression the minifier keeps, and every bundle
161 // through the door paid its four hundred bytes for nothing.
162 const code = readFileSync(bundlePath("range"), "utf8");
163 expect(code).toContain("webgl2"); // the reader is alive
164 for (const text of [NO_PICTURE, NO_PICTURE_SAYS.slice(0, 40), "driver not named"])
165 expect(code).not.toContain(text);
166 });
167
155168 test("the stub page leaves nothing of itself behind", () => {
156169 // Every boot above undid its install, so no other test inherits a document.
157170 expect("document" in globalThis).toBe(HAD_DOCUMENT);
src/range/readout.test.ts +1 −1
@@ -145,7 +145,7 @@ test("the horizon follows the pitch and the compass follows the yaw", () => {
145145
146146 test("the fallback scene is the same size on the glass at every device ratio", () => {
147147 // This is the picture a machine whose driver refused the shaders gets — see
148 // `client/support.ts` — so at 150% its lines cannot be two thirds of a pixel
148 // `engine/support.ts` — so at 150% its lines cannot be two thirds of a pixel
149149 // and its compass numbers cannot be eight pixels tall.
150150 const tickHeight = (paint: ReturnType<typeof recorder>): number => {
151151 // The compass's first tick: the second moveTo/lineTo pair, after the horizon.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs