feat: 研究规则 rules 模块(任务书 02)

rules 是纯计算,src/rules 共 1761 行:方法.md 第六部分的全部公式、第七部分的余额与扣次、4.2 的入口分配,以及种子与规则登记表。

预期值有三个来源:方法.md 的手算例;tests/fixtures/rules/ 下不看实现写的 Python 脚本;参考项目 calc15、calc14b 里与现行方法一致的部分。审阅做了两轮,每轮都换一个全新上下文的子 Agent,提出的问题都已修复或写明不修的理由。

用户确认的约定(原话"1–8 都同意"):入池、采纳、消融中发现都用严格不等号;成本用 max(0, ĝ₀);没测的格附"含它的最好组合"的净值;战绩在分数原值上判。三个数改为严格递增后,删掉了尖峰的全部专门处理(bfb96ba)。vocab 里 Range 的说明同步改为严格递增。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 01:50:21 -04:00
parent bfb96ba7d7
commit 60c21ae346
37 changed files with 14067 additions and 3 deletions
+65
View File
@@ -0,0 +1,65 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import type { Range } from "../src/vocab/index.ts";
// tests/fixtures/rules/ 下的预期值由独立的 Python 脚本照 docs/方法.md 和约定表算出(脚本开头注释写了格式)。
// 这里只负责读文件和比较数字;把场景映射到 rules 的函数上,在各个 rules-fixtures-*.test.ts 里做。
export interface Scenario {
name: string;
section: string;
params: Record<string, any>;
input: Record<string, any>;
expected: any;
note: string;
}
export interface FixtureFile {
generator: string;
tolerance: Record<string, any>;
models?: Record<string, { params: Record<string, any>; input: Record<string, any> }>;
scenarios: Scenario[];
}
export function loadFixture(file: string): FixtureFile {
return JSON.parse(readFileSync(new URL(`./fixtures/rules/${file}`, import.meta.url), "utf8")) as FixtureFile;
}
/** 按名字取场景,并登记"已覆盖";每个测试文件最后核对 JSON 里的场景都被取过。 */
export function scenarioPicker(fixture: FixtureFile): {
get: (name: string) => Scenario;
unused: () => string[];
} {
const used = new Set<string>();
return {
get(name) {
const s = fixture.scenarios.find((x) => x.name === name);
assert.ok(s, `JSON 里没有场景 ${name}`);
used.add(name);
return s;
},
unused: () => fixture.scenarios.map((s) => s.name).filter((n) => !used.has(n)),
};
}
/** 绝对误差 tol 以内算相同。 */
export function close(actual: number, expected: number, tol: number, what: string): void {
assert.ok(
Number.isFinite(actual) && Math.abs(actual - expected) <= tol,
`${what}:实现 ${actual},预期 ${expected},差 ${actual - expected},容差 ${tol}`,
);
}
/** 网格值:|差| ≤ 1e-12 + 1e-9·|值|(ablation.json tolerance.grid)。 */
export function closeGrid(actual: number, expected: number, what: string): void {
close(actual, expected, 1e-12 + 1e-9 * Math.abs(expected), what);
}
export function closeArray(actual: readonly number[], expected: readonly number[], tol: number, what: string): void {
assert.equal(actual.length, expected.length, `${what}:长度`);
actual.forEach((a, i) => close(a, expected[i]!, tol, `${what}[${i}]`));
}
export function range(r: { q1: number; q5: number; q9: number }): Range {
return { q1: r.q1, q5: r.q5, q9: r.q9 };
}