fix(tier2): fixture readState 投影成 driver 契约形状——破 board is not iterable
C_frame 后 harness 抛「board is not iterable」:business-sim.driver findMergePair
对 state.board 做 for...of,但 game-core readState 旧导出 board=merge.boardView()
={size,cells[i][j],selected}(对象,非数组,无可点坐标)→ 抛错、其后八门全「门未执行」。
根因=fixture 导出态与 driver 契约(business-sim.driver §读什么 state)不一致。改:
- 抽 layout.js 单一源(棋盘 BOARD_*/CELL + 订单面板 PANEL_* 逻辑坐标),game-core 与
play-scene 同源不漂移。
- game-core readState 投影成 driver 契约形状:board=扁平格数组[{idx,i,j,x,y,item}]、
orders 补 id 别名=instId、新增 submitTargets[{orderId,x,y}]、mergeChains、
lostStreak(=failStreak 别名)、selected(I 门读)。
- play-scene 填平台侧 TODO-4 订单命中:面板行 k → ordersView()[k].instId →
core.fulfillOrder,与 submitTargets 同源,driver 交单 tap 真可达。
内部网格视图(merge.boardView)仍供 scene 渲染,两者各取所需不冲突。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
da184f067c
commit
712547d212
@ -31,6 +31,7 @@ import { createOrderSystem } from './systems/order-system.js';
|
||||
import {
|
||||
computeReachableSet, checkRequiresReachable, checkDagAcyclic,
|
||||
} from './systems/reachability.js';
|
||||
import { cellCenter, orderRowCenter } from './layout.js';
|
||||
|
||||
/**
|
||||
* 创建 mini-肥鹅 游戏核心。
|
||||
@ -91,17 +92,55 @@ export function createGameCore(deps) {
|
||||
|
||||
/**
|
||||
* 可观测状态快照(A6 语义 state;与 _forensicsView().state() 同源)。
|
||||
* PhaserGameInstance.readState / play-scene HUD / latch 门都读它。单独暴露省去每处都 _forensicsView().state()。
|
||||
* @returns {{phase:string, coins:number, ingredients:object, board:object, orders:object[], failStreak:number}}
|
||||
* PhaserGameInstance.readState / play-scene HUD / latch 门 / harness driver 都读它。
|
||||
*
|
||||
* 【形状对齐 harness driver 契约(business-sim.driver.js §读什么 state)】
|
||||
* driver 读 state 自适应出招,需要每个棋盘格 / 订单的【可点逻辑坐标】才能 tap,故本视图把内部
|
||||
* 网格态【投影成 driver 契约形状】:
|
||||
* - board: 扁平格数组 [{idx,i,j,x,y,item}](x/y=格中心逻辑坐标,与 play-scene 渲染/命中同源 layout.js)。
|
||||
* - orders: [{id,instId,requires,reward,patience,status}](id=instId 别名;driver 按 id/status 选可交单)。
|
||||
* - submitTargets: [{orderId,x,y}](每单交单可点坐标;driver 据它点交单,与 hitTest 订单命中区同源)。
|
||||
* - mergeChains: [{from,to,cost}](driver 据它把盲合成 steer 成「合成出订单要的物品」)。
|
||||
* - lostStreak: 连续流失计数别名(driver 破产路判 lose;= failStreak)。
|
||||
* 注:内部网格视图仍经 core.systems.merge.boardView() 给 play-scene 渲染(二维 cells),两者各取所需、不冲突。
|
||||
* @returns {{phase:string, coins:number, ingredients:object, board:object[], orders:object[], submitTargets:object[], mergeChains:object[], lostStreak:number, failStreak:number}}
|
||||
*/
|
||||
readState() {
|
||||
const bv = merge.boardView(); // { size, cells[i][j], selected }
|
||||
const size = bv.size;
|
||||
// 扁平化棋盘 → driver 可点格数组(带格中心逻辑坐标,layout 单一源)。
|
||||
const board = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
for (let j = 0; j < size; j++) {
|
||||
const c = cellCenter(i, j);
|
||||
board.push({ idx: i * size + j, i, j, x: c.x, y: c.y, item: bv.cells[i][j] });
|
||||
}
|
||||
}
|
||||
const ordersRaw = order.ordersView(); // [{instId,tmplId,requires,reward,patience,remainMs,status}]
|
||||
// 订单投影成 driver 契约形状(补 id 别名 = instId;只把 active 当可交单)。
|
||||
const orders = ordersRaw.map((o) => ({
|
||||
id: o.instId, instId: o.instId, requires: { ...o.requires },
|
||||
reward: o.reward, patience: o.patience, remainMs: o.remainMs, status: o.status,
|
||||
}));
|
||||
// 交单坐标:每个在场订单一行(与 play-scene 订单命中区 orderRowCenter 同源),driver 据它点交单。
|
||||
const submitTargets = ordersRaw.map((o, k) => {
|
||||
const r = orderRowCenter(k);
|
||||
return { orderId: o.instId, x: r.x, y: r.y };
|
||||
});
|
||||
// 合成链(driver steer 用;cost 透传,unlockCoins 对 driver 选格不必需故不带)。
|
||||
const mergeChains = MERGE_CHAINS.map((c) => ({ from: c.from, to: c.to, cost: c.cost }));
|
||||
const failStreak = order.failStreak();
|
||||
return {
|
||||
phase: state.phase,
|
||||
coins: resource.state.coins,
|
||||
ingredients: { ...resource.state.ingredients },
|
||||
board: merge.boardView(),
|
||||
orders: order.ordersView(),
|
||||
failStreak: order.failStreak(),
|
||||
board,
|
||||
orders,
|
||||
submitTargets,
|
||||
mergeChains,
|
||||
selected: bv.selected, // 控制体(I 门 controlCheck 读 selected 判点击响应)
|
||||
lostStreak: failStreak, // driver 破产路读 lostStreak(= failStreak 别名)
|
||||
failStreak,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
41
tier2/fixtures/mini-fei-e/src/layout.js
Normal file
41
tier2/fixtures/mini-fei-e/src/layout.js
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* src/layout.js —— mini-肥鹅 布局常量单一源(棋盘 + 订单面板逻辑坐标;平台预建)
|
||||
* owner:tier2 引擎线(M1 · scaffold platform 预建部分)
|
||||
*
|
||||
* ════════════════════════════════════════════════════════════════════════════
|
||||
* 【这份是什么 · 为什么单独抽一份】
|
||||
* driver(business-sim.driver.js)读语义 state 自适应出招——它需要每个棋盘格 / 每个订单的【可点逻辑坐标 x/y】
|
||||
* 才能 tap。这套坐标原先只活在 play-scene 的 BOARD_X/BOARD_Y/CELL,game-core 导出 state 时拿不到 → 导出的
|
||||
* board 是无坐标的 {cells[i][j]} 网格,driver 无法点 + `for...of` 一个对象直接抛「board is not iterable」。
|
||||
*
|
||||
* 抽成单一源:game-core 据它把 board/订单坐标算进 readState(),play-scene 据它渲染 + hitTest——
|
||||
* 两边坐标系同源、不漂移(渲染坐标 = 命中坐标 = driver tap 坐标)。
|
||||
*
|
||||
* 【坐标系】逻辑视口像素(390×844;与 host viewport / CDP 取证视口对齐)。
|
||||
* ════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// 棋盘 3×3 居中偏上,每格 110px,左上角原点 (BOARD_X, BOARD_Y)。
|
||||
export const BOARD_X = 30;
|
||||
export const BOARD_Y = 120;
|
||||
export const CELL = 110;
|
||||
export const BOARD_SIZE = 3;
|
||||
|
||||
// 订单面板:屏下半部竖排,每单一行可点(交单命中区)。driver 据导出的 submitTargets 点这里交单。
|
||||
// 面板从 PANEL_Y 起,每行 PANEL_ROW_H 高,可点区横向 [PANEL_X, PANEL_X+PANEL_W]。
|
||||
export const PANEL_X = 20;
|
||||
export const PANEL_Y = 470;
|
||||
export const PANEL_W = 350;
|
||||
export const PANEL_ROW_H = 64;
|
||||
|
||||
/** 棋盘格 (i 行, j 列) → 格中心逻辑坐标 {x, y}(渲染 / 命中 / driver tap 同源)。 */
|
||||
export function cellCenter(i, j) {
|
||||
return { x: BOARD_X + j * CELL + CELL / 2, y: BOARD_Y + i * CELL + CELL / 2 };
|
||||
}
|
||||
|
||||
/** 订单第 k 行(0 基)→ 交单可点区中心逻辑坐标 {x, y}。 */
|
||||
export function orderRowCenter(k) {
|
||||
return { x: PANEL_X + PANEL_W / 2, y: PANEL_Y + k * PANEL_ROW_H + PANEL_ROW_H / 2 };
|
||||
}
|
||||
@ -23,6 +23,12 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
// 布局常量单一源(棋盘 + 订单面板逻辑坐标;与 game-core readState 导出坐标同源,见 layout.js)。
|
||||
import {
|
||||
BOARD_X, BOARD_Y, CELL,
|
||||
PANEL_X, PANEL_Y, PANEL_W, PANEL_ROW_H,
|
||||
} from '../layout.js';
|
||||
|
||||
/**
|
||||
* 创建主玩法 scene(返回 Phaser.Scene 配置对象;不顶层 import phaser,经 deps.Phaser 注入)。
|
||||
* @param {Object} deps
|
||||
@ -42,11 +48,7 @@ export function createPlayScene(deps) {
|
||||
const VIEW_W = 390;
|
||||
const VIEW_H = 844;
|
||||
|
||||
// 棋盘布局常量(命中映射用;平台预建定坐标系,LLM 渲染须按此映射对齐)。
|
||||
// 3×3 棋盘居中偏上,每格 110px,左上角原点 (BOARD_X, BOARD_Y)。
|
||||
const BOARD_X = 30;
|
||||
const BOARD_Y = 120;
|
||||
const CELL = 110;
|
||||
// 棋盘 / 订单面板布局常量从 layout.js 单一源取(命中映射用;与 game-core readState 导出坐标同源,driver tap 对齐)。
|
||||
|
||||
// scene 内部句柄(create 时填;占位渲染用)。
|
||||
let _sceneRef = null;
|
||||
@ -166,12 +168,18 @@ export function createPlayScene(deps) {
|
||||
return { kind: 'cell', i, j };
|
||||
}
|
||||
}
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// 【LLM 填表现层 · TODO-4 · 订单命中映射】
|
||||
// LLM 定了订单面板布局(TODO-2)后,在此把面板区域内的点击映射到 { kind:'order', instId }。
|
||||
// 约定:返回 core.systems.order.ordersView() 里对应订单的 instId;driver 会点这块区域交单。
|
||||
// spike 第一段:driver 走 host.injectInput 直接给订单区坐标,故此映射须与 TODO-2 面板布局对齐。
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// ── 订单面板命中(平台预建·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;
|
||||
},
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user