From 9b6341fe46315331dba32e9565135c09a0990edc Mon Sep 17 00:00:00 2001 From: tekki mariani Date: Sat, 21 Mar 2026 11:19:54 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20file=20logging=20via=20Vite?= =?UTF-8?q?=20middleware=20to=20ZoomTestScene?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vite dev server gets a /api/log middleware (POST appends to game-test.log, DELETE clears it). ZoomTestScene writes a zoom event with before/after state on every scroll, plus a full snapshot every 2 seconds. Log entries are newline-delimited JSON. --- .claude/settings.local.json | 35 +++++++++++++ game-test.log | 5 ++ src/test/ZoomTestScene.ts | 102 +++++++++++++++++++++++++++++++++--- vite.config.ts | 30 ++++++++++- 4 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 game-test.log diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..ddf8a33 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,35 @@ +{ + "permissions": { + "allow": [ + "Bash(curl:)", + "Bash(curl -s \"https://git.zally.dev/api/v1/repos/tekki/nissefolk/issues/1\" -H \"Authorization: token de54ccf9eadd5950a6ea5fa264b6404acdecc732\")", + "Bash(python3 -m json.tool)", + "Bash(curl -s \"https://git.zally.dev/api/v1/repos/tekki/nissefolk/issues/1/timeline\" -H \"Authorization: token de54ccf9eadd5950a6ea5fa264b6404acdecc732\")", + "Bash(curl:*)", + "Bash(python3 -c \":*)", + "Bash(git checkout:*)", + "Bash(npx tsc:*)", + "Bash(npm run:*)", + "Bash(/usr/local/bin/npm run:*)", + "Bash(/home/tekki/.nvm/versions/node/v24.14.0/bin/npm run:*)", + "Bash(export PATH=\"/home/tekki/.nvm/versions/node/v24.14.0/bin:$PATH\")", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\(''''html_url'''', d.get\\(''''message'''',''''''''\\)\\)\\)\")", + "Bash(git pull:*)", + "Bash(for id:*)", + "Bash(do echo:*)", + "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\(''''html_url'''',''''''''\\)\\)\")", + "Bash(TOKEN=\"de54ccf9eadd5950a6ea5fa264b6404acdecc732\" BASE=\"https://git.zally.dev/api/v1/repos/tekki/nissefolk\" __NEW_LINE_2bc8ebfb809e4939__ for id in 5 6 7 9)", + "Bash(TOKEN=\"de54ccf9eadd5950a6ea5fa264b6404acdecc732\")", + "Bash(BASE=\"https://git.zally.dev/api/v1/repos/tekki/nissefolk\")", + "Bash(__NEW_LINE_5d5fe245d6f316dc__ for:*)", + "Bash(do)", + "Bash(done)", + "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\(''''html_url'''',''''''''\\), d.get\\(''''number'''',''''''''\\), d.get\\(''''message'''',''''''''\\)\\)\")", + "Bash(git remote:*)", + "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\(''''login'''',''''''''\\), d.get\\(''''message'''',''''''''\\)\\)\")" + ] + } +} diff --git a/game-test.log b/game-test.log new file mode 100644 index 0000000..9ca65be --- /dev/null +++ b/game-test.log @@ -0,0 +1,5 @@ +{"t":1774091984264,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":0,"y":0},"world":{"x":0,"y":0}}} +{"t":1774091986280,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":288,"y":400},"world":{"x":609,"y":961.5}}} +{"t":1774091988280,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":288,"y":400},"world":{"x":609,"y":961.5}}} +{"t":1774091990281,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":288,"y":400},"world":{"x":609,"y":961.5}}} +{"t":1774091992281,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":288,"y":400},"world":{"x":609,"y":961.5}}} diff --git a/src/test/ZoomTestScene.ts b/src/test/ZoomTestScene.ts index 4468841..f848663 100644 --- a/src/test/ZoomTestScene.ts +++ b/src/test/ZoomTestScene.ts @@ -1,17 +1,19 @@ import Phaser from 'phaser' import { TILE_SIZE } from '../config' -const GRID_TILES = 50 // world size in tiles -const MIN_ZOOM = 0.25 -const MAX_ZOOM = 4.0 -const ZOOM_STEP = 0.1 -const MARKER_EVERY = 5 // small crosshair every N tiles -const LABEL_EVERY = 10 // coordinate label every N tiles -const CAMERA_SPEED = 400 // px/s +const GRID_TILES = 50 // world size in tiles +const MIN_ZOOM = 0.25 +const MAX_ZOOM = 4.0 +const ZOOM_STEP = 0.1 +const MARKER_EVERY = 5 // small crosshair every N tiles +const LABEL_EVERY = 10 // coordinate label every N tiles +const CAMERA_SPEED = 400 // px/s +const SNAPSHOT_EVERY = 2000 // ms between periodic log snapshots /** * First test scene: observes pure Phaser default zoom behavior. * No custom scroll compensation — cam.setZoom() only, zoom anchors to camera center. + * Logs zoom events and periodic snapshots to /api/log (written to game-test.log). * * Controls: Scroll wheel to zoom, WASD / Arrow keys to pan. */ @@ -27,12 +29,17 @@ export class ZoomTestScene extends Phaser.Scene { a: Phaser.Input.Keyboard.Key d: Phaser.Input.Keyboard.Key } + private snapshotTimer = 0 constructor() { super({ key: 'ZoomTest' }) } create(): void { + // Clear log file at scene start + fetch('/api/log', { method: 'DELETE' }) + this.writeLog('scene_start', { tileSize: TILE_SIZE, gridTiles: GRID_TILES }) + this.drawGrid() this.setupCamera() this.setupInput() @@ -111,6 +118,7 @@ export class ZoomTestScene extends Phaser.Scene { /** * Registers scroll wheel zoom and stores keyboard key references. * Zoom uses cam.setZoom() only — pure Phaser default, anchors to camera center. + * Each zoom event is logged immediately with before/after state. */ private setupInput(): void { const cam = this.cameras.main @@ -128,13 +136,42 @@ export class ZoomTestScene extends Phaser.Scene { } this.input.on('wheel', ( - _ptr: Phaser.Input.Pointer, + ptr: Phaser.Input.Pointer, _objs: unknown, _dx: number, dy: number ) => { + const zoomBefore = cam.zoom + const scrollXBefore = cam.scrollX + const scrollYBefore = cam.scrollY + const newZoom = Phaser.Math.Clamp(cam.zoom - Math.sign(dy) * ZOOM_STEP, MIN_ZOOM, MAX_ZOOM) cam.setZoom(newZoom) + + // Log after Phaser has applied the zoom (next microtask so values are updated) + setTimeout(() => { + this.writeLog('zoom', { + direction: dy > 0 ? 'out' : 'in', + zoomBefore: +zoomBefore.toFixed(4), + zoomAfter: +cam.zoom.toFixed(4), + scrollX_before: +scrollXBefore.toFixed(2), + scrollY_before: +scrollYBefore.toFixed(2), + scrollX_after: +cam.scrollX.toFixed(2), + scrollY_after: +cam.scrollY.toFixed(2), + scrollX_delta: +(cam.scrollX - scrollXBefore).toFixed(2), + scrollY_delta: +(cam.scrollY - scrollYBefore).toFixed(2), + mouseScreen: { x: +ptr.x.toFixed(1), y: +ptr.y.toFixed(1) }, + mouseWorld: { x: +ptr.worldX.toFixed(2), y: +ptr.worldY.toFixed(2) }, + centerWorld_after: { + x: +(cam.scrollX + (cam.width / cam.zoom) / 2).toFixed(2), + y: +(cam.scrollY + (cam.height / cam.zoom) / 2).toFixed(2), + }, + vpTiles_after: { + w: +((cam.width / cam.zoom) / TILE_SIZE).toFixed(3), + h: +((cam.height / cam.zoom) / TILE_SIZE).toFixed(3), + }, + }) + }, 0) }) } @@ -157,6 +194,13 @@ export class ZoomTestScene extends Phaser.Scene { update(_time: number, delta: number): void { this.handleKeyboard(delta) this.updateOverlay() + + // Periodic snapshot + this.snapshotTimer += delta + if (this.snapshotTimer >= SNAPSHOT_EVERY) { + this.snapshotTimer = 0 + this.writeSnapshot() + } } /** @@ -236,4 +280,46 @@ export class ZoomTestScene extends Phaser.Scene { this.logText.setText(lines) } + + /** + * Writes a periodic full-state snapshot to the log. + */ + private writeSnapshot(): void { + const cam = this.cameras.main + const ptr = this.input.activePointer + const vpW = cam.width / cam.zoom + const vpH = cam.height / cam.zoom + + this.writeLog('snapshot', { + zoom: +cam.zoom.toFixed(4), + scrollX: +cam.scrollX.toFixed(2), + scrollY: +cam.scrollY.toFixed(2), + vpScreen: { w: cam.width, h: cam.height }, + vpWorld: { w: +vpW.toFixed(2), h: +vpH.toFixed(2) }, + vpTiles: { w: +((vpW / TILE_SIZE).toFixed(3)), h: +((vpH / TILE_SIZE).toFixed(3)) }, + centerWorld: { + x: +(cam.scrollX + vpW / 2).toFixed(2), + y: +(cam.scrollY + vpH / 2).toFixed(2), + }, + mouse: { + screen: { x: +ptr.x.toFixed(1), y: +ptr.y.toFixed(1) }, + world: { x: +ptr.worldX.toFixed(2), y: +ptr.worldY.toFixed(2) }, + }, + }) + } + + /** + * POSTs a structured log entry to the Vite dev server middleware. + * Written to game-test.log in the project root. + * @param event - Event type label + * @param data - Payload object to serialize as JSON + */ + private writeLog(event: string, data: Record): void { + const entry = JSON.stringify({ t: Date.now(), event, ...data }) + fetch('/api/log', { + method: 'POST', + headers: { 'Content-Type': 'text/plain' }, + body: entry, + }).catch(() => { /* swallow if dev server not running */ }) + } } diff --git a/vite.config.ts b/vite.config.ts index 6d5059d..61741bc 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,11 +1,39 @@ import { defineConfig } from 'vite' import { resolve } from 'path' +import fs from 'fs' + +const LOG_FILE = resolve(__dirname, 'game-test.log') export default defineConfig({ server: { port: 3000, - host: true + host: true, }, + plugins: [ + { + name: 'game-logger', + configureServer(server) { + server.middlewares.use('/api/log', (req, res) => { + if (req.method === 'POST') { + let body = '' + req.on('data', chunk => { body += chunk }) + req.on('end', () => { + fs.appendFileSync(LOG_FILE, body + '\n', 'utf8') + res.writeHead(200) + res.end('ok') + }) + } else if (req.method === 'DELETE') { + fs.writeFileSync(LOG_FILE, '', 'utf8') + res.writeHead(200) + res.end('cleared') + } else { + res.writeHead(405) + res.end() + } + }) + }, + }, + ], build: { outDir: 'dist', assetsInlineLimit: 0,