import { compareIds } from "../rules/index.ts"; import type { ChecklistOf } from "../vocab/index.ts"; import { beliefSection, codeSection, Draft, entryVersions, factorLines, metricsSection, ownSection, publicRecords, registeredMetrics, researcherTaskSection, runLines, validEntries, } from "./blocks.ts"; import { json, num, rangeText, score } from "./format.ts"; import { factorId, factorKey } from "./shapes.ts"; // 研究者的 11 种任务,每种一个函数,明写从账本取哪几样(方法.md 第三部分、4.2、4.3)。 // 顺序:你自己的 → 公共材料(任务与本轮、基线、研究记录、信念本、各任务另加的几节、本任务要处理的东西、已登记指标清单)。 // 别人的提交一律经 Board.published(已关阶段的最后一版)读;作者自评、别人的第一次预测、别人的退回都不读。 type Own = ReadonlyArray<{ heading: string; lines: string[] }>; /** * 研究者材料的骨架。middle 放在信念本之后、指标清单之前。 * 第零轮(4.2)只有任务与本轮、基线:不跑实验,信念本不变,也还没有研究记录和指标可列。 */ function frame(d: Draft, own: Own, middle: () => void): void { ownSection(d, own); d.group("公共材料", "下面是大家看到的同一份材料,不隐藏任何正式证据;第一部分已经给过的内容,这里只写一句指引。"); researcherTaskSection(d); codeSection(d); if (d.round >= 1) { publicRecords(d); beliefSection(d); } middle(); if (d.round >= 1) metricsSection(d); } /** 提案、回应、统一共用的因子规则(方法.md 第五部分"延续""指标"、4.3 ②)。 */ function factorRules(d: Draft) { const registered = registeredMetrics(d.board).map((m) => m.id); return { factorLimit: d.req.study.factorLimit, continuable: d.board.protocols().flatMap((p) => p.protocol.factors.map((_, i) => factorId(p.candidate, factorKey(i)))), reusableMetrics: registered, takenMetricIds: [...d.req.study.metrics.map((m) => m.id), ...registered], }; } const PRESUMPTION = '挑刺里标"(推测)"的判断,是挑刺人没对照代码或已测运行核实过的猜想;作者在回应里同意它,也不会让它变成已核实。当作别人的猜想看,不当证据。'; // --------------------------------------------------------------------------- // 第零轮(方法.md 4.2) // --------------------------------------------------------------------------- /** 0.1 闭门写入口:任务说明、基线代码、基线分数;看不到别人的入口(任何入口都不读)。 */ export function entry(d: Draft): ChecklistOf<"entry"> { frame(d, [], () => undefined); return { kind: "entry", entries: { min: 2, max: 3 } }; } /** 入口和之前各次讨论:只读已关阶段的(写入口阶段关了才有讨论)。 */ function entriesAndDiscussions(d: Draft): void { const lines: string[] = []; for (const e of validEntries(d.board)) { d.record(e.id, `入口 ${e.id}:${e.name}(作者 ${e.author})`, e.body); lines.push(`### 入口 ${e.id}:${e.name}(作者 ${e.author})`, "", e.body, ""); } for (const s of d.board.published("discussion")) lines.push(`### ${s.author} 的讨论(阶段 ${s.stageId})`, "", s.content.body, ""); d.section("本任务要处理的东西", lines); const table: string[] = ['程序给的号;整份替换后的新清单接着往后编,旧编号作废,不会指到新入口。']; for (const [author, versions] of entryVersions(d.board)) { versions.forEach((v, i) => { const last = i === versions.length - 1; table.push(`- ${author},${i === 0 ? "写入口时交的" : `第 ${i} 次替换`}${last ? "(最后一版,有效)" : "(已被整份替换,编号作废)"}:`); v.entries.forEach((e, j) => table.push(` - ${v.ids[j]}${last ? "" : "(作废)"}:${e.name}`)); }); } d.section("入口编号对照", table); } /** 0.2 讨论:全部入口(标明作者)、之前各次的讨论。 */ export function discussion(d: Draft): ChecklistOf<"discussion"> { frame(d, [], () => entriesAndDiscussions(d)); return { kind: "discussion", entries: { min: 2, max: 3 } }; } /** 0.3 认领:最终的入口与全部讨论;至少排 min(N, 入口总数) 个。 */ export function claim(d: Draft): ChecklistOf<"claim"> { frame(d, [], () => entriesAndDiscussions(d)); const entries = validEntries(d.board).map((e) => e.id); const n = d.board.stage(d.req.stageId).roster.length; return { kind: "claim", entries, minChoices: Math.min(n, entries.length) }; } // --------------------------------------------------------------------------- // 第 r 轮(方法.md 4.3) // --------------------------------------------------------------------------- /** 某一轮实验员的提醒:每个候选最新一次实现或修复的 concerns,加消融实现的(只读已关阶段)。 */ function concernLines(d: Draft, round: number): string[] { const b = d.board; const candidates = [...new Set([...b.published("implementation"), ...b.published("repair")].filter((s) => s.round === round).map((s) => s.target!))]; const lines = [ ...candidates.map((c) => `- 候选 ${c}:${b.implementation(c)!.content.concerns}`), ...b.published("ablationImplementation").filter((s) => s.round === round).map((s) => `- 消融格 ${s.target}:${s.content.concerns}`), ]; return lines.length === 0 ? ["(没有)"] : lines; } /** ② 提案:公共材料,加上一轮全部收尾、上一轮实验员的提醒、可以写进"延续"的因子。 */ export function proposal(d: Draft): ChecklistOf<"proposal"> { frame(d, [], () => { const prev = d.round - 1; const wraps = d.board.published("wrapUp").filter((w) => w.round === prev); d.section( "上一轮的收尾", prev < 1 ? ["(这是第 1 轮,没有上一轮)"] : wraps.flatMap((w) => [`### ${w.author} 的收尾`, "", w.content.body, ""]), ); d.section("上一轮实验员实现时的提醒", prev < 1 ? ["(这是第 1 轮,没有上一轮)"] : concernLines(d, prev)); const lines = d.board.protocols().flatMap((p) => p.protocol.factors.map((f, i) => `- ${factorId(p.candidate, factorKey(i))} ${f.name}(关键设定:${f.keySettings};候选 ${p.candidate},基线 ${p.protocol.baseline}${f.continues === null ? "" : `;它自己延续 ${f.continues}`})`), ); d.section('可以写进"延续"的因子', lines.length === 0 ? ["(还没有任何冻结的因子)"] : lines); }); return { kind: "proposal", maxProposals: d.req.study.proposalLimit, ...factorRules(d) }; } /** 一条原提案全文,带作者。 */ function proposalLines(d: Draft, id: string): string[] { const p = d.board.proposal(id); return [`### 原提案 ${id}(作者 ${p.author})`, "", json(p.content)]; } /** 一个候选由哪几条原提案合并而成(判重合并的决定,方法.md 4.3 ③)。 */ function mergeLine(d: Draft, candidate: string): string { const ids = d.board.proposalsOf(candidate); return ids.length === 1 ? `候选 ${candidate} 就是原提案 ${ids[0]}。` : `候选 ${candidate} 由判重合并的 ${ids.join("、")} 合成,作者都保留作者关系。`; } /** 合并说明,后面跟这几条原提案的全文。 */ function candidateProposals(d: Draft, candidate: string): string[] { return [mergeLine(d, candidate), "", ...d.board.proposalsOf(candidate).flatMap((id) => proposalLines(d, id))]; } /** ④ 第一次预测:这个候选的全部原始提案。看不到作者自评(账本里已拆出封存)、看不到别人的预测。 */ export function firstPrediction(d: Draft): ChecklistOf<"firstPrediction"> { const candidate = d.req.object!; frame(d, [], () => d.section("本任务要处理的东西", candidateProposals(d, candidate))); return { kind: "firstPrediction", candidate }; } /** ⑤ 挑刺:被挑的那条原提案全文(用户 2026-10-03 决定 4:按原提案挑)。 */ export function critique(d: Draft): ChecklistOf<"critique"> { const id = d.req.object!; frame(d, [], () => d.section("本任务要处理的东西", proposalLines(d, id))); return { kind: "critique", proposal: id, factors: d.board.proposal(id).content.factors.map((_, i) => factorKey(i)) }; } /** * ⑥ 回应与统一:这个候选的全部原提案、针对它们的全部挑刺(已关的挑刺阶段)。 * 两者材料相同;统一另要逐条体现每份原提案(sources)。 */ function answer(d: Draft): { candidate: string; critiques: string[] } & ReturnType { const candidate = d.req.object!; const ids = d.board.proposalsOf(candidate); const list = d.board.published("critique").filter((c) => ids.includes(c.target!)).sort((a, b) => compareIds(a.item!, b.item!)); frame(d, [], () => { d.section("本任务要处理的东西", candidateProposals(d, candidate)); d.section( "本任务要答复的挑刺", list.length === 0 ? ["(没有针对这个候选的挑刺,replies 交空数组)"] : [PRESUMPTION, "", ...list.flatMap((c) => [`### 挑刺 ${c.item}(${c.author} 挑提案 ${c.target} 的 ${c.content.target})`, "", c.content.body, ""])], ); }); return { candidate, critiques: list.map((c) => c.item!), ...factorRules(d) }; } export const response = (d: Draft): ChecklistOf<"response"> => ({ kind: "response", ...answer(d) }); export function unification(d: Draft): ChecklistOf<"unification"> { const a = answer(d); return { kind: "unification", ...a, sources: d.board.proposalsOf(a.candidate) }; } /** * ⑦ 第二次预测:冻结协议、挑刺与回应按挑刺编号配对、本人第一次给这个候选的三个数(4.3 ⑦)。 * 第一次预测只取本人本轮的;别人的第一次和作者自评不读。留池候选本轮不做第一次预测(4.3 ④)。 */ export function secondPrediction(d: Draft): ChecklistOf<"secondPrediction"> { const candidate = d.req.object!; const first = () => d.board.published("firstPrediction").filter((s) => s.author === d.req.actor && s.target === candidate && s.round === d.round).at(-1)!; const own = [ { heading: "你第一次给这个候选的三个数", lines: d.board.carried(d.round).includes(candidate) ? ["(这是留池候选,本轮没有第一次预测)"] : [`一成 / 一半 / 九成:${rangeText(first().content.range)}(当时只读了原始提案)`, "", "读了冻结协议和挑刺与回应再给一次:该改就改,不该改就照旧,两份都会存档。"], }, ]; frame(d, own, () => { const p = d.board.protocol(candidate); const critiques = d.board.published("critique"); const pairs = p.replies.flatMap((r) => { const c = critiques.find((x) => x.item === r.critique)!; return [`### 挑刺 ${r.critique}(${c.author} 挑的)`, "", c.content.body, "", `作者的答复:${r.reply}`, ""]; }); d.section("本任务要处理的东西", [mergeLine(d, candidate), "", `### 候选 ${candidate} 的冻结协议`, "", json(p)]); d.section("挑刺与回应", pairs.length === 0 ? ["(没有针对这个候选的挑刺)"] : [PRESUMPTION, "", ...pairs]); }); return { kind: "secondPrediction", candidate }; } /** * ⑭ 解释:本轮各候选的运行与分数、实验员的提醒、核验结论、被采纳改法的因子(要给三个数的格)、 * 本轮运行的产物(只放进查询)。本轮正向运行在采纳决定写入后公开。 */ export function explanation(d: Draft): ChecklistOf<"explanation"> { const adopted = d.board.decision("adopt", d.round)!.output.adopted!; const p = d.board.protocol(adopted); frame(d, [], () => { const runs = d.board.runs().filter((r) => r.plan.purpose === "forward" && r.plan.round === d.round && d.board.runPublic(r)); const verifications = d.board.published("verification").filter((v) => v.round === d.round); d.section("本任务要处理的东西", [ "### 本轮正向运行", "", ...runs.flatMap((r) => [...runLines(d, r), `(判分全文用 research_query 查 ${r.plan.run})`, ""]), "### 核验结论", "", ...(verifications.length === 0 ? ["(没有)"] : verifications.map((v) => `- 候选 ${v.target}:${v.content.body}`)), ]); d.section("本轮实验员实现时的提醒", concernLines(d, d.round)); d.section("本轮要给三个数的格", [ `被采纳的改法:候选 ${adopted},完整代码 ${score(d.board.baselineScore(adopted))};本批基线 ${p.baseline} ${score(d.board.baselineScore(p.baseline))}。`, "每条解释对下面每一格各给三个数:只关这一个因子、其余因子和保障照完整代码保留时,那次运行的最终指标(按分数原值)。", "", ...factorLines(p), ]); const artifacts = d.req.artifacts!; const listed = Object.keys(artifacts) .sort(compareIds) .flatMap((run) => Object.entries(artifacts[run]!).map(([file, text]) => `- ${d.record(`${run}/${file}`, `运行 ${run} 的产物 ${file}`, text)}`)); d.section("本轮运行的产物", ["用 research_query 按编号查全文:", ...(listed.length === 0 ? ["(没有)"] : listed)]); }); return { kind: "explanation", cells: p.factors.map((_, i) => factorKey(i)), maxExplanations: d.req.study.proposalLimit }; } /** ⑱ 收尾:本轮的采纳、选格(结算与押的因子在信念本里),实验员的提醒。 */ export function wrapUp(d: Draft): ChecklistOf<"wrapUp"> { frame(d, [], () => { const adopt = d.board.decision("adopt", d.round)!.output; const cells = d.board.decision("cells", d.round); d.section("本任务要处理的东西", [ adopt.adopted === null ? `本轮没有采纳:最好的有效候选 ${adopt.best === null ? "没有" : `${adopt.best.id},改进 ${score(adopt.best.improvement)}`},采纳线 ${score(adopt.line)}。` : `本轮采纳候选 ${adopt.adopted},改进 ${score(adopt.best!.improvement)},采纳线 ${score(adopt.line)}。`, cells === undefined ? "本轮没有选格。" : `本轮选格:测 ${cells.output.chosen.length === 0 ? "(一格都不测)" : cells.output.chosen.join("、")},净值 ${num(cells.output.chosenNet)}。`, '消融结算、各格结论和押的因子见"信念本"。', ]); d.section("本轮实验员实现时的提醒", concernLines(d, d.round)); }); return { kind: "wrapUp" }; }