commit 17733c85aa2ea0577cff75b40c6e46ee3e4cccb5
parent df1c7123b6e429d644768b17e155bcacf23697da
Author: Kyle Barlow <kb@kylebarlow.com>
Date: Wed, 22 Apr 2026 14:29:07 -0700
Scale game up 5x and zoom out camera to improve perceived rendering framerate. Add settings gear button for speed menu.
Diffstat:
3 files changed, 79 insertions(+), 26 deletions(-)
diff --git a/games/fire-truck/game.js b/games/fire-truck/game.js
@@ -1,13 +1,14 @@
const FT = window.FireTruckLib;
-const CELL_SIZE = 96;
-const ROAD_WIDTH = 44;
-const BASE_SPEED = 34;
-const MAX_SPEED = 44;
-const ACCEL = 52;
-const BRAKE = 64;
+const SCALE = 5;
+const CELL_SIZE = 96 * SCALE;
+const ROAD_WIDTH = 44 * SCALE;
+const BASE_SPEED = 170;
+const MAX_SPEED = 220;
+const ACCEL = 260;
+const BRAKE = 320;
const PROMPT_TRIGGER_DIST = CELL_SIZE * 0.72;
-const STOP_LINE_DIST = 6;
+const STOP_LINE_DIST = 6 * SCALE;
const AUTO_FAIL_GRACE_MS = 4500;
class FireTruckScene extends Phaser.Scene {
@@ -78,9 +79,15 @@ class FireTruckScene extends Phaser.Scene {
}).setOrigin(0.5).setScrollFactor(0).setDepth(50);
this.statusText.setAlpha(0.88);
+ this.uiCamera = this.cameras.add(0, 0, this.scale.width, this.scale.height);
+ this.cameras.main.setZoom(1 / SCALE);
+
this.renderCity();
this.createTruck();
+ this.uiCamera.ignore([this.roadGraphics, this.buildingGraphics, this.truck]);
+ this.cameras.main.ignore([this.overlay, this.successOverlay, this.promptText, this.statusText]);
+
this.currentStep = this.route[0];
const start = this.worldPoint(-1, 0);
this.segmentEnd = this.worldPoint(this.currentStep.x, this.currentStep.y);
@@ -138,9 +145,9 @@ class FireTruckScene extends Phaser.Scene {
const py = y * CELL_SIZE - CELL_SIZE * 0.38;
const color = palette[Math.abs((x * 13 + y * 17) % palette.length)];
buildings.fillStyle(color, 1);
- buildings.fillRoundedRect(px, py, CELL_SIZE * 0.76, CELL_SIZE * 0.76, 10);
+ buildings.fillRoundedRect(px, py, CELL_SIZE * 0.76, CELL_SIZE * 0.76, 10 * SCALE);
buildings.fillStyle(0xffffff, 0.16);
- buildings.fillRect(px + 8, py + 8, CELL_SIZE * 0.3, 10);
+ buildings.fillRect(px + 8 * SCALE, py + 8 * SCALE, CELL_SIZE * 0.3, 10 * SCALE);
}
}
@@ -154,8 +161,8 @@ class FireTruckScene extends Phaser.Scene {
if (cell.exits.W) road.fillRect(center.x - CELL_SIZE / 2, center.y - ROAD_WIDTH / 2, CELL_SIZE / 2, ROAD_WIDTH);
road.fillStyle(0xfff4b1, 0.92);
- if (cell.exits.N && cell.exits.S) road.fillRect(center.x - 3, center.y - CELL_SIZE / 2 + 8, 6, CELL_SIZE - 16);
- if (cell.exits.E && cell.exits.W) road.fillRect(center.x - CELL_SIZE / 2 + 8, center.y - 3, CELL_SIZE - 16, 6);
+ if (cell.exits.N && cell.exits.S) road.fillRect(center.x - 3 * SCALE, center.y - CELL_SIZE / 2 + 8 * SCALE, 6 * SCALE, CELL_SIZE - 16 * SCALE);
+ if (cell.exits.E && cell.exits.W) road.fillRect(center.x - CELL_SIZE / 2 + 8 * SCALE, center.y - 3 * SCALE, CELL_SIZE - 16 * SCALE, 6 * SCALE);
});
}
@@ -166,6 +173,7 @@ class FireTruckScene extends Phaser.Scene {
const light = this.add.rectangle(-14, 0, 8, 8, 0x1d7cf2, 1);
this.truck = this.add.container(0, 0, [body, cab, ladder, light]);
this.truck.setDepth(35);
+ this.truck.setScale(SCALE);
}
onResize(gameSize) {
@@ -173,6 +181,7 @@ class FireTruckScene extends Phaser.Scene {
this.successOverlay.setSize(gameSize.width, gameSize.height);
this.promptText.setPosition(gameSize.width / 2, gameSize.height - 74);
this.statusText.setPosition(gameSize.width / 2, gameSize.height - 34);
+ if (this.uiCamera) this.uiCamera.setSize(gameSize.width, gameSize.height);
}
updateTargetSpeed() {
@@ -304,7 +313,7 @@ class FireTruckScene extends Phaser.Scene {
}
const remaining = Math.hypot(this.segmentEnd.x - this.truck.x, this.segmentEnd.y - this.truck.y);
- if (remaining <= 0.8) {
+ if (remaining <= 0.8 * SCALE) {
this.truck.setPosition(this.segmentEnd.x, this.segmentEnd.y);
this.advancePhase();
}
diff --git a/games/fire-truck/index.html b/games/fire-truck/index.html
@@ -51,37 +51,78 @@
}
#hud .title { font-size: 1.2rem; font-weight: 600; }
#hud .hint { font-size: 0.9rem; opacity: 0.78; }
- #settings {
- margin-top: 0.4rem;
- font-size: 0.9rem;
+ #settings-btn {
+ position: fixed;
+ top: 0.6rem;
+ right: 1rem;
+ z-index: 30;
+ background: rgba(255, 255, 255, 0.72);
+ border-radius: 50%;
+ width: 2.6rem;
+ height: 2.6rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ font-size: 1.4rem;
+ user-select: none;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+ transition: background 0.1s;
+ }
+ #settings-btn:hover { background: rgba(255, 255, 255, 0.9); }
+ #settings-panel {
+ position: fixed;
+ top: 3.6rem;
+ right: 1rem;
+ z-index: 30;
+ background: rgba(255, 255, 255, 0.92);
+ border-radius: 12px;
+ padding: 1rem;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ display: none;
+ font-size: 0.95rem;
+ color: #2e372d;
+ }
+ #settings-panel.open { display: block; }
+ #settings-panel label { margin-right: 0.5rem; font-weight: 600; }
+ #speed-select {
+ padding: 0.25rem 0.5rem;
+ font-family: inherit;
+ border-radius: 6px;
+ border: 1px solid #ccc;
}
#game-container { flex: 1; width: 100%; height: 100%; }
</style>
</head>
<body>
<nav><a href="../../index.html">← Back to KGames</a></nav>
+ <div id="settings-btn">⚙️</div>
+ <div id="settings-panel">
+ <label for="speed-select">Speed:</label>
+ <select id="speed-select">
+ <option value="0.5">0.5x</option>
+ <option value="1" selected>1x (Default)</option>
+ <option value="2">2x</option>
+ <option value="3">3x</option>
+ <option value="5">5x</option>
+ </select>
+ </div>
<div id="hud">
<div class="title">Arrow Key Fire Truck</div>
<div class="hint">Left arrow = left, up arrow = straight, right arrow = right</div>
- <div id="settings">
- <label for="speed-select">Speed:</label>
- <select id="speed-select">
- <option value="0.5">0.5x</option>
- <option value="1" selected>1x (Default)</option>
- <option value="2">2x</option>
- <option value="3">3x</option>
- <option value="5">5x</option>
- </select>
- </div>
</div>
<div id="game-container"></div>
<script>
if (typeof window.GAME_SPEED_MULTIPLIER === 'undefined') {
window.GAME_SPEED_MULTIPLIER = 1.0;
}
+ document.getElementById('settings-btn').addEventListener('click', () => {
+ document.getElementById('settings-panel').classList.toggle('open');
+ });
document.getElementById('speed-select').addEventListener('change', (e) => {
window.GAME_SPEED_MULTIPLIER = parseFloat(e.target.value);
if (window.__FT_SCENE__) window.__FT_SCENE__.updateTargetSpeed();
+ e.target.blur();
});
</script>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script>
diff --git a/tests/browser/runner.js b/tests/browser/runner.js
@@ -14,7 +14,7 @@ async function main() {
async function test(name, fn) {
const page = await browser.newPage();
// Speed up fire-truck tests
- await page.addInitScript(() => { window.GAME_SPEED_MULTIPLIER = 2.0; });
+ await page.addInitScript(() => { window.GAME_SPEED_MULTIPLIER = 8.0; });
const errors = [];
page.on('console', m => { if (m.type() === 'error') errors.push('[console] ' + m.text()); });
page.on('pageerror', e => { errors.push('[pageerror] ' + e.message); });
@@ -135,6 +135,7 @@ async function main() {
await test('fire-truck: prompt appears before an intersection', async (page, url) => {
await page.goto(`${url}/games/fire-truck/`, { waitUntil: 'domcontentloaded' });
+ await page.evaluate(() => { window.__FT_SCENE__.truck.setPosition(window.__FT_SCENE__.segmentEnd.x - 500, window.__FT_SCENE__.segmentEnd.y); });
await page.waitForFunction(() => window.__FT_SCENE__ && !!window.__FT_SCENE__.promptDir, null, { timeout: 25000 });
const promptDir = await page.evaluate(() => window.__FT_SCENE__.promptDir);
if (!['left', 'right', 'straight'].includes(promptDir)) throw new Error(`Unexpected prompt ${promptDir}`);
@@ -142,6 +143,7 @@ async function main() {
await test('fire-truck: wrong arrow stops the truck', async (page, url) => {
await page.goto(`${url}/games/fire-truck/`, { waitUntil: 'domcontentloaded' });
+ await page.evaluate(() => { window.__FT_SCENE__.truck.setPosition(window.__FT_SCENE__.segmentEnd.x - 500, window.__FT_SCENE__.segmentEnd.y); });
await page.waitForFunction(() => window.__FT_SCENE__ && !!window.__FT_SCENE__.promptDir, null, { timeout: 25000 });
// Freeze the truck so it doesn't cross the intersection while playwright is IPCing
await page.evaluate(() => { window.__FT_SCENE__.targetSpeed = 0; window.__FT_SCENE__.speed = 0; window.__FT_SCENE__.lastStepTime = performance.now(); });
@@ -156,6 +158,7 @@ async function main() {
await test('fire-truck: correct arrow after failure resumes movement', async (page, url) => {
await page.goto(`${url}/games/fire-truck/`, { waitUntil: 'domcontentloaded' });
+ await page.evaluate(() => { window.__FT_SCENE__.truck.setPosition(window.__FT_SCENE__.segmentEnd.x - 500, window.__FT_SCENE__.segmentEnd.y); });
await page.waitForFunction(() => window.__FT_SCENE__ && !!window.__FT_SCENE__.promptDir, null, { timeout: 25000 });
await page.evaluate(() => { window.__FT_SCENE__.targetSpeed = 0; window.__FT_SCENE__.speed = 0; window.__FT_SCENE__.lastStepTime = performance.now(); });
const promptDir = await page.evaluate(() => window.__FT_SCENE__.promptDir);