✨ add file logging via Vite middleware to ZoomTestScene
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.
This commit is contained in:
35
.claude/settings.local.json
Normal file
35
.claude/settings.local.json
Normal file
@@ -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'''',''''''''\\)\\)\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
5
game-test.log
Normal file
5
game-test.log
Normal file
@@ -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}}}
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
import Phaser from 'phaser'
|
import Phaser from 'phaser'
|
||||||
import { TILE_SIZE } from '../config'
|
import { TILE_SIZE } from '../config'
|
||||||
|
|
||||||
const GRID_TILES = 50 // world size in tiles
|
const GRID_TILES = 50 // world size in tiles
|
||||||
const MIN_ZOOM = 0.25
|
const MIN_ZOOM = 0.25
|
||||||
const MAX_ZOOM = 4.0
|
const MAX_ZOOM = 4.0
|
||||||
const ZOOM_STEP = 0.1
|
const ZOOM_STEP = 0.1
|
||||||
const MARKER_EVERY = 5 // small crosshair every N tiles
|
const MARKER_EVERY = 5 // small crosshair every N tiles
|
||||||
const LABEL_EVERY = 10 // coordinate label every N tiles
|
const LABEL_EVERY = 10 // coordinate label every N tiles
|
||||||
const CAMERA_SPEED = 400 // px/s
|
const CAMERA_SPEED = 400 // px/s
|
||||||
|
const SNAPSHOT_EVERY = 2000 // ms between periodic log snapshots
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* First test scene: observes pure Phaser default zoom behavior.
|
* First test scene: observes pure Phaser default zoom behavior.
|
||||||
* No custom scroll compensation — cam.setZoom() only, zoom anchors to camera center.
|
* 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.
|
* 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
|
a: Phaser.Input.Keyboard.Key
|
||||||
d: Phaser.Input.Keyboard.Key
|
d: Phaser.Input.Keyboard.Key
|
||||||
}
|
}
|
||||||
|
private snapshotTimer = 0
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({ key: 'ZoomTest' })
|
super({ key: 'ZoomTest' })
|
||||||
}
|
}
|
||||||
|
|
||||||
create(): void {
|
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.drawGrid()
|
||||||
this.setupCamera()
|
this.setupCamera()
|
||||||
this.setupInput()
|
this.setupInput()
|
||||||
@@ -111,6 +118,7 @@ export class ZoomTestScene extends Phaser.Scene {
|
|||||||
/**
|
/**
|
||||||
* Registers scroll wheel zoom and stores keyboard key references.
|
* Registers scroll wheel zoom and stores keyboard key references.
|
||||||
* Zoom uses cam.setZoom() only — pure Phaser default, anchors to camera center.
|
* 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 {
|
private setupInput(): void {
|
||||||
const cam = this.cameras.main
|
const cam = this.cameras.main
|
||||||
@@ -128,13 +136,42 @@ export class ZoomTestScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.input.on('wheel', (
|
this.input.on('wheel', (
|
||||||
_ptr: Phaser.Input.Pointer,
|
ptr: Phaser.Input.Pointer,
|
||||||
_objs: unknown,
|
_objs: unknown,
|
||||||
_dx: number,
|
_dx: number,
|
||||||
dy: 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)
|
const newZoom = Phaser.Math.Clamp(cam.zoom - Math.sign(dy) * ZOOM_STEP, MIN_ZOOM, MAX_ZOOM)
|
||||||
cam.setZoom(newZoom)
|
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 {
|
update(_time: number, delta: number): void {
|
||||||
this.handleKeyboard(delta)
|
this.handleKeyboard(delta)
|
||||||
this.updateOverlay()
|
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)
|
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<string, unknown>): 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 */ })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,39 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
const LOG_FILE = resolve(__dirname, 'game-test.log')
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
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: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
assetsInlineLimit: 0,
|
assetsInlineLimit: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user