From 4127095ee8ce3b9e322238e3743564e7146e0957 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Sat, 8 Aug 2026 12:56:12 -0700 Subject: [PATCH] Stop midi mode from outliving the address that asked for it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visiting `?midi` once wrote the choice to localStorage, so plain oskiewar.com then read it back and came up in midi mode with the procedural bank muted — permanently, with nothing on screen saying why. Remembering it was the mistake: the address is the whole state now, and the flag anyone already stored is cleared rather than merely ignored, so a refresh is enough to get the normal game back. oskiewar.com is the game, `?midi` is this visit, midi.oskiewar.com is how you keep it. The lamp also stops fighting the round QR, which owns the top-right of the HUD-safe frame whenever it is up. It keeps that corner and the same five-percent inset, but sits below the band the QR reserves, placed from script against the canvas because those numbers live in game units and the canvas is what converts them into CSS pixels. --- xbox/live/mac-test.html | 48 +++++++++++++++++++------- xbox/live/social/manifest.json | 2 +- xbox/live/tests/oskiewar-midi.test.mjs | 17 ++++++++- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/xbox/live/mac-test.html b/xbox/live/mac-test.html index df6b357b5f..16ed16787e 100644 --- a/xbox/live/mac-test.html +++ b/xbox/live/mac-test.html @@ -71,11 +71,12 @@ /* Scoring from a DAW is anonymous dummy play, so the account door is shut and the corner it occupied is free for the midi lamp. */ body.midi-out #logout { display: none; } - #midi-out { position: fixed; top: max(14px, env(safe-area-inset-top)); - right: max(14px, env(safe-area-inset-right)); z-index: 6; display: none; - padding: 6px 8px; border: 2px solid #6e768d; border-radius: 8px; - background: #171b28; box-shadow: 2px 3px 0 rgba(20, 25, 38, .35); - line-height: 0; } + /* Placed from script against the canvas, since the HUD's title-safe frame + and the round QR live in game units rather than CSS ones. */ + #midi-out { position: fixed; top: 14px; right: 14px; z-index: 6; + display: none; padding: 6px 8px; border: 2px solid #6e768d; + border-radius: 8px; background: #171b28; + box-shadow: 2px 3px 0 rgba(20, 25, 38, .35); line-height: 0; } body.midi-out #midi-out { display: block; } body.social-preview #midi-out { display: none; } #midi-out .key-white { fill: #b0b8ca; transition: fill .18s ease-out; } @@ -798,6 +799,26 @@ // The lamp answers every note that leaves, so the corner doubles as proof // the bridge is sending even when a pad is still silent. const midiLamp = document.querySelector("#midi-out"); + + // The round QR owns the top-right corner of the HUD-safe frame whenever it + // is up (`hello.js` spectatorQrBox), so the lamp keeps the same corner and + // the same five-percent inset but sits under the band the QR reserves. + // These numbers mirror hudSafeInset/spectatorQrBox; the canvas keeps a + // stable 1080-unit height, which is what converts them into CSS pixels. + function layoutMidiLamp() { + const box = canvas.getBoundingClientRect(); + if (!box.height) return; + const unit = box.height / 1080; + const compact = canvas.width < 1500; + const inset = Math.min(72, Math.max(compact ? 24 : 48, + Math.round(Math.min(canvas.width, 1080) * .05))); + const qrBand = (compact ? 108 : 158) + 12; + midiLamp.style.top = `${box.top + (inset + qrBand) * unit}px`; + midiLamp.style.right = `${innerWidth - box.right + inset * unit}px`; + } + addEventListener("resize", layoutMidiLamp); + addEventListener("orientationchange", layoutMidiLamp); + let lampTimer = null; const flashMidiLamp = () => { midiLamp.classList.add("lit"); @@ -806,18 +827,19 @@ }; const oskiewarMidi = createOskiewarMidi({ onSignal: flashMidiLamp }); globalThis.__oskiewarMidi = oskiewarMidi; + // The address is the whole state, and nothing about it survives a refresh: + // oskiewar.com is the game, `?midi` is this visit, midi.oskiewar.com is the + // way to keep it. Remembering the choice meant one visit to `?midi` left the + // procedural bank muted on the plain site forever, with nothing on screen + // saying why — so the old flag is cleared rather than read. + try { localStorage.removeItem("oskiewar-midi"); } catch {} const midiParam = new URLSearchParams(location.search).get("midi"); - let midiMode = midiParam; - if (midiParam !== null) { - try { localStorage.setItem("oskiewar-midi", midiParam || "1"); } catch {} - } else if (location.hostname.startsWith("midi.")) { - midiMode = "1"; // the subdomain is the ask; no query string to remember - } else { - try { midiMode = localStorage.getItem("oskiewar-midi"); } catch {} - } + const midiMode = midiParam !== null ? midiParam + : location.hostname.startsWith("midi.") ? "1" : null; if (midiMode !== null && midiMode !== "0" && midiMode !== "off") { if (await oskiewarMidi.enable()) { document.body.classList.add("midi-out"); + layoutMidiLamp(); oskiewarMidi.connectSignals(globalThis); const layered = midiMode === "both"; if (!layered) oskiewarSfx.mute(true); diff --git a/xbox/live/social/manifest.json b/xbox/live/social/manifest.json index c1996d52eb..7dbfbc0cc0 100644 --- a/xbox/live/social/manifest.json +++ b/xbox/live/social/manifest.json @@ -1,7 +1,7 @@ { "format": "ac.oskiewar.social-preview", "version": 2, - "build": "508d577023a8bbda", + "build": "06b89a9d0989f02d", "theme": "light", "imageWidth": 1200, "imageHeight": 630, diff --git a/xbox/live/tests/oskiewar-midi.test.mjs b/xbox/live/tests/oskiewar-midi.test.mjs index 7366b28e3e..59b13d718b 100644 --- a/xbox/live/tests/oskiewar-midi.test.mjs +++ b/xbox/live/tests/oskiewar-midi.test.mjs @@ -213,9 +213,24 @@ test("midi mode shuts the account door and lights the lamp", () => { assert.match(webShell, /classList\.add\("midi-out"\)/); }); +test("midi mode never outlives the address that asked for it", () => { + assert.doesNotMatch(webShell, /localStorage\.getItem\("oskiewar-midi"\)/, + "a remembered flag would mute the plain site's bank with nothing saying why"); + assert.doesNotMatch(webShell, /localStorage\.setItem\("oskiewar-midi"/); + assert.match(webShell, /localStorage\.removeItem\("oskiewar-midi"\)/, + "the flag anyone already stored has to be cleared, not merely ignored"); +}); + +test("the lamp keeps the HUD's safe frame and clears the QR's corner", () => { + assert.match(webShell, /function layoutMidiLamp\(\)/); + assert.match(webShell, /canvas\.getBoundingClientRect\(\)/); + assert.match(webShell, /box\.height \/ 1080/, "game units convert via the canvas"); + assert.match(webShell, /compact \? 108 : 158/, "the QR band must be reserved"); + assert.match(webShell, /addEventListener\("resize", layoutMidiLamp\)/); +}); + test("the web shell keeps midi opt-in so it cannot silence the bank by surprise", () => { assert.match(webShell, /import createOskiewarMidi from "\/oskiewar-midi\.mjs"/); - assert.match(webShell, /localStorage\.getItem\("oskiewar-midi"\)/); assert.match(webShell, /oskiewarMidi\.connectSignals\(globalThis\)/); assert.match(webShell, /oskiewarSfx\.mute\(true\)/, "the bank is muted unless layered"); assert.match(webShell, /pagehide[\s\S]{0,60}panic\(\)/, "held notes are released on hide"); -- 2.54.0