Phase 3: AR hunt /hunt + launch page /play - shared-visual ghosts, colour lure, hauntometer
This commit is contained in:
+5
-2
@@ -23,7 +23,7 @@ const COLORS = ['Red', 'Blue', 'Yellow'];
|
|||||||
|
|
||||||
export function createHunt(THREE, scene, opts = {}) {
|
export function createHunt(THREE, scene, opts = {}) {
|
||||||
const roster = opts.roster || [];
|
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 spawnArcDeg = opts.spawnArcDeg ?? 90; // ±45° — tighter so ghosts land in front
|
||||||
const fleeMinMs = opts.fleeMinMs ?? 12000; // longer discovery window
|
const fleeMinMs = opts.fleeMinMs ?? 12000; // longer discovery window
|
||||||
const fleeMaxMs = opts.fleeMaxMs ?? 18000;
|
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
|
fleeAt: performance.now() + (fleeMinMs + Math.random() * (fleeMaxMs - fleeMinMs)), // escapes if ignored
|
||||||
});
|
});
|
||||||
haunt = Math.min(100, haunt + 4); // presence raises the meter
|
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 ---
|
// --- capture attempt: called by the page with a charge level 0..1 ---
|
||||||
@@ -150,6 +151,8 @@ export function createHunt(THREE, scene, opts = {}) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
update, spawn, fireAt, capture, flee, setLure, listActive,
|
update, spawn, fireAt, capture, flee, setLure, listActive,
|
||||||
|
setMaxActive(n) { maxActive = Math.max(1, n | 0); },
|
||||||
|
get maxActive() { return maxActive; },
|
||||||
get haunt() { return haunt; },
|
get haunt() { return haunt; },
|
||||||
get captured() { return captured.slice(); },
|
get captured() { return captured.slice(); },
|
||||||
get luredColor() { return luredColor; },
|
get luredColor() { return luredColor; },
|
||||||
|
|||||||
@@ -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;
|
||||||
Reference in New Issue
Block a user