fix(escape): 暗室状态引导方案落地(25号策划文档 B1-B8 → F1-F9 实施)
策划轮:25-escape-map-flow.md as-built地图/玩家流程/信息账本+盲区清单(S1-S7)+方案B1-B8 实施(玩法零改动,只加状态引导层): - F1 撞墙方位句「左边是墙」(wall_dir_*×4,同向10s限频) - F2 目标句并入方位「铁柜,在右面的房间」(gd_*_dir×3) - F3 无进展150s活跃计时器+gd_prog_1..3进度重述(只重述不泄题,专治活跃迷路玩家——修卡关计时从「无进展」退化为「无操作」的既定偏差) - F4 双击兜底直通L2降级L1(不再白送答案) - F5 空房首访描述(4空房可区分) - F7 busy窗口手势缓存补判(原静默吞手势) - F8 结算链单击守卫(time_band/again完整到达) - F9 跳过引导补播目标句 - F6 空房方位特征句按25号裁定留档后续轮 新增语音10句×2性别(flock串行QC无离群,ASR逐字正确) 验证:escape playtest 235/235(+85新断言)+ guide 132/132 + 终检全绿 + 14游戏回归零回退 live复跑10.4分钟通关:零截断,S1-S7盲区逐条核销 档案 research/escape-state/round1-2;01号文档追加修订记录R1
This commit is contained in:
+54
-17
@@ -756,9 +756,9 @@ realLog('== ⑩b H5 rhythm:教学应答窗口无任何尝试 → gd_miss_none
|
||||
resetTimers();
|
||||
}
|
||||
|
||||
realLog('== ⑩c H1 escape:目标句后按房间图播 gd_room_*(同房间不播,移动坐标系)==');
|
||||
realLog('== ⑩c H1/B2 escape:目标句后按房间图播方位句(round2 B2:三处固定接线点升级为"目标名+方位"一体句,异常路径回落 gd_room_*)==');
|
||||
{
|
||||
// 方向映射按图校验(直连单元断言)
|
||||
// 方向映射按图校验(直连单元断言,gd_room_* 映射表保留不动)
|
||||
const rig0 = makeRig(EscapeGame);
|
||||
const { game: g0 } = rig0;
|
||||
await rig0.audio.unlock();
|
||||
@@ -768,10 +768,15 @@ realLog('== ⑩c H1 escape:目标句后按房间图播 gd_room_*(同房间
|
||||
assert(g0._roomDirVoice([1, 0]) === 'voice/gd_room_u', 'H1:目标在北(上面的房间)→ gd_room_u');
|
||||
assert(g0._roomDirVoice([1, 2]) === 'voice/gd_room_d', 'H1:目标在南(下面的房间)→ gd_room_d');
|
||||
assert(g0._roomDirVoice([1, 1]) === null, 'H1:目标在同一房间不播');
|
||||
// B2 异常路径回落:计算方位与枚举不符 → 原 gd_room_*(原句保留接线,归因真话)
|
||||
g0._playGoalDir([0, 1], 'voice/gd_cabinet_dir', 'voice/gd_room_r'); // 目标在西 ≠ 枚举右
|
||||
await flush();
|
||||
assert(rig0.playLog.some((e) => e.id === 'voice/gd_room_l'), 'B2:方位与枚举不符时回落 gd_room_*');
|
||||
g0.stop();
|
||||
resetTimers();
|
||||
|
||||
// 三处目标句接线点:found_key → cabinet(2,1);gd_next_fuse → box(0,0);fix_box → gate(2,2)
|
||||
// (B2 换线:gd_room_r/l → gd_cabinet_dir/gd_box_dir/gd_gate_dir,断言同步登记变更)
|
||||
const rig = makeRig(EscapeGame);
|
||||
const { audio, playLog, game } = rig;
|
||||
storageData.set('soundgame.escape.guideDone', '1'); // 二周目直达 REOPEN→PLAY
|
||||
@@ -779,42 +784,74 @@ realLog('== ⑩c H1 escape:目标句后按房间图播 gd_room_*(同房间
|
||||
game.start();
|
||||
await advance(() => game.phase === 'REOPEN');
|
||||
await advance(() => !audio.isVoicePlaying()); // reopen_prompt 播完
|
||||
await gesture(game, 'tap'); // 直接开始 → PLAY (1,1)
|
||||
await gesture(game, 'tap'); // 直接开始 → PLAY (1,1)(B8:start_room → g7_goal 链)
|
||||
await advance(() => game.phase === 'PLAY' && !audio.isVoicePlaying());
|
||||
assert(game.x === 1 && game.y === 1 && !game.hasKey, '前提:二周目 PLAY 起点、身上无钥匙');
|
||||
|
||||
// ① found_key:储物间拾钥匙 → 铁柜房在东(右面的房间)
|
||||
await gesture(game, 'left'); // (0,1)
|
||||
// ① found_key:储物间拾钥匙 → 铁柜房在东 → B2 一体句 gd_cabinet_dir
|
||||
await gesture(game, 'left'); // (0,1)(B5a:首访播 empty_2)
|
||||
await gesture(game, 'down'); // (0,2) 储物间(首到播 room_storage)
|
||||
await advance(() => !audio.isVoicePlaying()); // 房间描述播完再单击(否则单击=跳句)
|
||||
await gesture(game, 'tap'); // 检查 → found_key(800ms 后)→ 方位句
|
||||
await advance(() => playLog.some((e) => e.id === 'voice/gd_room_r'));
|
||||
await gesture(game, 'tap'); // 检查 → found_key(800ms 后)→ 一体句
|
||||
await advance(() => playLog.some((e) => e.id === 'voice/gd_cabinet_dir'));
|
||||
const ifk = playLog.findIndex((e) => e.id === 'voice/found_key');
|
||||
assert(ifk > -1 && playLog.findIndex((e) => e.id === 'voice/gd_room_r') > ifk,
|
||||
'H1:found_key 后接播 gd_room_r(目标铁柜房按图在东)');
|
||||
assert(ifk > -1 && playLog.findIndex((e) => e.id === 'voice/gd_cabinet_dir') > ifk,
|
||||
'B2:found_key 后接播 gd_cabinet_dir(铁柜,在右面的房间)');
|
||||
assert(!playLog.some((e) => e.id === 'voice/gd_room_r'), 'B2:正常路径不再播 gd_room_r');
|
||||
|
||||
// ② gd_next_fuse:开柜得保险丝 → 电箱房在西(左面的房间)
|
||||
// ② gd_next_fuse:开柜得保险丝 → 电箱房在西 → B2 一体句 gd_box_dir
|
||||
await gesture(game, 'right'); await gesture(game, 'right'); // (1,2) (2,2)
|
||||
await gesture(game, 'up'); // (2,1) 铁柜房
|
||||
await gesture(game, 'doubletap'); // 开柜
|
||||
await advance(() => playLog.some((e) => e.id === 'voice/gd_room_l'));
|
||||
await advance(() => playLog.some((e) => e.id === 'voice/gd_box_dir'));
|
||||
const inf = playLog.findIndex((e) => e.id === 'voice/gd_next_fuse');
|
||||
assert(inf > -1 && playLog.findIndex((e) => e.id === 'voice/gd_room_l') > inf,
|
||||
'H1:gd_next_fuse 后接播 gd_room_l(目标电箱房按图在西)');
|
||||
assert(inf > -1 && playLog.findIndex((e) => e.id === 'voice/gd_box_dir') > inf,
|
||||
'B2:gd_next_fuse 后接播 gd_box_dir(电箱,在左面的房间)');
|
||||
assert(!playLog.some((e) => e.id === 'voice/gd_room_l'), 'B2:正常路径不再播 gd_room_l');
|
||||
|
||||
// ③ fix_box:修电箱通电 → 大门在东(右面的房间)
|
||||
// ③ fix_box:修电箱通电 → 大门在东 → B2 一体句 gd_gate_dir
|
||||
await advance(() => !audio.isVoicePlaying()); // gd_box_dir 播完再走(首访空房句不顶链)
|
||||
await gesture(game, 'left'); await gesture(game, 'left'); // (1,1) (0,1)
|
||||
await gesture(game, 'up'); // (0,0) 电箱房
|
||||
await gesture(game, 'doubletap'); // 修电箱
|
||||
await advance(() => playLog.filter((e) => e.id === 'voice/gd_room_r').length >= 2);
|
||||
await advance(() => playLog.some((e) => e.id === 'voice/gd_gate_dir'));
|
||||
const ifx = playLog.findIndex((e) => e.id === 'voice/fix_box');
|
||||
assert(ifx > -1 && playLog.map((e) => e.id).lastIndexOf('voice/gd_room_r') > ifx,
|
||||
'H1:fix_box 后接播 gd_room_r(目标大门按图在东)');
|
||||
assert(ifx > -1 && playLog.findIndex((e) => e.id === 'voice/gd_gate_dir') > ifx,
|
||||
'B2:fix_box 后接播 gd_gate_dir(大门,在右面的房间)');
|
||||
assert(game.powerOn, '前提:电箱已通电(接线点均在真实解谜链上)');
|
||||
game.stop();
|
||||
resetTimers();
|
||||
}
|
||||
|
||||
realLog('== ⑩d B6 escape:引导 busy 窗口手势缓存(full 档)——解锁后补判、答错路径、不触发跳过 ==');
|
||||
{
|
||||
const rig = makeRig(EscapeGame);
|
||||
const { audio, playLog, game } = rig;
|
||||
await audio.unlock();
|
||||
game.start();
|
||||
await advance(() => game.phase === 'GUIDE' && !game._guideBusy && !audio.isVoicePlaying()); // G0 待输入
|
||||
await gesture(game, 'tap'); // G0 → _advance(1) busy(g1_move + skip_hint)
|
||||
assert(game._guideBusy, '前提:G0→G1 推进语音播放中(busy 窗口)');
|
||||
await gesture(game, 'up'); // busy 窗口注入滑动 → 缓存(不当场判定)
|
||||
assert(game.guideStep === 0 && game.guideSwipes === 0, 'B6:busy 窗口手势不当场判定');
|
||||
// 解锁 G1 → 缓存 up 按 G1 补判 = 作答(sfx/step + 推进 G2)
|
||||
await advance(() => game.guideStep === 2 && !game._guideBusy);
|
||||
assert(playLog.some((e) => e.id === 'sfx/step'), 'B6:full 档下缓存手势解锁后补判作答(脚步声)');
|
||||
|
||||
// G2 busy 缓存非答案手势 → 解锁 G3 后按答错路径(纠正语音,不触发下滑跳过)
|
||||
await gesture(game, 'up'); // G2 第一滑
|
||||
await advance(() => !audio.isVoicePlaying());
|
||||
await gesture(game, 'right'); // G2 第二滑 → _advance(3) busy
|
||||
await gesture(game, 'down'); // busy 缓存下滑(非 G3 答案)
|
||||
assert(game.guideStep === 2, 'B6:busy 窗口仍停 G2');
|
||||
await advance(() => game.guideStep === 3 && !game._guideBusy);
|
||||
assert(voicePlays(playLog, 'voice/guide_wrong_left') === 1, 'B6:缓存手势不合法按答错路径处理(guide_wrong_left)');
|
||||
assert(voicePlays(playLog, 'voice/guide_skip') === 0, 'B6:缓存的下滑不触发跳过引导(live round1 S6 事故修复)');
|
||||
assert(game.phase === 'GUIDE', 'B6:仍在引导中(未被保送 PLAY)');
|
||||
game.stop();
|
||||
resetTimers();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 汇总
|
||||
|
||||
realLog(`\n通过 ${passCount} 条断言,失败 ${failures.length} 条`);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* 《暗室》新手会话剧本:老玩家二周目不算——这里清空过进度走首局引导(G0-G7),
|
||||
* 每步听清再做;进 PLAY 后先连撞两次墙(触发向导提前一级的卡关提示),
|
||||
* 然后找到铁柜房开柜拿保险丝、照 round4 H1 的目标方位句跨四个房间修电箱、
|
||||
* 再摸到大门收工(目标句后 3s 内都能听到方位句)。
|
||||
* 每步听清再做;进 PLAY 后先连撞两次墙(round2 B1:wall 后跟陈述式方位句
|
||||
* 「上边是墙。」,同向 10s 限频),经空房首访描述(B5a)找到铁柜房开柜拿保险丝、
|
||||
* 照 round2 B2 的"目标名+方位"一体句跨四个房间修电箱、再摸到大门收工。
|
||||
*/
|
||||
export function describe(game) {
|
||||
switch (game.phase) {
|
||||
@@ -55,31 +55,34 @@ export async function session(ctx, mock) {
|
||||
if (why) ctx.note(why);
|
||||
};
|
||||
|
||||
// 先往北乱撞探探边界:两步走到 (0,0),再连撞两次北墙——向导把卡关提示提前带上来
|
||||
// 先往北乱撞探探边界:两步走到 (0,0)——(0,1) 首访会播 empty_2(B5a 空房听觉锚),
|
||||
// 再连撞两次北墙——wall 后各跟一句陈述式方位「上边是墙。」(B1,同向 10s 限频)
|
||||
ctx.silence(3, '辨了辨方向');
|
||||
await move('up');
|
||||
await move('up');
|
||||
await move('up', '北边是墙,撞了一下——「那边是墙」后面还跟了句方向(向上推的是北墙)');
|
||||
await move('up', '又撞了一下——听到游戏主动来问我了');
|
||||
await ctx.waitVirtual(5200); // 等卡关轮询的 5s tick:连续撞墙 → 提前一级的暗示
|
||||
await move('up', '路过一间空房,它自己开口了:摸到的只有冰冷粗糙的墙壁');
|
||||
await move('up', '电流声——电箱房到了');
|
||||
await move('up', '北边是墙,撞了一下——「那边是墙」后面跟了句「上边是墙。」,这面不通记住了');
|
||||
await move('up', '又撞了一下——同方向 10 秒内不重复报方位,游戏不啰嗦');
|
||||
await ctx.waitVirtual(5200); // 缓一缓(卡关轮询 5s tick 空转,活跃操作不触发提示)
|
||||
|
||||
// 去铁柜房:(0,0) → 右×2 = (2,0) → 下 = (2,1),双击用钥匙
|
||||
await move('right', '往东走,风声换了个方向');
|
||||
await move('right');
|
||||
// ((1,0)/(2,0) 首访各播 empty_1/empty_3——B5a:四间空房从此声学可区分)
|
||||
await move('right', '往东走,一间空房自报家门:空空荡荡只有回声');
|
||||
await move('right', '又一间空房:除了远处的水滴声什么都没有');
|
||||
await move('down', '到了铁柜房,柜子锁着');
|
||||
// 用钥匙 → unlock_cabinet + gd_next_fuse + gd_room_l(round4 H1:目标句带方位,
|
||||
// 从铁柜房 (2,1) 看电箱房 (0,0) 在西 = 左面的房间)
|
||||
// 用钥匙 → unlock_cabinet + gd_next_fuse + gd_box_dir(round2 B2:目标名+方位
|
||||
// 一体句,从铁柜房 (2,1) 看电箱房 (0,0) 在西 = 「电箱,在左面的房间。」)
|
||||
await ctx.gesture('doubletap');
|
||||
await ctx.waitVirtual(5600);
|
||||
ctx.status('柜子开了。「保险丝到手。接下来,去找电箱。」后面还跟了一句「左面的房间。」——这回知道该往哪边摸了');
|
||||
ctx.status('柜子开了。「保险丝到手。接下来,去找电箱。」后面跟的是「电箱,在左面的房间。」——目标名和方位一句话说全了');
|
||||
// round4 H1 剧本:照方位句跨房间去电箱房——(2,1) → 左×2 = (0,1) → 上 = (0,0)
|
||||
await move('left', '照方位句往左(西)摸');
|
||||
await move('left');
|
||||
await move('up', '电流声越来越清晰——电箱房到了');
|
||||
// 修电箱 → fix_box + gd_room_r(round4 H1:从 (0,0) 看大门 (2,2) 在东 = 右面的房间)
|
||||
// 修电箱 → fix_box + gd_gate_dir(round2 B2:从 (0,0) 看大门 (2,2) 在东 =
|
||||
// 「大门,在右面的房间。」)
|
||||
await ctx.gesture('doubletap');
|
||||
await ctx.waitVirtual(5600);
|
||||
ctx.status('电箱修好了。目标句后面照样跟着方位——「右面的房间。」大门在东边');
|
||||
ctx.status('电箱修好了。目标句后面照样是目标名+方位——「大门,在右面的房间。」');
|
||||
// 照方位句多跨两个房间摸向大门:(0,0) → 右×2 = (2,0) → 下×2 = (2,2)
|
||||
await move('right', '照方位句往右(东)摸');
|
||||
await move('right');
|
||||
|
||||
@@ -1038,7 +1038,17 @@
|
||||
"time_band4": "你逃出来了,用时超过十分钟。",
|
||||
"unlock_cabinet": "钥匙插进锁孔,缓缓转动。咔哒一声,柜门开了。柜子深处,躺着一枚保险丝。保险丝到手了。",
|
||||
"use_fail": "这里没有用得上东西的地方。换个房间试试,或者先单击检查一下周围。",
|
||||
"wall": "那边是墙,换个方向。"
|
||||
"wall": "那边是墙,换个方向。",
|
||||
"wall_dir_l": "左边是墙。",
|
||||
"wall_dir_r": "右边是墙。",
|
||||
"wall_dir_u": "上边是墙。",
|
||||
"wall_dir_d": "下边是墙。",
|
||||
"gd_cabinet_dir": "铁柜,在右面的房间。",
|
||||
"gd_box_dir": "电箱,在左面的房间。",
|
||||
"gd_gate_dir": "大门,在右面的房间。",
|
||||
"gd_prog_1": "钥匙已经到手。找到能用它的锁。",
|
||||
"gd_prog_2": "铁柜已经打开。电还没有通。",
|
||||
"gd_prog_3": "电已经通了。去找那扇大门。"
|
||||
},
|
||||
"hunt": {
|
||||
"breach": "有野兽冲进营地了!",
|
||||
@@ -1958,4 +1968,4 @@
|
||||
"zhou_r2_plain": "茶是茶,水是水,差点火候。儿子的事,改天再说吧。",
|
||||
"zhou_r2_wrong": "……今晚这盏不对。儿子的事,不说也罢。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user