feat(framework): 收包路由分界(ActiveGame + Router)

C 规范 §4.1 的红线落地:
- platform/agent/room 三个 route → 平台 handlers(未注册显式抛错)
- 其他 route → activeGame.onReceive(rpc, data)(未激活或不匹配显式抛错)

ActiveGame: 当前子游戏槽(set/clear/get),进房时 setGame、离房 clear。
Router: 纯分发,不持状态。错误一律抛(第二准则:不静默吞包)。

128/128 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 19:55:05 +08:00
co-authored by Claude Opus 5
parent d10c43e6ea
commit 045d9ec8c9
4 changed files with 185 additions and 0 deletions
@@ -0,0 +1,17 @@
import type { IGameModule } from '../sdk/index.ts';
/**
* 当前激活的子游戏槽(= 旧 Game_Modify 的运行时角色)。
*
* 框架在进房时 setGame(),离房时 clear()。Router 据此分发对局 route 的包。
* 单例设计:同一时刻只允许一个子游戏激活(与旧 12_Logic 一致)。
*/
export class ActiveGame {
private game: IGameModule | null = null;
get(): IGameModule | null { return this.game; }
set(game: IGameModule): void { this.game = game; }
clear(): void { this.game = null; }
}
@@ -0,0 +1,53 @@
import type { InboundMessage } from '../core/types/envelope.ts';
import { Route } from '../core/constants.ts';
import type { ActiveGame } from './active-game.ts';
/** 平台层 handler 表:key = rpc 字符串(与协议逐字对齐)。 */
export type PlatformHandlers = Record<string, (data: unknown) => void>;
/**
* Router:收包路由分界(C §4.1 的红线)。
*
* 分发规则:
* - route ∈ {platform, agent, room} → 平台 handlers(RpcList 覆盖的包)
* - route = <game route>(= activeGame.route)→ activeGame.onReceive
* - 其他或未注册:显式抛错(第二准则:不静默吞包)
*
* 不持有状态——只做消息分发。状态由 PlatformSession/子游戏维护。
*/
export class Router {
constructor(
private readonly opts: {
activeGame: ActiveGame;
platformHandlers: PlatformHandlers;
},
) {}
dispatch(msg: InboundMessage): void {
const { route, rpc, data } = msg;
// 平台三层 route:platform/agent/room 全部走平台 handlers
if (route === Route.platform || route === Route.agent || route === Route.room) {
const handler = this.opts.platformHandlers[rpc];
if (!handler) {
throw new Error(
`Router 收到未注册的平台 rpc: route=${route} rpc=${rpc}(platformHandlers 未注册,第二准则禁止兜底)`,
);
}
handler(data);
return;
}
// 对局 route:必须与 activeGame.route 严格匹配
const game = this.opts.activeGame.get();
if (!game) {
throw new Error(
`Router 收到对局包但无激活子游戏: route=${route} rpc=${rpc}(未进房或子游戏未注册)`,
);
}
if (game.route !== route) {
throw new Error(
`Router route 与 activeGame.route 不匹配: msg.route=${route} activeGame.route=${game.route}`,
);
}
game.onReceive(rpc, data);
}
}