web: fix PixiJS texture frame (Rectangle), renderer mount error state; admin certification writes game_versions; docs
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
4 changed files +14 −7
modified
apps/web/src/components/game/game-client.tsx
+7 −1
@@ -87,7 +87,13 @@ export function GameClient({ game, definition }: Props) { | ||
| 87 | 87 | let cancelled = false; |
| 88 | 88 | (async () => { |
| 89 | 89 | setProgress(15); |
| 90 | − await renderer.mount(canvasRef.current!); | |
| 90 | + try { | |
| 91 | + await renderer.mount(canvasRef.current!); | |
| 92 | + } catch (e) { | |
| 93 | + console.error("renderer mount failed", e); | |
| 94 | + if (!cancelled) setError({ code: "GAME_UNAVAILABLE", message: "Your browser could not start the game renderer (WebGL required)." }); | |
| 95 | + return; | |
| 96 | + } | |
| 91 | 97 | setProgress(60); |
| 92 | 98 | try { |
| 93 | 99 | const launch = await api<{ state: ClientOutcome["stateAfter"] | null }>(`/api/games/${game.slug}/launch`, { method: "POST" }); |
modified
apps/web/src/components/game/renderer.ts
+2 −2
@@ -1,6 +1,6 @@ | ||
| 1 | 1 | "use client"; |
| 2 | 2 | |
| 3 | −import { Application, Container, Graphics, Sprite, Text, TextStyle, Texture, type Renderer } from "pixi.js"; | |
| 3 | +import { Application, Container, Graphics, Rectangle, Sprite, Text, TextStyle, Texture, type Renderer } from "pixi.js"; | |
| 4 | 4 | import type { SpinStep } from "@spinza/game-core/client"; |
| 5 | 5 | import type { ClientDefinition } from "./types"; |
| 6 | 6 | import { drawSymbol, hexColor, lighten } from "./symbol-art"; |
@@ -350,7 +350,7 @@ export class SlotRenderer { | ||
| 350 | 350 | const size = 176; |
| 351 | 351 | for (const s of this.opts.def.symbols) { |
| 352 | 352 | const art = drawSymbol(s.style, { size, tier: s.tier, kind: s.kind }); |
| 353 | − const tex = renderer.generateTexture({ target: art, resolution: 2, frame: { x: -size / 2, y: -size / 2, width: size, height: size } as never }); | |
| 353 | + const tex = renderer.generateTexture({ target: art, resolution: 2, frame: new Rectangle(-size / 2, -size / 2, size, size) }); | |
| 354 | 354 | this.textures.set(s.id, tex); |
| 355 | 355 | art.destroy({ children: true }); |
| 356 | 356 | } |
modified
apps/web/src/components/lobby/game-art.tsx
+4 −3
@@ -701,7 +701,7 @@ export function GameArt({ slug, name, palette, backdrop, particles, variant = "c | ||
| 701 | 701 | const { w: W, h: H, pad, maxFont, oneLine } = DIMS[variant]; |
| 702 | 702 | const rnd = mulberry32(hashString(`${slug}:${variant}`)); |
| 703 | 703 | const ctx: Ctx = { W, H, rnd, p: palette, id }; |
| 704 | − const Motif = MOTIFS[motifKey]; | |
| 704 | + const motif = MOTIFS[motifKey]; | |
| 705 | 705 | |
| 706 | 706 | return ( |
| 707 | 707 | <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid slice" className={cn("block h-full w-full", className)} role={label ? "img" : undefined} aria-label={label} aria-hidden={label ? undefined : true}> |
@@ -750,8 +750,9 @@ export function GameArt({ slug, name, palette, backdrop, particles, variant = "c | ||
| 750 | 750 | </filter> |
| 751 | 751 | </defs> |
| 752 | 752 | <rect width={W} height={H} fill={`url(#${id}-bg)`} /> |
| 753 | − <Motif {...ctx} /> | |
| 754 | − <Particles {...ctx} kind={pt} /> | |
| 753 | + {/* Called as plain functions (not components) so the seeded PRNG sequence is consumed exactly once per render — keeps SSR and client output identical even under StrictMode double-rendering. */} | |
| 754 | + {motif(ctx)} | |
| 755 | + {Particles({ ...ctx, kind: pt })} | |
| 755 | 756 | <rect width={W} height={H} fill={`url(#${id}-vignette)`} /> |
| 756 | 757 | <rect x="0.5" y="0.5" width={W - 1} height={H - 1} fill="none" stroke="#fff" strokeOpacity="0.06" /> |
| 757 | 758 | {showName ? <Title name={name} W={W} H={H} pad={pad} maxFont={maxFont} oneLine={oneLine} p={palette} /> : null} |
modified
qa/e2e.mjs
+1 −1
@@ -13,7 +13,7 @@ let chromium; | ||
| 13 | 13 | try { |
| 14 | 14 | ({ chromium } = require("playwright")); |
| 15 | 15 | } catch { |
| 16 | − ({ chromium } = require(path.join(process.env.HOME ?? "", "Desktop/Projets/apps-web/lou-ka/frontend/node_modules/playwright"))); | |
| 16 | + ({ chromium } = require(path.join(process.env.HOME ?? "", "Desktop/Projets/apps-web/fetcha/qa/node_modules/playwright"))); | |
| 17 | 17 | } |
| 18 | 18 | |
| 19 | 19 | const BASE = (process.env.BASE ?? "http://localhost:8230").replace(/\/$/, ""); |
| 20 | 20 | |