host修根因(mini-desktop CDP验出双Phaser实例致可见区纯黑+真指针死):main.js重写为宿主契约形态——
工厂零Phaser import、永不自建引擎实例,场景经ctx.game.scene.add挂bootPhaserHost唯一实例(与夜市款同构);
真人pointerdown与injectInput('tap')同走route(x,y)→readState().tapTargets命中序(ui/layout.js唯一坐标事实)=同因果,
M0手牌盖板吃点击缺陷随重构消失;run-smoke S0六条静态自查防回潮;battle-scene.js退役git留档。
M1单局闭环:S5链轴完整化(链轨延链指引/graceUsed/周天金闪)+S6 map-core三幕DAG(战/精英/憩/宝匣/Boss,
静态可达自检)+S7敌8种(多段/甲攻复合/烛阴双相位/饕餮增势,意图回合开始钉住「展示=执行」逐字段成立)+
S8 card driver v1+play-spec;30卡五行各6/3遗物全规则级/奖励三选一可跳过/HP携带/run级三latch。
数值首轮回填全标〔草案〕(贪心v1胜率5%=下界非标定对象,A0带45-60%校准归M2 S12三档driver)。
完整run单局墙钟首实测(§6.4 M1出口):40 seed中位1.6ms/p95 3ms级(headless开发机口径)→外推M2全矩阵
96k局≈分钟级单线程,数值CI可行性坐实。harness共享面双席合笔:card-battle.driver+business-serve.driver新增、
play-phaser.cdp.cjs runDriver type分发additive(卡牌/夜市两席同文件双分发共存复核OK,未声明时行为逐字不变)。
证据:battle-core 40断言(T1a/T1b随定容更新)+run-core 12组+run-smoke全绿(地图150张/首局门5 seed全win/
通关2/40全自然终局零invalid/幕缩放1070采样全中);主会话真跑复验。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 lines
4.1 KiB
JavaScript
77 lines
4.1 KiB
JavaScript
/**
|
||
* business-serve.driver.js —— 经营·服务循环形状(business-serve)确定性 harness driver(tier2 富游戏线)
|
||
* owner:北极星顶级线①(W-POLARIS)《夜市一条街》M1 九门接线 | 被 play-phaser.cdp.cjs runDriver 分发调用
|
||
* (与 business-sim/card-battle 并列,按 runDriver 注释官方配方接入)
|
||
*
|
||
* ════════════════════════════════════════════════════════════════════════════
|
||
* 【这份是什么】「点顾客即成单」服务循环的确定性驱动:读语义 state 自适应出招(不盲打坐标),
|
||
* 把经营日循环真输入驱动到 day_end 终态,供九门判定(E_live/G_input/H_progress 的 driven 供给端)。
|
||
* 与 business-sim.driver(mini-肥鹅 merge/orders 形状)是同一经营品类的两个 state 形状;
|
||
* 富三门的经营-serve 语义归 M2 定义,本 driver 只管九门 driven——spec 用独立 driver.type
|
||
* 'business-serve',富三门按 harness 品类占位「门待建」走,不套 merge 语义防假绿。
|
||
*
|
||
* 【读什么 state】(game-core readState 契约)
|
||
* { phase:'title'|'play'|'day_end', coins, served,
|
||
* tapTargets:[{customerId,x,y,patienceLeft,servable}], // 等待中顾客可点逻辑坐标
|
||
* stoves:[{idx,busy,...}] }
|
||
*
|
||
* 【出招策略】(与 scripts/logic-smoke.mjs 勤快席同族,浏览器侧无白盒进货,只按 servable 优先)
|
||
* 有空灶时:优先「可做(servable)且耐心最紧」的顾客,真触摸点其逻辑坐标;
|
||
* 无可点(灶全忙/无客)则小步等待;phase 进入 terminalPhases 即停,交 latch 校验。
|
||
* ════════════════════════════════════════════════════════════════════════════
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
/** 逻辑坐标钳制(避免点出取证视口外;竖屏 390×844)。 */
|
||
function clamp(v, max) {
|
||
const hi = max != null ? max : 386;
|
||
if (typeof v !== 'number' || Number.isNaN(v)) return Math.floor(hi / 2);
|
||
return Math.max(4, Math.min(hi - 4, v));
|
||
}
|
||
|
||
/** 从 state 选出招目标:可做优先、耐心紧迫优先;无灶/无客返 null。(纯函数,单测直测) */
|
||
function pickTarget(state) {
|
||
if (!state) return null;
|
||
const targets = Array.isArray(state.tapTargets) ? state.tapTargets : [];
|
||
const stoves = Array.isArray(state.stoves) ? state.stoves : [];
|
||
if (!targets.length) return null;
|
||
if (!stoves.some((s) => s && !s.busy)) return null; // 无空灶不空点(点了也 stoves_busy)
|
||
const byUrgency = [...targets].sort((a, b) => a.patienceLeft - b.patienceLeft);
|
||
return byUrgency.find((t) => t.servable !== false) || byUrgency[0];
|
||
}
|
||
|
||
/**
|
||
* 主路径:驱动服务循环到 day_end(或步数上限)。
|
||
* @param cdp CDP 会话
|
||
* @param driver play-spec.driver({type:'business-serve', steps?, stepMs?, terminalPhases?})
|
||
* @param hashes 帧哈希数组(供 E 门活性采样)
|
||
* @param deps {tap,key,drag,readGameState,sampleHash,getPath,delay}
|
||
* @returns {drove, steps}
|
||
*/
|
||
async function run(cdp, driver, hashes, deps) {
|
||
const { tap, readGameState, sampleHash, delay } = deps;
|
||
const steps = driver.steps || 110;
|
||
const stepMs = driver.stepMs != null ? driver.stepMs : 400;
|
||
const terminals = Array.isArray(driver.terminalPhases) && driver.terminalPhases.length
|
||
? driver.terminalPhases : ['day_end'];
|
||
let drove = 0;
|
||
|
||
for (let i = 0; i < steps; i++) {
|
||
const s = await readGameState(cdp);
|
||
if (s && terminals.indexOf(s.phase) >= 0) break; // 已收钟 → 停,交 latch 校验
|
||
const target = pickTarget(s);
|
||
if (target) {
|
||
await tap(cdp, clamp(target.x), clamp(target.y, 840)); // 真触摸(经浏览器→Phaser input→core.tapAt)
|
||
drove++;
|
||
} else {
|
||
await delay(Math.min(stepMs, 200)); // 无可点(灶忙/无客/缺料无可做单):小步等出餐腾灶
|
||
}
|
||
try { hashes.push(await sampleHash(cdp)); } catch (_) {}
|
||
await delay(stepMs);
|
||
}
|
||
return { drove, steps };
|
||
}
|
||
|
||
module.exports = { run, pickTarget, clamp };
|