feat(saa): 零-LLM driver 内置绑定 injectDriverByArchetype + 5 单测
design 只声明 gatespec 不写 driver→amodel 路无人绑 driver→游戏停菜单→九门 E_live/H 名存实亡。按 classify archetype 确定性补内置 driver(经营/反应→tap occupied、三消→placement、跑酷→key-cycle),显式 driver 不覆盖、无合适内置(如 bubble)不强补。WIP:未在 mini mvn 验;archetype→targets 契约假设正于 driver 调查中复核。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f5918e449e
commit
d342de088e
@ -457,6 +457,11 @@ final class SaaGenNodes {
|
||||
try {
|
||||
Path spec = gameRuntimeRoot.resolve("games").resolve("_wg1-gen").resolve(gameId).resolve("play-spec.json");
|
||||
String playSpec = s.value(SaaStudioNodes.K_PLAY_SPEC, String.class).orElse("{}");
|
||||
// 零-LLM driver 内置绑定:design 按 gatespec 规则③只声明「什么算赢/有进展」、不写 driver;amodel 路无人绑 driver →
|
||||
// 游戏不被驱动→停菜单→九门 E_live/H 因「未驱动」降 advisory 名存实亡(创始人「门要可信」指令)。此处据 classify 的
|
||||
// archetype 确定性补内置 driver,让经营/反应/放置类被真玩通,使门真正测到「能不能玩」。已显式声明 driver 则尊重不覆盖。
|
||||
String archetype = s.value(SaaStudioNodes.K_ARCHETYPE, String.class).orElse("");
|
||||
playSpec = injectDriverByArchetype(playSpec, archetype);
|
||||
Files.write(spec, playSpec.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
errs.add("amodel play-spec.json 写入失败:" + e.getMessage());
|
||||
@ -795,6 +800,54 @@ final class SaaGenNodes {
|
||||
return lines.isEmpty() ? "真玩未过(原因未知)。" : "真玩未过:\n" + String.join("\n", lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* 零-LLM driver 内置绑定:据 classify 的 archetype 确定性映射到 play.cdp.cjs 内置 driver(harness 自适应读 _forensicsView state 出招)。
|
||||
* <p>design 按 gatespec 规则③只声明「什么算赢/有进展」、不写 driver;但 amodel 路无人绑 driver → 游戏不被驱动 → 停菜单 →
|
||||
* 九门 E_live/H 因「未驱动」降 advisory 名存实亡。本处补齐:让经营/反应/放置等类被真玩通,使门可信(创始人指令)。
|
||||
* <p><b>已含 driver(显式声明)则原样返回不覆盖</b>;无合适内置 driver(如 narrative)则不补;解析异常原样返回(绝不因补 driver 破坏 play-spec)。
|
||||
*/
|
||||
static String injectDriverByArchetype(String playSpecJson, String archetype) {
|
||||
try {
|
||||
JsonNode node = MAPPER.readTree((playSpecJson == null || playSpecJson.isBlank()) ? "{}" : playSpecJson);
|
||||
if (!node.isObject()) {
|
||||
return playSpecJson;
|
||||
}
|
||||
com.fasterxml.jackson.databind.node.ObjectNode spec = (com.fasterxml.jackson.databind.node.ObjectNode) node;
|
||||
if (spec.has("driver") && !spec.get("driver").isNull()) {
|
||||
return spec.toString(); // 显式 driver 优先,不覆盖
|
||||
}
|
||||
com.fasterxml.jackson.databind.node.ObjectNode driver = driverForArchetype(archetype);
|
||||
if (driver == null) {
|
||||
return spec.toString(); // 无合适内置 driver(如 narrative)→ 不补,保持未驱动(E_live/H 走 advisory)
|
||||
}
|
||||
spec.set("driver", driver);
|
||||
return spec.toString();
|
||||
} catch (Exception e) {
|
||||
return playSpecJson; // 解析异常 → 原样,不破坏 play-spec
|
||||
}
|
||||
}
|
||||
|
||||
/** archetype → 内置 driver 配置(null=无合适内置 driver)。保守可扩展:经营/反应类点 occupied 目标,放置类点空位,跑酷/躲避走键循环。 */
|
||||
private static com.fasterxml.jackson.databind.node.ObjectNode driverForArchetype(String archetype) {
|
||||
String a = (archetype == null) ? "" : archetype.trim().toLowerCase();
|
||||
com.fasterxml.jackson.databind.node.ObjectNode d = MAPPER.createObjectNode();
|
||||
switch (a) {
|
||||
// 经营/放置/合成/点击器/打地鼠/兜底:点「有内容」的离散目标(等待的客/冒头的鼠/可收产出)推进 → 服务族 tap-occupied。
|
||||
case "tycoon": case "idle": case "clicker": case "merge": case "generic": case "":
|
||||
return d.put("type", "tap-targets").put("targetsPath", "targets").put("targetMode", "occupied")
|
||||
.put("steps", 80).put("stepMs", 500);
|
||||
// 三消/消行:点离散空位/可消组 → 放置族(默认 occupied!==true)。
|
||||
case "match3": case "line-clear":
|
||||
return d.put("type", "tap-targets").put("targetsPath", "targets").put("steps", 40).put("stepMs", 360);
|
||||
// 跑酷/躲避:连续输入 → 键循环兜底。
|
||||
case "runner": case "dodge":
|
||||
return d.put("type", "key-cycle").put("steps", 60).put("stepMs", 220);
|
||||
// 瞄准发射(泡泡龙)等:无通用内置参数,本期不补 → 不驱动(E_live/H advisory),留未来 aim-fire 绑定。
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 取字符串尾部 n 字(日志摘要用)。 */
|
||||
private static String tail(String s, int n) {
|
||||
if (s == null) {
|
||||
|
||||
@ -315,4 +315,48 @@ class SaaFullGraphE2eGateLogicTest {
|
||||
assertTrue(fb.contains("D_render"), "真硬门 D_render 失败应回喂救场");
|
||||
assertFalse(fb.contains("E_live"), "advisory E_live 不应回喂救场");
|
||||
}
|
||||
|
||||
// ═══════════════════════ ② driver 内置绑定 archetype→driver(零-LLM,2026-06-23 wanglanmei E2E)═══════════════════════
|
||||
//
|
||||
// design 只声明 gatespec、不写 driver → amodel 路无人绑 driver → 游戏不被驱动 → 九门 E_live/H 名存实亡。
|
||||
// injectDriverByArchetype 据 classify archetype 确定性补内置 driver,使经营/反应类被真玩通(创始人「门要可信」)。
|
||||
|
||||
@Test
|
||||
@DisplayName("injectDriverByArchetype:tycoon(经营)→ 补 tap-targets occupied(点等待的客→真驱动)")
|
||||
void injectDriver_tycoon_tapOccupied() {
|
||||
JsonNode v = verdict(SaaGenNodes.injectDriverByArchetype("{\"expectLatch\":true}", "tycoon"));
|
||||
assertEquals("tap-targets", v.path("driver").path("type").asText());
|
||||
assertEquals("occupied", v.path("driver").path("targetMode").asText(), "经营/服务族点 occupied 目标");
|
||||
assertTrue(v.path("expectLatch").asBoolean(), "原 gatespec 字段须保留");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("injectDriverByArchetype:match3 → tap-targets 放置族(不带 targetMode)")
|
||||
void injectDriver_match3_placement() {
|
||||
JsonNode v = verdict(SaaGenNodes.injectDriverByArchetype("{}", "match3"));
|
||||
assertEquals("tap-targets", v.path("driver").path("type").asText());
|
||||
assertTrue(v.path("driver").path("targetMode").isMissingNode(), "放置族不带 occupied 语义");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("injectDriverByArchetype:已显式 driver → 不覆盖(尊重显式声明)")
|
||||
void injectDriver_respectsExplicit() {
|
||||
JsonNode v = verdict(SaaGenNodes.injectDriverByArchetype("{\"driver\":{\"type\":\"seek-food\"}}", "tycoon"));
|
||||
assertEquals("seek-food", v.path("driver").path("type").asText(), "显式 driver 优先,不被 archetype 覆盖");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("injectDriverByArchetype:无合适内置(bubble)→ 不补(保持未驱动 advisory)")
|
||||
void injectDriver_noFit_skips() {
|
||||
JsonNode v = verdict(SaaGenNodes.injectDriverByArchetype("{\"expectLatch\":true}", "bubble"));
|
||||
assertTrue(v.path("driver").isMissingNode(), "无合适内置 driver 不强补(否则误绑→门误判)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("injectDriverByArchetype:空/generic → 兜底 tap-targets occupied")
|
||||
void injectDriver_genericFallback() {
|
||||
JsonNode v = verdict(SaaGenNodes.injectDriverByArchetype("{}", ""));
|
||||
assertEquals("tap-targets", v.path("driver").path("type").asText());
|
||||
assertEquals("occupied", v.path("driver").path("targetMode").asText());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user