fix(tier2): recHook 透传到资源系统——F 门/三联动门记 resource.* call-ID

F 门/三联动门读 window.__engineCalls 找 resource.addCoins/consumeIngredient 前缀,
但旧版只有 engine-caps(粒子/音频)经 recHook 记 __engineCalls,资源系统只记内部
_callCounts、不进 __engineCalls → calls=0、F 门 + 三联动门真接线判据永假。

修(additive,不破契约必填项):host 经 boot 上下文透传 recHook → main.js 喂
createGameCore → resource-system 完单真调 addCoins/合成真调 consumeIngredient 时
recHook('resource.addCoins'/'resource.consumeIngredient') 记进 __engineCalls。
headless/静态校验下 recHook 缺省 no-op、不抛错。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zizi 2026-06-23 20:48:40 +00:00
parent 44d1b45583
commit 8ea6133e63
4 changed files with 18 additions and 3 deletions

View File

@ -203,6 +203,9 @@ export async function bootPhaserHost(opts) {
// 受控能力面经 boot 上下文额外透传(游戏经 ctx.getEngine() 拿headless 下 no-op 能力)。
// 注:契约 PhaserHostBootContext 不含 caps 字段caps 经 game 内部约定取;此处 additive 透传不破契约必填项。
caps,
// recHook additive 透传:游戏逻辑(如资源系统完单/合成)经它把 call-ID 记进 window.__engineCalls,
// 供 F 门 / 富游戏三联动门证「真调资源(addCoins/consumeIngredient)、非自绘伪装」(同 caps 不破契约必填项)。
recHook,
}));
// 就绪钩子harness 可挂取证全局headless 下用于逻辑校验)。
onReady(host, { game, phaserGame: null, canvas: null, caps, uncaught, latchInfo, pollLatch });
@ -248,6 +251,7 @@ export async function bootPhaserHost(opts) {
seed,
assets,
caps, // additive 透传受控面(游戏经 ctx.getEngine() 拿;不破契约必填项)
recHook, // additive 透传:游戏逻辑经它记 call-ID 进 __engineCalls(F 门 / 三联动门真接线证据)
}));
// 挂逐帧 latch 轮询Phaser scene events 'postupdate' 每帧后调A4 铁律核心)。
self.events.on('postupdate', pollLatch);

View File

@ -44,9 +44,11 @@ export function createGameCore(deps) {
if (!rng || typeof rng.next !== 'function') {
throw new Error('[game-core] 必须注入受控随机源 randomnext/range'); // 错误路径留痕
}
// call-ID 取证钩子host 经 main.js 透传;缺省 no-op。资源系统据它把 'resource.*' 记进 __engineCalls(F 门/三联动门)。
const recHook = deps.recHook || (() => {});
// 三系统装配(资源是中心,合成 / 订单都注入它 → 耦合点 3/4/5 真接线)。
const resource = createResourceSystem({ random: rng, startIngredients: START_INGREDIENTS, startCoins: START_COINS });
const resource = createResourceSystem({ random: rng, startIngredients: START_INGREDIENTS, startCoins: START_COINS, recHook });
const merge = createMergeSystem({ resource, chains: MERGE_CHAINS, random: rng });
const order = createOrderSystem({
resource, templates: ORDER_TEMPLATES, activeCount: ACTIVE_ORDER_COUNT, loseStreak: LOSE_STREAK, random: rng,

View File

@ -54,8 +54,10 @@ export default function createGame(opts) {
async boot(ctx) {
// 据 boot.seed 派生受控随机源(确定性;禁裸 Math.random
const random = createSeededRandom(ctx.seed >>> 0);
// 建游戏核心(三系统逻辑,平台预建写死)。
core = createGameCore({ random });
// 建游戏核心三系统逻辑平台预建写死。recHook 由 host 经 boot 上下文 additive 透传:
// 资源系统完单/合成时经它把 'resource.addCoins'/'resource.consumeIngredient' 记进 window.__engineCalls,
// 供 F 门 / 富游戏三联动门证真接线(headless 下 ctx.recHook 缺省 → no-op)。
core = createGameCore({ random, recHook: ctx.recHook });
core.start(); // title → playspike 直接进局;产线可加标题页)
// 受控能力面host 经 boot 上下文 additive 透传 capsheadless 下为 no-op 能力面)。

View File

@ -28,6 +28,8 @@
* @param {{ next:()=>number, range:(a:number,b:number)=>number }} [deps.random] 受控随机源资源系统本身少用随机预留
* @param {Record<string, number>} deps.startIngredients 开局库存
* @param {number} deps.startCoins 开局金币
* @param {(id:string)=>void} [deps.recHook] call-ID 取证钩子host boot 透传 window.__engineCalls
* 每次真调命令记 'resource.<cmd>' call-ID, F / 富游戏三联动门(callsAddCoins/callsConsumeIngredient)证真接线
* @returns {ResourceSystem}
*/
export function createResourceSystem(deps) {
@ -38,6 +40,9 @@ export function createResourceSystem(deps) {
ingredients: { ...(deps.startIngredients || {}) },
};
// call-ID 取证钩子(缺省 no-opheadless / 静态校验下无 host → no-op不抛错
const recHook = (deps && deps.recHook) || (() => {});
// F 门真接线取证四个命令的真实调用计数机器证据agent 自述不作数)。
const _callCounts = { addCoins: 0, spendCoins: 0, consumeIngredient: 0, addIngredient: 0 };
@ -52,6 +57,7 @@ export function createResourceSystem(deps) {
addCoins(n) {
// 受控日志锚:金币变动是经济门核心,留可追溯计数。
_callCounts.addCoins++;
recHook('resource.addCoins'); // 真接线 call-ID(F 门 / 三联动门 callsAddCoins 读 __engineCalls)
state.coins += Math.max(0, n | 0);
},
@ -80,6 +86,7 @@ export function createResourceSystem(deps) {
const have = state.ingredients[itemId] || 0;
if (have < need) return { ok: false, reason: 'no_stock' }; // 库存不足 → 失败
state.ingredients[itemId] = have - need;
recHook('resource.consumeIngredient'); // 真接线 call-ID(三联动门 callsConsumeIngredient 读 __engineCalls;仅真扣成功记)
return { ok: true };
},