From 373f9265143d651489eb4bc046e827e1ca86961d Mon Sep 17 00:00:00 2001 From: Henrik Kurelid Date: Sun, 25 Oct 2026 03:00:43 +0100 Subject: [PATCH 1/2] fix(nr-use): the light-gun sight no longer hides the web picture behind black The stylesheet paints every canvas black, and the sight's overlay is a canvas over the picture, so capturing the mouse in Vs. Duck Hunt on web covered the game. The overlay now declares a transparent background. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QmNN1L1G3mw6Daxa3WfkCj --- web/src/display/crosshair.ts | 4 +- .../display/crosshair_overlay_style.test.ts | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 web/src/display/crosshair_overlay_style.test.ts diff --git a/web/src/display/crosshair.ts b/web/src/display/crosshair.ts index 65f53594e..c84e37130 100644 --- a/web/src/display/crosshair.ts +++ b/web/src/display/crosshair.ts @@ -25,7 +25,9 @@ export function createCrosshair( overlayCanvas.style.left = "0"; overlayCanvas.style.pointerEvents = "none"; // Allow mouse events to pass through overlayCanvas.style.zIndex = "10"; - + // The stylesheet paints every canvas black behind its picture; this one lies over the picture. + overlayCanvas.style.background = "transparent"; + // Match the target canvas size overlayCanvas.width = targetCanvas.width; overlayCanvas.height = targetCanvas.height; diff --git a/web/src/display/crosshair_overlay_style.test.ts b/web/src/display/crosshair_overlay_style.test.ts new file mode 100644 index 000000000..04dec0dd8 --- /dev/null +++ b/web/src/display/crosshair_overlay_style.test.ts @@ -0,0 +1,37 @@ +// @vitest-environment jsdom +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { afterEach, expect, it } from "vitest"; + +import { createCrosshair } from "./crosshair"; + +const currentDir = dirname(fileURLToPath(import.meta.url)); +const mainCss = readFileSync(resolve(currentDir, "../../main.css"), "utf8"); + +afterEach(() => { + document.head.innerHTML = ""; + document.body.innerHTML = ""; +}); + +// nr-use: the shell's stylesheet paints every canvas black, and the sight's overlay is a canvas +// laid over the picture, so a captured light gun hid the whole game behind a black sheet. +it("Given the shell's stylesheet, When the light-gun sight's overlay is created over the picture, Then the overlay is see-through", () => { + const style = document.createElement("style"); + style.textContent = mainCss; + document.head.appendChild(style); + const wrap = document.createElement("div"); + wrap.className = "screen-wrap"; + const screen = document.createElement("canvas"); + screen.id = "screen"; + wrap.appendChild(screen); + document.body.appendChild(wrap); + + createCrosshair(screen); + + const overlay = wrap.querySelectorAll("canvas")[1]; + expect(overlay).toBeDefined(); + expect(getComputedStyle(screen).backgroundColor).toBe("rgb(0, 0, 0)"); + expect(getComputedStyle(overlay).backgroundColor).toBe("rgba(0, 0, 0, 0)"); +}); From d755cead48d371de43bf948a2a88287ee5154be6 Mon Sep 17 00:00:00 2001 From: Henrik Kurelid Date: Sun, 25 Oct 2026 03:02:22 +0100 Subject: [PATCH 2/2] test(nr-use): stub the 2D context in the sight overlay test Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QmNN1L1G3mw6Daxa3WfkCj --- web/src/display/crosshair_overlay_style.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/src/display/crosshair_overlay_style.test.ts b/web/src/display/crosshair_overlay_style.test.ts index 04dec0dd8..aaf0fd3cb 100644 --- a/web/src/display/crosshair_overlay_style.test.ts +++ b/web/src/display/crosshair_overlay_style.test.ts @@ -3,7 +3,7 @@ import { readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { afterEach, expect, it } from "vitest"; +import { afterEach, expect, it, vi } from "vitest"; import { createCrosshair } from "./crosshair"; @@ -11,6 +11,7 @@ const currentDir = dirname(fileURLToPath(import.meta.url)); const mainCss = readFileSync(resolve(currentDir, "../../main.css"), "utf8"); afterEach(() => { + vi.restoreAllMocks(); document.head.innerHTML = ""; document.body.innerHTML = ""; }); @@ -18,6 +19,10 @@ afterEach(() => { // nr-use: the shell's stylesheet paints every canvas black, and the sight's overlay is a canvas // laid over the picture, so a captured light gun hid the whole game behind a black sheet. it("Given the shell's stylesheet, When the light-gun sight's overlay is created over the picture, Then the overlay is see-through", () => { + // jsdom has no 2D context; creating the sight draws nothing, so an empty one is enough. + vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue( + {} as CanvasRenderingContext2D, + ); const style = document.createElement("style"); style.textContent = mainCss; document.head.appendChild(style);