feat(hunt): 《围猎》2-4人实时联机纯音频协作守卫——纯音频MMORPG第一阶段切片

- 设计:docs/design/23-hunt.md + voice-ids(84 id,166条)+ 13号断言登记,§15记录10+1条设计裁决
- 服务器:零依赖WebSocket(ws.js)+ 房间管理/服务端权威战斗/bot AI(hunt-room.js),断线30s宽限resume回槽、超时转bot;修复package.json模块类型误判的既有破坏
- 客户端:web/hunt/ 全套(教学T0-T4/听觉视野裁剪R1-R8/结算排行榜/guide接入),playtest 211断言
- 引擎:audio.js 后向兼容 opts.pan(StereoPannerNode),web-engine-api.md 同步
- 资产:166条TTS语音(QC无离群)+ 13件合成sfx(真峰值≤-3dBFS)+ 2首Mureka BGM,终检全绿
- 平衡:bot调参至挂机局71%失守/C、3bot局71% A/B(hunt-balance-scan.mjs 24种子扫描)
- 接线:大厅卡片、profiles.js hunt桶、README/roadmap MMO路线条目

验证:hunt-sim 16/16 + api-smoke 12/12 + hunt playtest 211/211 + 既有10游戏与引擎回归零回退 + 真服务器WS端到端冒烟通过
This commit is contained in:
xsl
2026-09-13 11:08:01 +08:00
parent 31372615b2
commit 005851d01a
392 changed files with 6826 additions and 19 deletions
+29
View File
@@ -41,6 +41,10 @@
* sessions.json {token: username} → tokens.json(补 180 天滑动过期)
* <username>.json {game: data} → profiles/<username>.json(每桶补 updatedAt/rev)
*
* 4. 《围猎》联机 WebSocket(docs/design/23-hunt.md §6):GET /ws/hunt 由
* 'upgrade' 事件接管(ws.js 手写握手/帧编解码,零依赖),房间与战斗权威模拟
* 在 hunt-room.js(全内存,不落盘);与静态托管/REST 同一 HTTP 服务器、同一端口。
*
* 安全水位(家庭局域网):PIN scrypt 哈希(N=16384, r=8)、token 180 天滑动过期 +
* 登出吊销 + 惰性清扫、/api/device per-IP 频控(默认 10 个/天,env SG_DEVICE_LIMIT)、
* /api/pair/* per-IP 频控 + join 5 连错封锁 + 配对码日志脱敏。
@@ -53,6 +57,9 @@ const fsp = require('node:fs/promises');
const path = require('node:path');
const crypto = require('node:crypto');
const huntWs = require('./ws'); // 《围猎》零依赖 WebSocket 传输层(23-hunt.md §6.1)
const hunt = require('./hunt-room'); // 《围猎》房间管理 + 服务端权威战斗模拟(§6.4-§7)
const PORT = Number(process.env.PORT) || 8000;
const ROOT = path.resolve(__dirname, '..'); // web/
const DATA_DIR = process.env.SG_DATA_DIR
@@ -923,11 +930,33 @@ async function main() {
}
});
// ---- 《围猎》WS 端点(23-hunt.md §6.1):/ws/hunt 由 upgrade 接管,其余路径拒升级 ----
const huntManager = hunt.createManager();
server.on('upgrade', (req, socket, head) => {
let pathname;
try {
pathname = new URL(req.url || '/', 'http://localhost').pathname;
} catch (_) {
socket.destroy();
return;
}
if (pathname !== '/ws/hunt') {
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
socket.destroy();
return;
}
const conn = huntWs.accept(req, socket, head);
if (conn) huntManager.attach(conn);
});
const huntTick = setInterval(() => huntManager.tick(hunt.TICK_MS), hunt.TICK_MS);
if (huntTick.unref) huntTick.unref();
server.listen(PORT, () => {
console.log(`[server] SoundGame 服务器已启动: http://localhost:${PORT}/`);
console.log(`[server] 静态根目录: ${ROOT}`);
console.log(`[server] 数据目录: ${DATA_DIR}`);
console.log(`[server] 档案数: ${Object.keys(profileIndex.profiles).length},token 数: ${Object.keys(tokens).length}`);
console.log(`[server] 围猎 WS: ws://localhost:${PORT}/ws/hunt(零依赖,23-hunt.md §6)`);
});
}