二七王:平台接线与加载段

SubGameHooks 只解包转交、不写业务:_ReceiveData 收到的是完整包,
按 rpc 分发;Free 的参数是平台已取出的 deskfree、可能为 null;
appStart 只记座位与房间选项,UI 初始化留给 C 阶段。

index.html 里 handler 排在 Dispatcher 之前——后者注册时要引用全部 handler。

另加 test_index_load_order.js:按 index.html 实际 script 顺序自动加载
codes/ 全部文件 + 3 个框架依赖,验证顺序无遗漏、无环,并断言
EQW_Dispatcher 路由表确已注册,作为长期守卫。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 20:16:45 +08:00
co-authored by Claude Opus 5
parent dac4a94b38
commit 22e43cf2e6
4 changed files with 225 additions and 1 deletions
+109
View File
@@ -0,0 +1,109 @@
// index.html 加载顺序自检(task-13 步骤6守卫):
// 按 index.html 里 <script src> 的实际先后顺序,抽取「codes/ 全部文件 + 它们依赖的
// 3 个框架文件(EventBus/SpriteEventController/AlignmentUtils)」,依次用 vm 加载,
// 确认:1) 无异常抛出(顺序错了会因引用未定义全局而报错)
// 2) 关键全局全部就绪
// 3) EQW_Dispatcher 的路由表确已注册(hasHandler('chupai1') === true)
// 这份顺序即是 index.html 的真实加载顺序,不是我们另行假设的一份。
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const t = require('./_assert')();
const ROOT = path.resolve(__dirname, '..', '..');
const indexHtml = fs.readFileSync(path.join(ROOT, 'client', 'index.html'), 'utf8');
// 抽取全部本地 <script src="..."> 路径,按文档出现顺序
const scriptRe = /<script[^>]*\ssrc="([^"]+)"[^>]*><\/script>/g;
const allSrcs = [];
let m;
while ((m = scriptRe.exec(indexHtml)) !== null) {
allSrcs.push(m[1]);
}
t.eq('index.html 至少解析出脚本标签', allSrcs.length > 0, true);
// 只保留:codes/ 下全部文件 + 3 个框架依赖文件,其余(vendor/引擎/大厅平台代码等)
// 无法在 Node 里单独跑,本自检不覆盖,留给「浏览器人工验证」。
const NEEDED_FRAMEWORK = [
'js/gameabc-framework/system/EventBus.js',
'js/gameabc-framework/system/SpriteEventController.js',
'js/gameabc-framework/ui/AlignmentUtils.js'
];
const relevant = allSrcs.filter(function (src) {
return NEEDED_FRAMEWORK.indexOf(src) !== -1 || src.indexOf('js/01_SubGame/codes/') === 0;
});
// 期望齐全的清单:本任务新增/相关的 state/ net/ 文件,一个都不能漏
const EXPECT_PRESENT = [
'js/01_SubGame/codes/state/Events.js',
'js/01_SubGame/codes/state/GameState.js',
'js/01_SubGame/codes/state/RoomOptions.js',
'js/01_SubGame/codes/net/handlers/FailHandler.js',
'js/01_SubGame/codes/net/handlers/DealHandler.js',
'js/01_SubGame/codes/net/handlers/CallHandler.js',
'js/01_SubGame/codes/net/handlers/MainHandler.js',
'js/01_SubGame/codes/net/handlers/BuryHandler.js',
'js/01_SubGame/codes/net/handlers/PlayHandler.js',
'js/01_SubGame/codes/net/handlers/QueryHandler.js',
'js/01_SubGame/codes/net/handlers/ReadyHandler.js',
'js/01_SubGame/codes/net/handlers/ResultHandler.js',
'js/01_SubGame/codes/net/handlers/ResyncHandler.js',
'js/01_SubGame/codes/net/Dispatcher.js',
'js/01_SubGame/codes/net/Rpc.js',
'js/01_SubGame/codes/SubGameHooks.js'
];
EXPECT_PRESENT.forEach(function (src) {
t.eq('index.html 含脚本 ' + src, allSrcs.indexOf(src) !== -1, true);
});
// 相对顺序断言:handler 全部排在 Dispatcher 之前;state/ 排在 net/handlers/ 之前
const idx = {};
relevant.forEach(function (src, i) { idx[src] = i; });
var dispatcherIdx = idx['js/01_SubGame/codes/net/Dispatcher.js'];
[
'js/01_SubGame/codes/net/handlers/FailHandler.js',
'js/01_SubGame/codes/net/handlers/DealHandler.js',
'js/01_SubGame/codes/net/handlers/CallHandler.js',
'js/01_SubGame/codes/net/handlers/MainHandler.js',
'js/01_SubGame/codes/net/handlers/BuryHandler.js',
'js/01_SubGame/codes/net/handlers/PlayHandler.js',
'js/01_SubGame/codes/net/handlers/QueryHandler.js',
'js/01_SubGame/codes/net/handlers/ReadyHandler.js',
'js/01_SubGame/codes/net/handlers/ResultHandler.js',
'js/01_SubGame/codes/net/handlers/ResyncHandler.js'
].forEach(function (h) {
t.eq('handler 排在 Dispatcher 之前: ' + h, idx[h] < dispatcherIdx, true);
});
t.eq('state/ 排在 net/handlers/ 之前', idx['js/01_SubGame/codes/state/GameState.js'] < idx['js/01_SubGame/codes/net/handlers/FailHandler.js'], true);
t.eq('SubGameHooks 排在 Dispatcher 之后', idx['js/01_SubGame/codes/SubGameHooks.js'] > dispatcherIdx, true);
// 依次实际加载,确认不抛异常,且顺序自洽
global.window = global;
let loadErr = null;
try {
relevant.forEach(function (src) {
const abs = path.join(ROOT, 'client', src);
const code = fs.readFileSync(abs, 'utf8');
vm.runInThisContext(code, { filename: src });
});
} catch (e) {
loadErr = e;
}
t.eq('按 index.html 顺序加载无异常', loadErr ? (loadErr.stack || String(loadErr)) : null, null);
// 关键全局就绪
[
'EQW_Events', 'EQW_GameState', 'EQW_RoomOptions_Parse', 'EQW_Dispatcher', 'EQW_Rpc',
'EQW_DealHandler', 'EQW_CallHandler', 'EQW_MainHandler', 'EQW_BuryHandler', 'EQW_PlayHandler',
'EQW_QueryHandler', 'EQW_ReadyHandler', 'EQW_ResultHandler', 'EQW_ResyncHandler', 'EQW_FailHandler',
'SubGameHooks'
].forEach(function (name) {
t.eq('全局就绪: ' + name, typeof global[name] !== 'undefined', true);
});
// 路由表确已注册
if (typeof global.EQW_Dispatcher !== 'undefined') {
t.eq("EQW_Dispatcher.hasHandler('chupai1')", global.EQW_Dispatcher.hasHandler('chupai1'), true);
}
process.exit(t.done('index_load_order') ? 0 : 1);