STEP: 1538 / Test: 43/43 PASS / 姉妹: puzzle-verifier (STEP 1536) / Type: 推理探偵コネクタ (端子/チップ/回路/マシン)
❶ パズル側 (STEP 1536) と姉妹、 回路の向きが逆: puzzle は生成器が解を先に持ち fields を hide、 mystery は事実 → 結論への推論連鎖そのものが回路。 生成器 (マシン) は犯人を先に決め、 逆算で手掛かりを配置。
❷ 検証器 2 条件: (a) uniqueness = 犯人一意 (fair-play も同時に拾う) / (b) necessity = 冗長な手掛かりが無い (推理が骨抜きにならない)。 両方満たしたものだけ 「well-posed」 として出題可能。
❸ narrative layer を必ず挟む (藤本さん warning への直接応答): raw chip output は味気ないので、 「火災の14分前に通報した人物がいた」 型の物語断片に prose 化。 renderNarrative(report) で 事件初期提示 → 推理展開 → 結論の 3 節を Markdown で出力。
❹ anti-repetition (Jaccard): 直前 N 事件の chip 組合せと Jaccard 類似度を計算、 threshold 超えは 「またこのパターンか」 予防のため 別 chip set に振り直す指標を返す。
| 藤本さん指定 | 本 module | 役割 |
|---|---|---|
| 端子 (terminal) | Fact { type: FactType } | 時刻 / 人物 / 場所 / 証言 / 記録 の 5 型 |
| チップ (chip) | ReasoningChip (5 個) | contradiction / timeline / alibi / elimination / common_feature |
| 回路 (circuit) | runCircuit(spec) | 初期 fact から犯人確定への推論連鎖 (chip の順次適用) |
| マシン (event generator) | generateMystery(suspects, truth, chips) | 真相を先に決め 逆算で fact を配置、 冗長 fact を iterative trim |
| 検証器 | verifyMystery(spec) | uniqueness (犯人一意) + necessity (冗長ゼロ) の 2 条件検証 |
| narrative layer | renderNarrative(report) | 回路 → 物語断片への prose 化 (味気ない chip output 対策) |
| outcome | 意味 | 診断 |
|---|---|---|
UNIQUE_CORRECT | ちょうど 1 人残り、 expectedCulprit と一致 | well-posed 候補 (necessity 次第) |
UNIQUE_WRONG | 1 人残るが expected と不一致 | 生成器バグ (逆算式が誤り) |
MULTIPLE | >1 人残る | 手掛かり不足 or fair-play violation (後出しがなければ絞れる想定が崩れた) |
NONE | 全員消去 | 事実が矛盾しているか消去法が過剰 |
for (const f of providedFacts) {
const trimmed = { ...spec, providedFacts: providedFacts.filter(x => x.id !== f.id) };
const report = verifyMystery(trimmed);
if (report.uniqueness.outcome === 'UNIQUE_CORRECT') {
redundantFactIds.push(f.id); // この fact なしでも一意 → 冗長
}
}
各 fact を一つずつ抜いて全体を再検証。 抜いても一意性が保たれる fact は「無くても解ける」 = 冗長。 藤本さん指摘 「余分な手掛かりが無いか」 の機械的判定。
const suspects = [
{ id: 'A', name: '田中', attributes: { leftHanded: false, mustHaveBeenAt: 'bar', mustHaveBeenTime: 14 } },
{ id: 'B', name: '佐藤', attributes: { leftHanded: true } },
{ id: 'C', name: '木村', attributes: { leftHanded: true, mustHaveBeenAt: 'station', mustHaveBeenTime: 14 } },
];
const spec = {
suspects,
providedFacts: [
{ id: 'f1', type: 'person', subject: 'CULPRIT', predicate: 'has_attribute',
value: { attribute: 'leftHanded', polarity: true } }, // 田中を消去 (右利き)
{ id: 'f2', type: 'record', subject: 'station', predicate: 'captured_at',
value: { medium: '監視カメラ', captured: 'C', time: 14 } }, // 木村のアリバイ成立
],
expectedCulprit: 'B',
chips: ['alibi', 'elimination'],
title: '書斎の事件',
};
const report = verifyMystery(spec);
// report.uniqueness.outcome === 'UNIQUE_CORRECT'
// report.necessity.outcome === 'ALL_NECESSARY'
// report.wellPosed === true
console.log(renderNarrative(report));
# 書斎の事件 ## 事件の初期提示 - 犯人 は 左利きである。 - station の 監視カメラ に 14 時点で C が記録されていた。 ## 推理の展開 - 木村 は 14 時に station にいたことが 監視カメラ に映っており、 アリバイが成立し容疑から外れる。 - 犯人は左利きのはずだが、 田中 は該当しないため除外される。 ## 結論 犯人は **佐藤** で確定した。 すべての手掛かりが結論に不可欠であり、 冗長な情報は無い。
const truth = {
culpritId: 'B',
time: 14,
crimeScene: 'study',
distinguishingAttributes: [{ attribute: 'leftHanded', polarity: true }],
};
const { spec, report, repetitionScore } = generateMystery(
suspects, truth, ['alibi', 'elimination'],
{ recentChipSets: [['alibi', 'elimination'], ['contradiction', 'timeline']] },
);
// report.wellPosed === true (by construction, iterative trim 適用済み)
// repetitionScore = max Jaccard against recentChipSets → 1.0 (完全一致 = 別 chip set 推奨)
| Section | Test | Assertions |
|---|---|---|
| 1 | Basic 3-suspect well-posed | 4 |
| 2 | MULTIPLE — fair-play violation (missing clue) | 3 |
| 3 | REDUNDANT_FACTS — 冗長 fact 検出 | 4 |
| 4 | NONE — over-elimination | 2 |
| 5 | UNIQUE_WRONG — 生成器バグ検出 | 2 |
| 6 | narrative fragments (renderNarrative) | 5 |
| 7 | Backward machine (well-posed by construction) | 4 |
| 8 | Anti-repetition (Jaccard) | 3 |
| 9 | contradiction chip narrative-only (no elimination in v0.1) | 4 |
| 10 | common_feature AND-summary narrative | 2 |
| 11 | runCircuit direct API | 2 |
| 12 | Single-suspect edge case | 2 |
| 13 | Invalid: expectedCulprit not in suspects | 1 |
| 14 | fair-play condition (missing key fact detected) | 2 |
| 15 | Narrative fallback (no fragments) | 1 |
| 16 | Chip ordering independence | 2 |
| 次元 | puzzle-verifier | mystery-reasoning-connector (本) |
|---|---|---|
| 回路の向き | 解 → hide → solver enumerate | 事実 → chip → 結論 (forward reasoning) |
| 生成器 | tool + partial input + hidden fields | 真相 (culprit + attributes) → 逆算 fact 配置 |
| 検証器 (a) | UNIQUE (hidden field の完成が一意) | UNIQUE_CORRECT (残 suspect が犯人と一致) |
| 検証器 (b) | — | ALL_NECESSARY (各 fact を抜くと一意崩れる) |
| narrative layer | — | 回路 → 物語断片 (5 chip × 名詞句 template) |
| anti-repetition | — | Jaccard against recentChipSets |
renderNarrative で prose 化。 各 chip が narrativeFragments を返す設計。generateMystery の recentChipSets option で Jaccard 類似度を返し、 生成側で別 chip set 選択を促す。