fix(SCOPONE-0004): improve UI text readability and score bar contrast
- MenuScene: add resolution: 2 to all text styles for crisp rendering - GameScene score bar: solid opaque background, brighter column headers (#999999), visible gold separator (alpha 0.7), resolution: 2 - GameScene player labels: reposition to viewport edges (South bottom, West left, East right), North after cards near table - Add resolution: 2 to all player label text styles
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user