Skip to content

Commit e05296c

Browse files
Add the cross-wiki move mapping for every boss
research/ground_truth/semantic/wiki_mapping/ lines up each product move with the Fextralife and Fandom entries that describe it and records how they relate. Across 106 product moves, 52 match one-to-one between the two wikis. scripts/wiki_mapping_report.py rebuilds the readable report and checks that every product move is mapped exactly once. CLAUDE.md and both ROADMAPs now state that the project owner decides what counts as ground truth, and document move-level sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 1bc5564 commit e05296c

14 files changed

Lines changed: 2362 additions & 0 deletions

‎CLAUDE.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,21 @@ Do not bulk-add every remaining Sekiro boss from Wiki data solely to finish a ch
494494

495495
---
496496

497+
## Progress
498+
499+
The existing content was reviewed against both Fextralife and Fandom, and the user decided each item. Done on `dev`:
500+
501+
- fixed: True Corrupted Monk phase 2 keeps her phase-1 moves; Guardian Ape gains the phase-2 Jumping Sweep; misattributed or misleading text corrected; Owl (Father)'s Shadowfall named as the combat art 秘传·巨型忍者落杀
502+
- the Isshin fight starts with a Genichiro, Way of Tomoe phase (only the moves both wikis agree on); Isshin's own phases are now 2-4, and existing Isshin attempts were moved up one phase by a migration
503+
- actions that cannot end an attempt were removed from the movesets (Owl's zig-zag, Guardian Ape's roar, Lady Butterfly's rafter jump, Great Shinobi Owl's Shinobi Charm)
504+
- both Owls gained Mikiri Counter, from Fandom, using the new move-level source fields
505+
- move types made consistent
506+
- the cross-wiki mapping for every boss is in `research/ground_truth/semantic/wiki_mapping/`; it records what both wikis say, including what the product does not use
507+
508+
Still open: whether Lady Butterfly and Guardian Ape adopt Fandom's more specific move lists, or keep their current lists until V4 rebuilds them.
509+
510+
---
511+
497512
# 13. Wiki Data Policy
498513

499514
Wiki sources are useful for:
@@ -674,6 +689,8 @@ Semantic Move
674689

675690
No single source should automatically override all others.
676691

692+
**The project owner decides what is ground truth.** Claude and any tooling gather evidence, compare sources and propose, stating their uncertainty. They never mark a semantic move, an engine mapping or a phase structure as verified on their own.
693+
677694
---
678695

679696
# 20. Rulesets
@@ -749,6 +766,8 @@ scripts/
749766
→ extraction / validation utilities
750767
```
751768

769+
`research/ground_truth/` already exists. V3 Milestone 5 added `semantic/wiki_mapping/`: one JSON file per boss that lines up product moves with Fextralife and Fandom entries, plus `scripts/wiki_mapping_report.py`, which rebuilds the readable report and checks that every product move is mapped exactly once. Use it as the starting candidate list for the semantic catalog and as the baseline for measuring the gap to engine-level ground truth.
770+
752771
---
753772

754773
# 22. Copyright / Repository Rule
@@ -1018,6 +1037,8 @@ multiple gameplay examples
10181037

10191038
Do not automatically treat a Wiki description as `high`.
10201039

1040+
Confidence values are proposals until the project owner confirms them.
1041+
10211042
---
10221043

10231044
# 31. V4 Milestone 5 — Completeness Review
@@ -1374,6 +1395,10 @@ If a semantic move needs to be removed:
13741395
3. migrate deliberately
13751396
4. only then remove or hide the move
13761397

1398+
Sources are recorded per boss (`bosses.source_name`, `bosses.source_url`). When a move's data comes from a different page, set that move's own `source_name` and `source_url`; leave them null otherwise. The frontend shows a move-level source under the move.
1399+
1400+
When the product's moves change, update `research/ground_truth/semantic/wiki_mapping/` and run `python research/ground_truth/scripts/wiki_mapping_report.py`.
1401+
13771402
---
13781403

13791404
# 46. API Compatibility

‎ROADMAP.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ do not force completeness
223223

224224
It may remain explicitly uncertain or be deferred to V4 ground-truth research.
225225

226+
This milestone also records a per-boss mapping between the product's moves and the Fextralife and Fandom entries (`research/ground_truth/semantic/wiki_mapping/`). It measures how far the two wikis disagree, which is a lower bound on the gap to ground truth, and it becomes the starting point for V4.
227+
226228
### Important Boundary
227229

228230
V3 does **not** require:
@@ -379,6 +381,8 @@ Wiki and community documentation remain useful for:
379381

380382
They are not treated as final completeness authorities.
381383

384+
The project owner makes the final decision on what counts as ground truth. Tooling and assistants gather evidence and propose; they do not mark anything as verified on their own.
385+
382386
---
383387

384388
# V4 Data Layers

‎ROADMAP.zh-CN.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ Boss 内容的翻译必须保留原始资料的含义。
223223

224224
它可以保持明确的"不确定"状态,或者留给 V4 的真实数据研究。
225225

226+
这个里程碑还会为每个 Boss 记录一份产品招式与 Fextralife、Fandom 条目之间的对照表(`research/ground_truth/semantic/wiki_mapping/`)。它衡量两个 Wiki 之间的分歧有多大,这是和真实数据之间差距的下限;它也是 V4 工作的起点。
227+
226228
### 重要边界
227229

228230
V3 **不**要求:
@@ -377,6 +379,8 @@ Wiki 和社区资料仍然适用于:
377379

378380
但它们不被当作完整性的最终依据。
379381

382+
哪些内容最终算作真实数据,由项目负责人决定。工具和 AI 助手只负责收集证据、提出建议,不会自行把任何内容标记为已验证。
383+
380384
---
381385

382386
# V4 数据层

‎research/ground_truth/README.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Ground truth research
2+
3+
Workspace for V4, Combat Ground Truth. See ROADMAP.md and CLAUDE.md (sections 18–36) for the plan.
4+
5+
It is kept separate from the production application: nothing here is read by the backend or the frontend, and nothing here changes the production database.
6+
7+
## Rules
8+
9+
- **The project owner decides what is ground truth.** Tools and Claude gather evidence, compare sources and propose; they never mark a semantic move, an engine mapping or a phase structure as verified.
10+
- Every observation carries its ruleset (`vanilla`, `resurrection`).
11+
- Do not commit proprietary game assets: game binaries, extracted archives, animation bundles, models or textures. Commit scripts, metadata, mappings, identifiers, hashes and annotations only.
12+
13+
## Contents
14+
15+
| Path | What it holds |
16+
|---|---|
17+
| `semantic/wiki_mapping/` | Per-boss mapping between product moves, Fextralife entries and Fandom entries, made during V3 M5. It is the starting candidate list for the V4 semantic catalog and the baseline for measuring the gap to engine-level ground truth. |
18+
| `scripts/wiki_mapping_report.py` | Rebuilds `semantic/wiki_mapping/README.md` from the JSON files, and checks that every product move is mapped exactly once (`--check`). |
19+
20+
The remaining folders from the roadmap (`rulesets/`, `characters/`, `engine/`, `observations/`, `annotations/`) are added when V4 work needs them.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""Build the readable report for the cross-wiki move mapping, and check it against the product.
2+
3+
python research/ground_truth/scripts/wiki_mapping_report.py # rewrite the report
4+
python research/ground_truth/scripts/wiki_mapping_report.py --check # only verify, exit 1 on problems
5+
6+
The mapping files (semantic/wiki_mapping/<boss>.json) are the source of truth. The check
7+
fails when a product move in backend/seed/bosses.json is missing from the mapping, mapped
8+
twice, or no longer exists, so the mapping cannot silently drift from the product.
9+
"""
10+
11+
import json
12+
import sys
13+
from collections import Counter
14+
from pathlib import Path
15+
16+
ROOT = Path(__file__).resolve().parents[3]
17+
MAPPING_DIR = ROOT / "research" / "ground_truth" / "semantic" / "wiki_mapping"
18+
REPORT = MAPPING_DIR / "README.md"
19+
SEED = ROOT / "backend" / "seed" / "bosses.json"
20+
21+
RELATIONS = {
22+
"same": "Both wikis describe the same action with the same boundaries.",
23+
"partial": "Overlapping, but details differ (hit count, follow-ups, conditions).",
24+
"split": "Divided differently: one entry on one side covers several on the other.",
25+
"conflict": "The wikis contradict each other.",
26+
"unclear": "No confident correspondence.",
27+
"only_fextralife": "Only Fextralife describes it.",
28+
"only_fandom": "Only Fandom describes it.",
29+
}
30+
V4_STATUSES = {"unverified"}
31+
32+
33+
def load_mappings() -> list[dict]:
34+
return [json.loads(p.read_text(encoding="utf-8")) for p in sorted(MAPPING_DIR.glob("*.json"))]
35+
36+
37+
def check(mappings: list[dict]) -> list[str]:
38+
seed = json.loads(SEED.read_text(encoding="utf-8"))
39+
product = {b["id"]: {m["id"] for p in b["phases"] for m in p["moves"]} for b in seed}
40+
problems = []
41+
for boss_id in product.keys() - {m["boss"] for m in mappings}:
42+
problems.append(f"{boss_id}: no mapping file")
43+
for mapping in mappings:
44+
boss_id = mapping["boss"]
45+
mapped = Counter(move for entry in mapping["entries"] for move in entry["product_moves"])
46+
for move, count in mapped.items():
47+
if count > 1:
48+
problems.append(f"{boss_id}/{move}: mapped {count} times")
49+
for move in product.get(boss_id, set()) - mapped.keys():
50+
problems.append(f"{boss_id}/{move}: product move missing from the mapping")
51+
for move in mapped.keys() - product.get(boss_id, set()):
52+
problems.append(f"{boss_id}/{move}: mapped but not in the product")
53+
for entry in mapping["entries"]:
54+
if entry["relation"] not in RELATIONS:
55+
problems.append(f"{boss_id}: unknown relation {entry['relation']!r}")
56+
if entry["v4_status"] not in V4_STATUSES:
57+
# Anything beyond "unverified" must be decided by the project owner, not by tooling.
58+
problems.append(f"{boss_id}: unexpected v4_status {entry['v4_status']!r}")
59+
return problems
60+
61+
62+
def cell(items: list[str]) -> str:
63+
return "<br>".join(f"`{i}`" if " " not in i and i.islower() else i for i in items) or "—"
64+
65+
66+
def report(mappings: list[dict]) -> str:
67+
lines = [
68+
"# Cross-wiki move mapping",
69+
"",
70+
"Generated by `research/ground_truth/scripts/wiki_mapping_report.py` from the JSON files in this folder. Do not edit by hand.",
71+
"",
72+
"For every boss in the product, each row lines up a product move with the Fextralife and Fandom entries that describe it, "
73+
"and records how they relate. It measures how much two independent human sources disagree, which is a **lower bound** on the "
74+
"gap between the product's movesets and engine-level ground truth.",
75+
"",
76+
"**Nothing here is ground truth.** Every row is `unverified`. V4 adds runtime and engine evidence to `engine_evidence`; "
77+
"which semantic moves and mappings become ground truth is decided by the project owner, never by tooling or by Claude.",
78+
"",
79+
"## Relations",
80+
"",
81+
"| Relation | Meaning |",
82+
"|---|---|",
83+
*[f"| `{name}` | {meaning} |" for name, meaning in RELATIONS.items()],
84+
"",
85+
"## Summary",
86+
"",
87+
"| Boss | Product moves | Rows | same | partial | split | conflict | unclear | only Fextralife | only Fandom | Product moves matched 1:1 |",
88+
"|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|",
89+
]
90+
totals = Counter()
91+
for m in mappings:
92+
rel = Counter(e["relation"] for e in m["entries"])
93+
product_moves = sum(len(e["product_moves"]) for e in m["entries"])
94+
matched = sum(len(e["product_moves"]) for e in m["entries"] if e["relation"] == "same")
95+
totals.update(rel)
96+
totals["product"] += product_moves
97+
totals["rows"] += len(m["entries"])
98+
totals["matched"] += matched
99+
lines.append(
100+
f"| {m['boss']} | {product_moves} | {len(m['entries'])} | "
101+
+ " | ".join(str(rel[r]) for r in RELATIONS)
102+
+ f" | {matched}/{product_moves} |"
103+
)
104+
lines.append(
105+
f"| **Total** | **{totals['product']}** | **{totals['rows']}** | "
106+
+ " | ".join(f"**{totals[r]}**" for r in RELATIONS)
107+
+ f" | **{totals['matched']}/{totals['product']}** |"
108+
)
109+
110+
for m in mappings:
111+
fx, fd = m["sources"]["fextralife"], m["sources"]["fandom"]
112+
lines += [
113+
"",
114+
f"## {m['boss']}",
115+
"",
116+
f"- Ruleset: {m['ruleset']}. Product data originally from: {m['product_source']}.",
117+
f"- Fextralife: <{fx['url']}> ({fx['version']}, fetched {fx['fetched']})",
118+
f"- Fandom: <{fd['url']}> ({fd['version']}, fetched {fd['fetched']})",
119+
*[f"- {note}" for note in m["notes"]],
120+
"",
121+
"| Product moves | Fextralife | Fandom | Relation | Notes |",
122+
"|---|---|---|---|---|",
123+
]
124+
for e in m["entries"]:
125+
notes = e["notes"].replace("|", "/") or ""
126+
lines.append(
127+
f"| {cell(e['product_moves'])} | {cell(e['fextralife'])} | {cell(e['fandom'])} | `{e['relation']}` | {notes} |"
128+
)
129+
return "\n".join(lines) + "\n"
130+
131+
132+
def main(argv: list[str]) -> int:
133+
mappings = load_mappings()
134+
problems = check(mappings)
135+
for problem in problems:
136+
print(problem, file=sys.stderr)
137+
if problems:
138+
return 1
139+
if "--check" not in argv:
140+
REPORT.write_text(report(mappings), encoding="utf-8", newline="\n")
141+
print(f"wrote {REPORT.relative_to(ROOT)}")
142+
return 0
143+
144+
145+
if __name__ == "__main__":
146+
sys.exit(main(sys.argv[1:]))

0 commit comments

Comments
 (0)