fix(tier2): 修真接线三处——pointerdown 闭包/逐帧补桌/patience 缩到有界可达
CDP 探针实证 driver tap 抛「this.handleNormalizedInput is not a function」→ state0/state1 全 null、E_live=1、F_wiring=0、九门连锁红。三处根因+修: ① Phaser 用「配置对象」实例化 scene 时只搬 create/update/preload,不搬对象上其它 方法 → create 内 `this.handleNormalizedInput`/`this.hitTest` undefined,玩家点屏即抛。 修:把 hitTest/handleNormalizedInput 抽成闭包 doHitTest/doHandleInput,pointerdown 与 host.injectInput 都调闭包、不依赖 this(对象方法保留为薄委托)。 ② 合成清空棋盘格、产物只进库存不回落棋盘 → 棋盘很快只剩单件、driver 无对可合成、 赢路卡在中途(offline sim:coins 停 54)。修:core.update 逐帧 merge.refillFromInventory() (幂等,把库存里没上桌的基础料补进空格)→ offline sim 赢路达 coins116/win。 ③ gold patience 440~600s 在有界 driver run(~30s)里订单永不流失 → 经济门破产路 不可达。修:patience 缩到 16~20s(赢路~6s 完成远早于;破产路~20s 内 3 单流失=lose)。 忠实 offline sim 验:patience≤30s 两路都达终态,≥60s 破产路 stuck=play。 验证:headless-smoke + logic-smoke 全绿(赢/输双路可达 + latch 不回弹)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
712547d212
commit
44d1b45583
@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": "mini-fei-e-1.0",
|
||||
"_note": "金标样例(人工预建)——供 driver / 三层校验门 / 富游戏三门做确定性对照测试,不依赖 LLM。本份满足所有 spike 门约束:① mergeChains 是 6 条不相交边(item-base→item-product)、DAG 无环;② 5 个订单的 requires 全是合成产出物(tier2),requiresReachable 通过;③ 开局 20→赢线 100、连续 3 流失=输线;④ unlockCoins 体现金币门控解锁(耦合点5)。主题=mini-肥鹅小卖部,照 wanglanmei-ref 砍小。",
|
||||
"_note": "金标样例(人工预建)——供 driver / 三层校验门 / 富游戏三门做确定性对照测试,不依赖 LLM。本份满足所有 spike 门约束:① mergeChains 是 6 条不相交边(item-base→item-product)、DAG 无环;② 5 个订单的 requires 全是合成产出物(tier2),requiresReachable 通过;③ 开局 20→赢线 100、连续 3 流失=输线;④ unlockCoins 体现金币门控解锁(耦合点5)。主题=mini-肥鹅小卖部,照 wanglanmei-ref 砍小。patience 单位=秒(game-core remainMs=patience*1000),取 16~20s 是为两路都能在有界 driver run 里跑到终态:赢路约 6s 完成(远早于 patience),破产路只合成不交单 → 3 单约 16~20s 内耗尽流失=lose(原 440~600s 在有界 run 里无法流失,经济门破产路不可达,本份修正)。",
|
||||
"currencies": {
|
||||
"coins": { "id": "coins", "initial": 20, "label": "金币" },
|
||||
"ingredients": {
|
||||
@ -32,11 +32,11 @@
|
||||
{ "from": "egg", "to": "cake", "cost": 2, "unlockCoins": 80 }
|
||||
],
|
||||
"orders": [
|
||||
{ "id": "order-1", "requires": { "bread": 1 }, "reward": 18, "patience": 600, "customerAssetId": "ph-customer-kid" },
|
||||
{ "id": "order-2", "requires": { "jam": 1 }, "reward": 22, "patience": 560, "customerAssetId": "ph-customer-lady" },
|
||||
{ "id": "order-3", "requires": { "soda": 1, "bread": 1 }, "reward": 30, "patience": 520, "customerAssetId": "ph-customer-grandpa" },
|
||||
{ "id": "order-4", "requires": { "cream": 1, "chocolate": 1 }, "reward": 40, "patience": 480, "customerAssetId": "ph-customer-auntie" },
|
||||
{ "id": "order-5", "requires": { "cake": 1 }, "reward": 48, "patience": 440, "customerAssetId": "ph-customer-kid" }
|
||||
{ "id": "order-1", "requires": { "bread": 1 }, "reward": 18, "patience": 20, "customerAssetId": "ph-customer-kid" },
|
||||
{ "id": "order-2", "requires": { "jam": 1 }, "reward": 22, "patience": 19, "customerAssetId": "ph-customer-lady" },
|
||||
{ "id": "order-3", "requires": { "soda": 1, "bread": 1 }, "reward": 30, "patience": 18, "customerAssetId": "ph-customer-grandpa" },
|
||||
{ "id": "order-4", "requires": { "cream": 1, "chocolate": 1 }, "reward": 40, "patience": 17, "customerAssetId": "ph-customer-auntie" },
|
||||
{ "id": "order-5", "requires": { "cake": 1 }, "reward": 48, "patience": 16, "customerAssetId": "ph-customer-kid" }
|
||||
],
|
||||
"winCondition": { "coinsTarget": 100 },
|
||||
"loseCondition": { "consecutiveOrderFails": 3 }
|
||||
|
||||
@ -157,6 +157,10 @@ export function createGameCore(deps) {
|
||||
update(dtSeconds) {
|
||||
if (state.phase !== 'play') return; // title / 终态不推进逻辑
|
||||
order.update(dtSeconds);
|
||||
// 逐帧补桌:合成会清空棋盘格(产物只进库存、不回落棋盘),不补桌则棋盘很快只剩单件、driver 无对可合成、
|
||||
// 赢路径被卡(coins 停在中途)。refillFromInventory 幂等——只把库存里还没上桌的基础料补进空格,
|
||||
// 棋盘满 / 库存耗尽即停。这是「棋盘=合成工作台、库存=料仓」设计下保正循环可持续的必需步(logic-smoke 同源)。
|
||||
merge.refillFromInventory();
|
||||
state.coins = resource.state.coins; // 同步渲染缓存
|
||||
_settle(); // 赢 / 输判定 + latch
|
||||
},
|
||||
|
||||
@ -54,6 +54,55 @@ export function createPlayScene(deps) {
|
||||
let _sceneRef = null;
|
||||
let _hudText = null;
|
||||
|
||||
/**
|
||||
* 命中测试:屏幕坐标 → 命中目标(棋盘格 / 订单面板;平台预建)。坐标系 = 逻辑视口像素(与 layout 同源)。
|
||||
* 定义为【闭包函数】而非对象方法——Phaser 用「配置对象」实例化 scene 时,只把 create/update/preload
|
||||
* 拷成生命周期回调,不会把对象上的其它方法搬到 scene 实例上,故 create 内 `this.hitTest` 会是 undefined。
|
||||
* 抽成闭包函数后,对象方法(host.injectInput 路径)与 Phaser pointerdown 路径都调它、不依赖 `this`。
|
||||
* @param {number} x @param {number} y
|
||||
* @returns {{kind:'cell', i:number, j:number} | {kind:'order', instId:string} | null}
|
||||
*/
|
||||
function doHitTest(x, y) {
|
||||
// 棋盘格命中(平台预建:与渲染坐标同源,确定性,driver 友好)。
|
||||
const lx = x - BOARD_X;
|
||||
const ly = y - BOARD_Y;
|
||||
if (lx >= 0 && ly >= 0) {
|
||||
const j = Math.floor(lx / CELL);
|
||||
const i = Math.floor(ly / CELL);
|
||||
if (i >= 0 && i < 3 && j >= 0 && j < 3) {
|
||||
return { kind: 'cell', i, j };
|
||||
}
|
||||
}
|
||||
// 订单面板命中(平台预建·driver 交单可达):第 k 行可点区映射到 ordersView()[k] 的 instId
|
||||
// (与 game-core 导出的 submitTargets 第 k 行同源),driver 据 submitTargets 点这里 → core.fulfillOrder 真交单。
|
||||
// 注:LLM 的 TODO-2/TODO-5 渲染订单面板时须用同一 layout(PANEL_*),才与本命中区 + driver tap 对齐。
|
||||
if (x >= PANEL_X && x <= PANEL_X + PANEL_W && y >= PANEL_Y) {
|
||||
const k = Math.floor((y - PANEL_Y) / PANEL_ROW_H);
|
||||
const orders = core.systems.order.ordersView();
|
||||
if (k >= 0 && k < orders.length) {
|
||||
return { kind: 'order', instId: orders[k].instId };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 归一化输入处理(闭包函数;命中后调 core 逻辑命令)。host.injectInput 与 Phaser pointerdown 共用。
|
||||
* @param {{type:'tap'|'key'|'drag', x?:number, y?:number, key?:string}} ev
|
||||
*/
|
||||
function doHandleInput(ev) {
|
||||
if (!core || !ev || ev.type !== 'tap') return;
|
||||
const hit = doHitTest(ev.x, ev.y);
|
||||
if (!hit) return;
|
||||
if (hit.kind === 'cell') {
|
||||
// 命中棋盘格 → 合成逻辑命令(两次点同物品合成,逻辑在 core/merge-system)。
|
||||
core.tapCell(hit.i, hit.j);
|
||||
} else if (hit.kind === 'order') {
|
||||
// 命中订单 → 交单逻辑命令。
|
||||
core.fulfillOrder(hit.instId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// Phaser scene 配置:key 由 main.js 注册时给('PlayScene');本对象作为 sceneConfig 传 scene.add。
|
||||
// 注:用对象式 scene(Phaser 支持 {create, update} 配置对象),避免顶层 class extends Phaser.Scene 依赖。
|
||||
@ -82,10 +131,11 @@ export function createPlayScene(deps) {
|
||||
if (typeof console !== 'undefined') console.warn('[play-scene] create 占位渲染异常:' + (e && e.message));
|
||||
}
|
||||
|
||||
// Phaser 指针输入订阅(真玩时玩家点屏;坐标交 hitTest 命中 → core 逻辑命令)。
|
||||
// Phaser 指针输入订阅(真玩时玩家点屏;坐标交命中 → core 逻辑命令)。
|
||||
// 调闭包 doHandleInput 而非 this.handleNormalizedInput——配置对象式 scene 的方法不在实例上(见 doHitTest 注释)。
|
||||
try {
|
||||
this.input.on('pointerdown', (p) => {
|
||||
this.handleNormalizedInput({ type: 'tap', x: p.x, y: p.y });
|
||||
doHandleInput({ type: 'tap', x: p.x, y: p.y });
|
||||
});
|
||||
} catch (e) { /* headless 无 input 时吞错 */ }
|
||||
|
||||
@ -135,52 +185,20 @@ export function createPlayScene(deps) {
|
||||
|
||||
/**
|
||||
* 归一化输入入口(main.js 的 PhaserGameInstance.injectInput 路由到这里;harness 驱动 + 真玩共用)。
|
||||
* 做命中测试:屏幕坐标 → 棋盘格 (i,j) / 订单 → 调 core 逻辑命令。
|
||||
* 委托给闭包 doHandleInput(不依赖 this,与 Phaser pointerdown 路径同一实现)。
|
||||
* @param {{type:'tap'|'key'|'drag', x?:number, y?:number, key?:string}} ev
|
||||
*/
|
||||
handleNormalizedInput(ev) {
|
||||
if (!core || !ev || ev.type !== 'tap') return;
|
||||
const hit = this.hitTest(ev.x, ev.y);
|
||||
if (!hit) return;
|
||||
if (hit.kind === 'cell') {
|
||||
// 命中棋盘格 → 合成逻辑命令(两次点同物品合成,逻辑在 core/merge-system)。
|
||||
core.tapCell(hit.i, hit.j);
|
||||
} else if (hit.kind === 'order') {
|
||||
// 命中订单 → 交单逻辑命令。
|
||||
core.fulfillOrder(hit.instId);
|
||||
}
|
||||
doHandleInput(ev);
|
||||
},
|
||||
|
||||
/**
|
||||
* 命中测试:屏幕坐标 → 命中目标(平台预建棋盘格映射;订单映射留 TODO 给 LLM 定面板布局后补)。
|
||||
* 坐标系 = 逻辑视口像素(与 BOARD_X/BOARD_Y/CELL 同源)。
|
||||
* 命中测试(外部可调;委托闭包 doHitTest)。坐标系 = 逻辑视口像素(与 layout 同源)。
|
||||
* @param {number} x @param {number} y
|
||||
* @returns {{kind:'cell', i:number, j:number} | {kind:'order', instId:string} | null}
|
||||
*/
|
||||
hitTest(x, y) {
|
||||
// 棋盘格命中(平台预建:与渲染坐标同源,确定性,driver 友好)。
|
||||
const lx = x - BOARD_X;
|
||||
const ly = y - BOARD_Y;
|
||||
if (lx >= 0 && ly >= 0) {
|
||||
const j = Math.floor(lx / CELL);
|
||||
const i = Math.floor(ly / CELL);
|
||||
if (i >= 0 && i < 3 && j >= 0 && j < 3) {
|
||||
return { kind: 'cell', i, j };
|
||||
}
|
||||
}
|
||||
// ── 订单面板命中(平台预建·driver 交单可达)──
|
||||
// 面板竖排:第 k 行的可点区 = [PANEL_X, PANEL_X+PANEL_W] × [PANEL_Y+k*ROW_H, +ROW_H]。
|
||||
// 把命中行 k 映射到 ordersView()[k] 的 instId(与 game-core 导出的 submitTargets 第 k 行同源),
|
||||
// driver 据 submitTargets 点这里 → core.fulfillOrder(instId) 真交单。
|
||||
// 注:LLM 的 TODO-2/TODO-5 渲染订单面板时须用同一 layout(PANEL_*),才与本命中区 + driver tap 对齐。
|
||||
if (x >= PANEL_X && x <= PANEL_X + PANEL_W && y >= PANEL_Y) {
|
||||
const k = Math.floor((y - PANEL_Y) / PANEL_ROW_H);
|
||||
const orders = core.systems.order.ordersView();
|
||||
if (k >= 0 && k < orders.length) {
|
||||
return { kind: 'order', instId: orders[k].instId };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return doHitTest(x, y);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user