diff --git a/docs/FINDINGS.md b/docs/FINDINGS.md index 0b4fc19..07b5b67 100644 --- a/docs/FINDINGS.md +++ b/docs/FINDINGS.md @@ -87,6 +87,18 @@ The `findCaptures()` in `engine.ts` correctly implements: - Sum subsets via power set enumeration ✓ - `applyMove()` auto-captures when possible ✓ +### SCOPONE-0004: Phaser Text Resolution for Crisp Rendering (2026-03-31) + +**Source**: Phaser 3 API docs (Context7 — `/websites/phaser_io_api-documentation`) + +Phaser `Text` objects render to an internal Canvas. When the game uses `Scale.FIT` (scaling 1280×720 to a larger display), the text canvas is rasterized at 1× and then upscaled by CSS, causing blur. + +**Fix**: Set the `resolution` property on each `TextStyle` or call `text.setResolution(value)`. A value of `2` doubles the internal canvas size, producing sharper text on high-DPI and scaled displays at the cost of more memory. The style property `resolution: 0` (default) uses the game's resolution (defaults to 1). + +**Recommended**: Use `resolution: 2` in all text style objects for critical UI text (menu, score bar, labels). Avoid higher values to limit memory impact. + +**Available since**: Phaser 3.12.0 (current project uses ^3.87.0). + ### SCOPONE-0003: findCaptures() Change Analysis (2026-03-31) **Current behavior** (engine.ts lines 43-62): When direct matches exist, `findCaptures()` bundles ALL into one `Card[]` and returns immediately (`results.push([...directMatches]); return results`). Sum captures are never computed when direct matches exist. diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index 2c2ce1c..86f7870 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -222,23 +222,23 @@ export class GameScene extends Phaser.Scene { private buildScoreBar(W: number): void { // [trueref: fillRoundedRect] Panel background const bg = this.add.graphics().setDepth(8); - bg.fillStyle(0x050e07, 0.92); + bg.fillStyle(0x050e07); bg.fillRect(0, 0, W, SCOREBAR_H); - bg.lineStyle(1, 0xffd700, 0.35); + bg.lineStyle(1, 0xffd700, 0.7); bg.lineBetween(0, SCOREBAR_H, W, SCOREBAR_H); const mkTxt = (x: number, y: number, val: string, color = '#ffffff', size = '15px') => - this.add.text(x, y, val, { fontFamily: 'monospace', fontSize: size, color }) + this.add.text(x, y, val, { fontFamily: 'monospace', fontSize: size, color, resolution: 2 }) .setOrigin(0.5).setDepth(9); // Left side — Team A this.add.text(10, SCOREBAR_H / 2, 'TEAM A (Tu + Compagno)', { - fontFamily: 'serif', fontSize: '13px', color: '#aaffaa', + fontFamily: 'serif', fontSize: '13px', color: '#aaffaa', resolution: 2, }).setOrigin(0, 0.5).setDepth(9); // Right side — Team B this.add.text(W - 10, SCOREBAR_H / 2, 'TEAM B (AI Ovest + AI Est)', { - fontFamily: 'serif', fontSize: '13px', color: '#ffaaaa', + fontFamily: 'serif', fontSize: '13px', color: '#ffaaaa', resolution: 2, }).setOrigin(1, 0.5).setDepth(9); // Center — Round @@ -252,10 +252,10 @@ export class GameScene extends Phaser.Scene { cols.forEach((_, i) => { const label = ['Sc', 'Ca', 'De', 'Pr', 'Pt'][i]; this.add.text(xA[i], SCOREBAR_H * 0.28, label, { - fontFamily: 'monospace', fontSize: '10px', color: '#666666', + fontFamily: 'monospace', fontSize: '10px', color: '#999999', resolution: 2, }).setOrigin(0.5).setDepth(9); this.add.text(xB[i], SCOREBAR_H * 0.28, label, { - fontFamily: 'monospace', fontSize: '10px', color: '#666666', + fontFamily: 'monospace', fontSize: '10px', color: '#999999', resolution: 2, }).setOrigin(0.5).setDepth(9); }); @@ -267,14 +267,14 @@ export class GameScene extends Phaser.Scene { scope: mkA(0), cards: mkA(1), denari: mkA(2), prim: mkA(3), total: this.add.text(xA[4], SCOREBAR_H * 0.72, '0', { fontFamily: 'Georgia, serif', fontSize: '20px', color: '#aaffaa', - stroke: '#000', strokeThickness: 2, + stroke: '#000', strokeThickness: 2, resolution: 2, }).setOrigin(0.5).setDepth(9), }; this.hudB = { scope: mkB(0), cards: mkB(1), denari: mkB(2), prim: mkB(3), total: this.add.text(xB[4], SCOREBAR_H * 0.72, '0', { fontFamily: 'Georgia, serif', fontSize: '20px', color: '#ffaaaa', - stroke: '#000', strokeThickness: 2, + stroke: '#000', strokeThickness: 2, resolution: 2, }).setOrigin(0.5).setDepth(9), }; } @@ -391,17 +391,18 @@ export class GameScene extends Phaser.Scene { // --------------------------------------------------------------------------- private buildPlayerLabels(W: number, H: number): void { - const defs: Array<{ idx: PlayerIndex; x: number; y: number; color: string; txt: string }> = [ - { idx: 0, x: W / 2, y: H - CH_H - 56, color: '#aaffaa', txt: 'Tu [Team A]' }, - { idx: 1, x: CH_A + 14, y: H / 2 + SCOREBAR_H / 2 - 60, color: '#ffaaaa', txt: 'AI\nOvest\n[B]' }, - { idx: 2, x: W / 2, y: SCOREBAR_H + 18, color: '#aaffaa', txt: 'Compagno [Team A]' }, - { idx: 3, x: W - CH_A - 14, y: H / 2 + SCOREBAR_H / 2 - 60, color: '#ffaaaa', txt: 'AI\nEst\n[B]' }, + const defs: Array<{ idx: PlayerIndex; x: number; y: number; color: string; + txt: string; originX: number; originY: number }> = [ + { idx: 0, x: W / 2, y: H - 8, color: '#aaffaa', txt: 'Tu [Team A]', originX: 0.5, originY: 1 }, + { idx: 1, x: 6, y: H / 2 + SCOREBAR_H / 2, color: '#ffaaaa', txt: 'AI\nOvest\n[B]', originX: 0, originY: 0.5 }, + { idx: 2, x: W / 2, y: SCOREBAR_H + CH_A + 34, color: '#aaffaa', txt: 'Compagno [Team A]', originX: 0.5, originY: 0 }, + { idx: 3, x: W - 6, y: H / 2 + SCOREBAR_H / 2, color: '#ffaaaa', txt: 'AI\nEst\n[B]', originX: 1, originY: 0.5 }, ]; for (const d of defs) { const lbl = this.add.text(d.x, d.y, d.txt, { fontFamily: 'serif', fontSize: '12px', color: d.color, - stroke: '#000', strokeThickness: 1, align: 'center', - }).setOrigin(0.5).setDepth(2); + stroke: '#000', strokeThickness: 1, align: 'center', resolution: 2, + }).setOrigin(d.originX, d.originY).setDepth(2); this.playerLabels.set(d.idx, lbl); } } diff --git a/src/scenes/MenuScene.ts b/src/scenes/MenuScene.ts index 9522e38..daea9ae 100644 --- a/src/scenes/MenuScene.ts +++ b/src/scenes/MenuScene.ts @@ -19,12 +19,14 @@ export class MenuScene extends Phaser.Scene { color: '#ffd700', stroke: '#000000', strokeThickness: 4, + resolution: 2, }).setOrigin(0.5); this.add.text(W / 2, H * 0.32, '2 vs 2 · Tu + Compagno vs 2 AI', { fontFamily: 'serif', fontSize: '22px', color: '#ccffcc', + resolution: 2, }).setOrigin(0.5); // Rules summary @@ -39,6 +41,7 @@ export class MenuScene extends Phaser.Scene { fontFamily: 'serif', fontSize: '18px', color: '#ffffff', + resolution: 2, }).setOrigin(0.5); }); @@ -49,6 +52,7 @@ export class MenuScene extends Phaser.Scene { fontFamily: 'Georgia, serif', fontSize: '22px', color: '#1a5c2a', + resolution: 2, }).setOrigin(0.5); btn.on('pointerover', () => btn.setFillStyle(0xffec6e));