lili 0fc1cb695b
Some checks failed
contract-gates / contract-gates (push) Has been cancelled
docs-gate / docs-gate (push) Has been cancelled
feat(polaris): 《山海巡异录》M3——战斗绘制 TDZ 根因修复+双职业 154 卡+进阶 A0–A15 六带全绿
绘制修复(mini-desktop CDP 复验揪出:战斗屏除回合横幅外 lumStd=0 全空,逻辑与指针全通):根因=run-scene.js
const txt 声明位于工厂 return 之后永不执行,txt 永陷 TDZ,全部整屏绘制函数每帧 ReferenceError 无声失败,
唯 floatText(function 声明提升)独活=只剩横幅;M1 并入 run-scene 时引入,headless 门不建场景故只有真浏览器
暴露。修=txt 改 function 声明(提升语义)+事故注释钉原地+自证钩子(场景 rebuild 后回写
readState().debug={displayCount,rebuilds,screen} 供 CDP 机断)+防回潮门 scene-smoke.mjs(mock 场景重放十屏/
异常零容忍/每屏绘制对象≥5;修复后 map=33/battle=32/reward=14)。
M3:S13 职业乙 66+中立 22=154 卡定容(static-screen 3,676,134 序列零经济环)+S14 进阶 ASCENSIONS 16 级入
矩阵轴+多样性 driver(chain/defensive 档,死卡候选 42 张如实挂账 README)+S15 meta 三件(图鉴/成就 40/每日,
meta-core.js+meta.test)+S16 事件 45;存档 v2 双域+v1 迁移链(19 断言);home 局外模式机+终局结算。
门证据:八门全绿(battle-core/logic-smoke/run-core+R15 职业进阶 7 断言/run-smoke/save v2 19 断言/meta/
static-screen/scene-smoke);六带全绿 nightly n=1000/格(甲 A0 58.2%/A5 32.2%/A15 6.9%,乙 55.1%/28.3%/5.7%,
带 45–60/25–40/5–15);构筑指纹去重 A0 六格全 100%(门>85% 草案);金标 26 套(甲 20 漂移≤3.6pp 全 PASS+乙 6 首锁);
**全矩阵 96,000 局书面口径首次真跑=158.6s、nightly 总 223.2s**;平衡轮主杠杆=职业起始遗物 +36.6pp;
主会话真跑复验八门+PR 层。浏览器像素复验随本笔 push 后触发。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:40:11 -07:00

131 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* scripts/static-screen.mjs —— 《山海巡异录》静态快筛(M2 S9;链护栏第①层 + 估值黄牌)
* owner:北极星顶级线③(M2)
*
* 【验什么】设计书 §6.3 无限连护栏·静态快筛层:对卡池做深度 ≤3 的有序组合遍历,
* 标红「净能量增益 + 净抽卡增益」同时为正的闭环(exit 1=命中,须同池禁入或加疲劳规则)。
* 本层只覆盖 ≤3 卡短闭环,是快筛不是证明;≥4 卡长循环靠运行时动作护栏+指纹观测兜(②③层)。
* 附带:估值黄牌普查(valuation.js 单源,advisory 只打印不拦——平衡门裁定二)。
*
* 【词汇封闭假设】经济环只由 energy/draw/chainBonus{energy,draw}/costChainDiscount 四词构成,
* 本筛用表级折算器(链态是唯一动态,精确可算)。E0 断言钉死封闭性:表中出现的经济词
* 若超出折算器词汇集,立即 FAIL——新经济词入表必须先扩本筛,防绕过。
*
* 用法:node scripts/static-screen.mjs(退出码 0=无命中;S9/S10 每批入表即跑)
*/
'use strict';
import { CARDS, ELEMENT_CYCLE } from '../src/data/tables.js';
import { valueOf } from '../src/sim/valuation.js';
import { isShengNext } from '../src/battle-core.js';
let failures = 0;
const fail = (msg) => { console.error(` FAIL ${msg}`); failures++; };
console.log(`[static-screen] 卡池 ${CARDS.length} 张;深度 ≤3 经济环快筛 + 估值黄牌`);
// ── E0 经济词汇封闭性(折算器认知集 = 表中实际经济词集;漂移即红)──
const ECON_WORDS = new Set(['energy', 'draw', 'chainBonus', 'costChainDiscount', 'ifZhoutian']);
{
const seen = new Set();
for (const c of CARDS) {
for (const k of Object.keys(c.effects)) {
// 产能量/产抽卡的词才是经济词;伤害/护甲/词缀/血价不构成经济环。
if (k === 'energy' || k === 'draw' || k === 'costChainDiscount') seen.add(k);
if (k === 'chainBonus' && (c.effects.chainBonus.energy || c.effects.chainBonus.draw)) seen.add('chainBonus');
if (k === 'ifZhoutian' && (c.effects.ifZhoutian.energy || c.effects.ifZhoutian.draw)) seen.add('ifZhoutian');
}
}
const unknown = [...seen].filter((w) => !ECON_WORDS.has(w));
if (unknown.length) fail(`E0 经济词超出折算器词汇集:${unknown.join(',')}(先扩本筛再入表)`);
else console.log(` PASS E0 经济词汇封闭(${[...seen].join(',') || '无'})`);
}
// ── E1 深度 ≤3 有序序列遍历(同名可重复=卡组多份合法)──
{
/**
* 表级折算一个序列:按打出序推链态,算净能量(得-费)与净抽(抽-打出数)。
* 费用折扣(costChainDiscount)与成链加成(chainBonus)按相生序精确判定。
*/
function evalSeq(seq) {
let prevEl = null;
let netEnergy = 0;
let netDraw = 0;
for (const c of seq) {
const extendsChain = prevEl != null && isShengNext(prevEl, c.element);
let cost = c.cost;
if (c.effects.costChainDiscount && extendsChain) cost = Math.max(0, cost - c.effects.costChainDiscount.n);
netEnergy -= cost;
if (c.effects.energy) netEnergy += c.effects.energy.n;
if (c.effects.draw) netDraw += c.effects.draw.n;
if (c.effects.chainBonus && extendsChain) {
if (c.effects.chainBonus.energy) netEnergy += c.effects.chainBonus.energy;
if (c.effects.chainBonus.draw) netDraw += c.effects.chainBonus.draw;
}
// ifZhoutian(M3):周天须五行全环,深度 ≤3 序列数学上不可能触发——本筛零入账即精确;
// ≥4 卡的周天经济环归运行时兜底层+指纹观测(§6.3 分层设防,快筛不越界宣称)。
netDraw -= 1; // 打出即消耗一张手牌
prevEl = c.element; // 延续与破序殊途同归:链尾都是本卡元素(破序=重开链,链首=本卡)
}
return { netEnergy, netDraw };
}
const hits = [];
const n = CARDS.length;
let checked = 0;
// 深度 13 全遍历(66³≈28.7 万序列,表级毫秒批)。
for (let a = 0; a < n; a++) {
const r1 = evalSeq([CARDS[a]]);
checked++;
if (r1.netEnergy > 0 && r1.netDraw > 0) hits.push([CARDS[a].id]);
for (let b = 0; b < n; b++) {
const r2 = evalSeq([CARDS[a], CARDS[b]]);
checked++;
if (r2.netEnergy > 0 && r2.netDraw > 0) hits.push([CARDS[a].id, CARDS[b].id]);
for (let c = 0; c < n; c++) {
const r3 = evalSeq([CARDS[a], CARDS[b], CARDS[c]]);
checked++;
if (r3.netEnergy > 0 && r3.netDraw > 0) hits.push([CARDS[a].id, CARDS[b].id, CARDS[c].id]);
}
}
}
if (hits.length) {
fail(`E1 经济环命中 ${hits.length} 条(样本:${hits.slice(0, 5).map((h) => h.join('→')).join(' | ')})`);
} else {
console.log(` PASS E1 深度 ≤3 无「净能量+净抽卡同正」闭环(遍历 ${checked} 序列)`);
}
}
// ── E2 估值黄牌普查(advisory:超稀有度帽打印;超 hardCap 才 FAIL,与 T1e 同线)──
{
const overCaps = [];
let overHard = null;
for (const c of CARDS) {
const v = valueOf(c);
if (v.overCap) overCaps.push(`${c.name}(${c.rarity})=${v.value}>帽${v.cap}`);
if (v.overHard) overHard = `${c.name}=${v.value}`;
}
if (overCaps.length) console.log(` WARN 估值黄牌 ×${overCaps.length}(advisory):${overCaps.join(' / ')}`);
else console.log(' PASS E2 全池估值均在稀有度帽内');
if (overHard) fail(`E2 估值超 hardCap:${overHard}(表失误量级)`);
}
// ── E3 表卫生(id 唯一/五行合法/稀有度合法/费用域)──
{
const ids = new Set();
let ok = true;
for (const c of CARDS) {
if (ids.has(c.id)) { fail(`E3 id 重复:${c.id}`); ok = false; }
ids.add(c.id);
if (!ELEMENT_CYCLE.includes(c.element)) { fail(`E3 非法五行:${c.id}=${c.element}`); ok = false; }
if (!['普通', '罕见', '稀有'].includes(c.rarity)) { fail(`E3 非法稀有度:${c.id}`); ok = false; }
if (!(Number.isInteger(c.cost) && c.cost >= 0 && c.cost <= 3)) { fail(`E3 费用出域:${c.id}=${c.cost}`); ok = false; }
if (!c.text || !c.name) { fail(`E3 缺文案:${c.id}`); ok = false; }
}
if (ok) console.log(` PASS E3 表卫生(id/五行/稀有度/费用/文案)`);
}
console.log(failures === 0 ? '[static-screen] 全绿' : `[static-screen] ${failures} 条失败`);
process.exit(failures === 0 ? 0 : 1);