diff --git a/public/hunt.js b/public/hunt.js index 990ffb9..05d9bc6 100644 --- a/public/hunt.js +++ b/public/hunt.js @@ -23,7 +23,7 @@ const COLORS = ['Red', 'Blue', 'Yellow']; export function createHunt(THREE, scene, opts = {}) { const roster = opts.roster || []; - const maxActive = opts.maxActive ?? 4; + let maxActive = opts.maxActive ?? 4; const spawnArcDeg = opts.spawnArcDeg ?? 90; // ±45° — tighter so ghosts land in front const fleeMinMs = opts.fleeMinMs ?? 12000; // longer discovery window const fleeMaxMs = opts.fleeMaxMs ?? 18000; @@ -82,7 +82,8 @@ export function createHunt(THREE, scene, opts = {}) { fleeAt: performance.now() + (fleeMinMs + Math.random() * (fleeMaxMs - fleeMinMs)), // escapes if ignored }); haunt = Math.min(100, haunt + 4); // presence raises the meter - onEvent({ type: 'spawn', id, ghost: g, haunt }); + const p = visual.object3d.position; + onEvent({ type: 'spawn', id, ghost: g, haunt, pos: { x: +p.x.toFixed(2), y: +p.y.toFixed(2), z: +p.z.toFixed(2) } }); } // --- capture attempt: called by the page with a charge level 0..1 --- @@ -150,6 +151,8 @@ export function createHunt(THREE, scene, opts = {}) { return { update, spawn, fireAt, capture, flee, setLure, listActive, + setMaxActive(n) { maxActive = Math.max(1, n | 0); }, + get maxActive() { return maxActive; }, get haunt() { return haunt; }, get captured() { return captured.slice(); }, get luredColor() { return luredColor; }, diff --git a/routes/hunt-log.js b/routes/hunt-log.js new file mode 100644 index 0000000..5f1cf38 --- /dev/null +++ b/routes/hunt-log.js @@ -0,0 +1,52 @@ +/** + * routes/hunt-log.js — receives hunt telemetry from the client and appends it + * to a daily log file on the server. Debug aid: confirms spawns / detections / + * captures are firing server-side even when something looks wrong on-device. + * + * Wire in server.js: + * import huntLogRoutes from './routes/hunt-log.js'; + * app.use('/api/hunt-log', huntLogRoutes); + * + * Log file: logs/hunt-YYYY-MM-DD.log (one JSON object per line) + */ +import { Router } from 'express'; +import { appendFile, mkdir } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const LOG_DIR = join(__dirname, '..', 'logs'); + +const router = Router(); + +// Accept a single event or a batch. Keep it small + permissive. +router.post('/', async (req, res) => { + try { + const body = req.body || {}; + const events = Array.isArray(body.events) ? body.events : [body]; + const day = new Date().toISOString().slice(0, 10); + const file = join(LOG_DIR, `hunt-${day}.log`); + + await mkdir(LOG_DIR, { recursive: true }); + const lines = events.map(e => JSON.stringify({ + t: new Date().toISOString(), + ip: req.ip, + type: e.type || 'unknown', + ghost: e.ghost || null, + color: e.color || null, + rarity: e.rarity || null, + haunt: typeof e.haunt === 'number' ? Math.round(e.haunt) : null, + pos: e.pos || null, // {x,y,z} spawn position when present + session: e.session || null, // client session id + })).join('\n') + '\n'; + + await appendFile(file, lines, 'utf8'); + res.json({ ok: true, logged: events.length }); + } catch (err) { + // never let logging break the hunt + console.error('hunt-log error:', err.message); + res.status(200).json({ ok: false }); + } +}); + +export default router;