fix(saa): B 收尾·客观门跨层对齐 advisory——objectiveGatesPass 读 verdict.advisoryGates
根因:8ac4ccc8 的「未驱动 E_live/H 降 advisory」只落在 play.cdp.cjs(verdict.pass/ advisoryGates),未传到生成环消费层 SaaGenNodes.objectiveGatesPass——后者把 E_live 当 硬客观门、完全不读 advisoryGates。未驱动游戏(打地鼠 menu 态无 driver→phase 恒 menu→ _forensicsView distinctStates:1)E_live 每轮必挂→1+maxRepairs(5)=6 次 M3 假失败救场, 与既有 line651 注释里早修过的 H_progress「空跑 8 轮救场/302K token」同一类病(driver 依赖门绑架生成)。 修: - objectiveGatesPass / summarizeFailedGuards 读 verdict.advisoryGates 同口径降级。 advisory 门要求【存在】(保住 C2 崩溃安全:缺门=harness 未跑完=失败)但【允许 pass=false】 (未驱动无法公平验活性);driven 时 play.cdp.cjs 不列入 advisory → 自动恢复致命,回归不破。 - objectiveGatesPass 提包级可见(与 summarizeFailedGuards 同范式),供纯逻辑单测。 验证:SaaFullGraphE2eGateLogicTest 23/23 PASS(+5 advisory 案:未驱动 E_live→过/ driven E_live→挂/缺门→挂/全过/反馈跳 advisory)。e2e 已实证 verdict.pass=true、 E_live/H 列 advisory(advisory 跨层生效)。 注:e2e 绿基线仍被一个【独立·先存】问题阻断——过门游戏不产终态回调(player 节点 M3 首次在 pass 路被触达,疑 M3-vision 挂),非本修引入,下一步处置。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8ac4ccc861
commit
cbe42933d0
@ -715,25 +715,51 @@ final class SaaGenNodes {
|
||||
* <p>遍历 verdict.guards 中出现的门,只对客观门要求 pass;driver 门跳过。无 guards 明细或未见任何客观门→
|
||||
* 保守退回聚合 verdict.pass(避免误放空壳)。<b>不改九门判定本身</b>,只改生成环对其的消费口径。
|
||||
*/
|
||||
private static boolean objectiveGatesPass(JsonNode verdict) {
|
||||
static boolean objectiveGatesPass(JsonNode verdict) { // 包级可见:供 SaaFullGraphE2eGateLogicTest 纯逻辑回归(与 summarizeFailedGuards 同范式)。
|
||||
JsonNode guards = verdict.path("guards");
|
||||
if (!guards.isObject() || guards.size() == 0) {
|
||||
return verdict.path("pass").asBoolean(false);
|
||||
}
|
||||
// C2 修(CodeRabbit/Codex 双报,2026-06-20):客观门按【必达集】校验——5 门必须【全部存在且 pass】,缺任一即判失败。
|
||||
// B 收尾·跨层对齐 advisory(2026-06-22):play.cdp.cjs 在【未驱动】(spec 无 driver/inputs)时把 E_live/H_progress 降为
|
||||
// advisory(verdict.advisoryGates) 并据此算 verdict.pass。该降级必须同口径传到生成环消费层,否则未驱动游戏每轮卡
|
||||
// E_live 触发无谓救场重生成——实证打地鼠 menu 态无 driver→phase 恒 menu→_forensicsView distinctStates:1→E_live 必挂,
|
||||
// 1+maxRepairs(5)=6 次 M3 生成全假失败(与 line 651 H_progress 空跑救场同一类病:driver 依赖门绑架生成)。
|
||||
java.util.Set<String> advisory = readAdvisoryGates(verdict);
|
||||
// C2 修(CodeRabbit/Codex 双报,2026-06-20):客观门按【必达集】校验——5 门必须【全部存在】,缺任一即判失败。
|
||||
// 旧"遍历出现的门、出现才查"会被 harness 中途崩钻空子误判 PASS:play.cdp.cjs 的 runDriver(含 seek-food)在
|
||||
// D_render/E_live 之前执行且未 try/catch,conc=12 下 CDP 抖动抛错→主 catch 只补 A_boot→verdict 只剩
|
||||
// A_boot/C_frame/I_control→旧逻辑 sawObjective=true 放行=【崩掉、根本没测到渲染/活性的游戏被判过】。
|
||||
// 缺门=harness 未跑完=失败,才安全(旧 rc==0 口径反不会误判;本修补回该安全性、又不重新绑 driver 门)。
|
||||
// 缺门=harness 未跑完=失败,才安全。advisory 门要求【存在】(保住该崩溃安全)但【允许 pass=false】(未驱动无法公平验活性)。
|
||||
for (String gate : OBJECTIVE_GATES) {
|
||||
JsonNode g = guards.path(gate);
|
||||
if (g.isMissingNode() || !g.path("pass").asBoolean(false)) {
|
||||
return false; // 客观门缺失或未过 → 生成硬门判失败。
|
||||
if (g.isMissingNode()) {
|
||||
return false; // 客观门缺失=harness 未跑完 → 生成硬门判失败(C2 崩溃安全)。
|
||||
}
|
||||
if (advisory.contains(gate)) {
|
||||
continue; // advisory 门:存在即可,允许未过(如未驱动时 E_live;driven 恢复致命由 play.cdp.cjs 不列入 advisory 实现)。
|
||||
}
|
||||
if (!g.path("pass").asBoolean(false)) {
|
||||
return false; // 非 advisory 客观门未过 → 生成硬门判失败。
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 读 verdict.advisoryGates(play.cdp.cjs 在未驱动时降级的非致命门,如 E_live/H_progress)。缺/非数组→空集。 */
|
||||
private static java.util.Set<String> readAdvisoryGates(JsonNode verdict) {
|
||||
java.util.Set<String> advisory = new java.util.HashSet<>();
|
||||
if (verdict == null) {
|
||||
return advisory;
|
||||
}
|
||||
JsonNode adv = verdict.path("advisoryGates");
|
||||
if (adv.isArray()) {
|
||||
for (JsonNode a : adv) {
|
||||
advisory.add(a.asText());
|
||||
}
|
||||
}
|
||||
return advisory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把失败的守卫摘成回喂文字(对齐 run.py:_verdict_feedback)。
|
||||
*/
|
||||
@ -742,6 +768,7 @@ final class SaaGenNodes {
|
||||
return "真玩阶段无 verdict 产出(疑似装载即崩/超时)。";
|
||||
}
|
||||
List<String> lines = new ArrayList<>();
|
||||
java.util.Set<String> advisory = readAdvisoryGates(verdict);
|
||||
JsonNode guards = verdict.path("guards");
|
||||
if (guards.isObject()) {
|
||||
Iterator<Map.Entry<String, JsonNode>> it = guards.fields();
|
||||
@ -751,6 +778,10 @@ final class SaaGenNodes {
|
||||
if (!GATE_ALL9 && !OBJECTIVE_GATES.contains(e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
// B 收尾:advisory 门(未驱动 E_live/H)非致命,不回喂救场——否则空喂"E_live 未过"驱动无谓重生成。
|
||||
if (!GATE_ALL9 && advisory.contains(e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
if (!e.getValue().path("pass").asBoolean(false)) {
|
||||
String g = e.getValue().toString();
|
||||
lines.add("- 守卫 " + e.getKey() + " 未过:" + (g.length() > 200 ? g.substring(0, 200) : g));
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.wanxiang.huijing.game.module.aigc.saa;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.wanxiang.huijing.game.module.aigc.service.executor.AigcExecutorProperties;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -246,4 +248,71 @@ class SaaFullGraphE2eGateLogicTest {
|
||||
assertEquals(1, clamp.size(), "k<1 → 至少 1 槽(防御,不返空)");
|
||||
assertEquals(4320, clamp.get(0)[0]);
|
||||
}
|
||||
|
||||
// ═══════════════════════ B 收尾·客观门跨层对齐 advisory(2026-06-22 修:未驱动 E_live 误触救场)═══════════════════════
|
||||
//
|
||||
// 背景:play.cdp.cjs 在【未驱动】(play-spec 无 driver/inputs)时把 E_live/H_progress 降为 advisory 并据此算 verdict.pass;
|
||||
// 但生成环消费层 objectiveGatesPass 旧实现把 E_live 当硬客观门 → 未驱动游戏(打地鼠 menu 态)phase 恒 menu →
|
||||
// distinctStates:1 → E_live 必挂 → 1+maxRepairs(5)=6 次 M3 假失败。本组锁住「客观门读 advisoryGates 同口径降级」。
|
||||
|
||||
/** 用 JSON 串构 verdict 节点(测试本地工具,避免手搭 ObjectNode)。 */
|
||||
private static JsonNode verdict(String json) {
|
||||
try {
|
||||
return new ObjectMapper().readTree(json);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("测试 verdict JSON 非法:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("objectiveGatesPass:未驱动 advisory E_live 失败 → 仍 PASS(B 收尾跨层对齐,不再误触救场)")
|
||||
void objectiveGatesPass_advisoryEliveFails_stillPass() {
|
||||
// 打地鼠 menu 态无 driver:A-D 全过 + E_live 失败但列入 advisoryGates → 客观门应判过(与 verdict.pass 同口径)。
|
||||
JsonNode v = verdict("{\"pass\":true,\"advisoryGates\":[\"E_live\",\"H_progress\"],"
|
||||
+ "\"guards\":{\"A_boot\":{\"pass\":true},\"B_uncaught\":{\"pass\":true},"
|
||||
+ "\"C_frame\":{\"pass\":true},\"D_render\":{\"pass\":true},"
|
||||
+ "\"E_live\":{\"pass\":false,\"distinctStates\":1}}}");
|
||||
assertTrue(SaaGenNodes.objectiveGatesPass(v), "advisory E_live 失败不应否决客观门(否则未驱动游戏空跑救场)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("objectiveGatesPass:driven(advisoryGates 空)E_live 失败 → FAIL(driven 恢复致命,回归不破)")
|
||||
void objectiveGatesPass_drivenEliveFails_fail() {
|
||||
// 有 driver 时 play.cdp.cjs 不把 E_live 列入 advisory → 客观门仍须 E_live 过,挂则判失败。
|
||||
JsonNode v = verdict("{\"pass\":false,\"advisoryGates\":[],"
|
||||
+ "\"guards\":{\"A_boot\":{\"pass\":true},\"B_uncaught\":{\"pass\":true},"
|
||||
+ "\"C_frame\":{\"pass\":true},\"D_render\":{\"pass\":true},"
|
||||
+ "\"E_live\":{\"pass\":false}}}");
|
||||
assertFalse(SaaGenNodes.objectiveGatesPass(v), "driven 时 E_live 仍是硬客观门");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("objectiveGatesPass:客观门缺失(harness 崩)→ FAIL(C2 崩溃安全不被 advisory 削弱)")
|
||||
void objectiveGatesPass_missingGate_failEvenIfAdvisory() {
|
||||
// D_render 缺失(harness 中途崩,没跑到渲染门):即便 advisory 含 D_render 也不得放行——缺=没跑完=失败。
|
||||
JsonNode v = verdict("{\"advisoryGates\":[\"D_render\",\"E_live\"],"
|
||||
+ "\"guards\":{\"A_boot\":{\"pass\":true},\"B_uncaught\":{\"pass\":true},"
|
||||
+ "\"C_frame\":{\"pass\":true},\"E_live\":{\"pass\":true}}}");
|
||||
assertFalse(SaaGenNodes.objectiveGatesPass(v), "客观门缺失=harness 未跑完,advisory 也不得放行(C2 崩溃安全)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("objectiveGatesPass:全客观门过 + 无 advisory → PASS(happy)")
|
||||
void objectiveGatesPass_allPass() {
|
||||
JsonNode v = verdict("{\"guards\":{\"A_boot\":{\"pass\":true},\"B_uncaught\":{\"pass\":true},"
|
||||
+ "\"C_frame\":{\"pass\":true},\"D_render\":{\"pass\":true},\"E_live\":{\"pass\":true}}}");
|
||||
assertTrue(SaaGenNodes.objectiveGatesPass(v));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("summarizeFailedGuards:advisory 门不回喂救场;真硬门失败仍回喂")
|
||||
void summarizeFailedGuards_skipsAdvisory() {
|
||||
// advisory E_live 失败不进救场反馈(否则空喂"E_live 未过"驱动无谓重生成);真硬门 D_render 失败仍须回喂。
|
||||
JsonNode v = verdict("{\"advisoryGates\":[\"E_live\"],"
|
||||
+ "\"guards\":{\"A_boot\":{\"pass\":true},\"D_render\":{\"pass\":false},"
|
||||
+ "\"E_live\":{\"pass\":false,\"distinctStates\":1}}}");
|
||||
String fb = SaaGenNodes.summarizeFailedGuards(v);
|
||||
assertTrue(fb.contains("D_render"), "真硬门 D_render 失败应回喂救场");
|
||||
assertFalse(fb.contains("E_live"), "advisory E_live 不应回喂救场");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user