From fe0d20ab8e8a31814fdaca3c7022299de02002b7 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:33:59 +0900 Subject: [PATCH 01/54] =?UTF-8?q?Add=20Pok=C3=A9mon=20Champions=20audit=20?= =?UTF-8?q?workflow=20scaffold?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pokemon-audit.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/pokemon-audit.yml diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml new file mode 100644 index 0000000..1c30097 --- /dev/null +++ b/.github/workflows/pokemon-audit.yml @@ -0,0 +1,22 @@ +name: pokemon-audit + +on: + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + audit: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Smoke + run: | + mkdir -p out + echo ok > out/smoke.txt + - uses: actions/upload-artifact@v4 + with: + name: pokemon-audit-smoke + path: out/ + if-no-files-found: error From 091a513875e34571cdac7b9592f2b2c6a20e7c4e Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:35:16 +0900 Subject: [PATCH 02/54] Add OP.GG tier scraper diagnostic --- pokemon_audit/scrape_debug.py | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 pokemon_audit/scrape_debug.py diff --git a/pokemon_audit/scrape_debug.py b/pokemon_audit/scrape_debug.py new file mode 100644 index 0000000..9728246 --- /dev/null +++ b/pokemon_audit/scrape_debug.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import json +import re +from pathlib import Path + +import requests +from bs4 import BeautifulSoup + +OUT = Path('out') +OUT.mkdir(exist_ok=True) +URL = 'https://op.gg/ja/pokemon-champions/tier' +HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/150 Safari/537.36', + 'Accept-Language': 'ja,en;q=0.7', + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache', +} + +r = requests.get(URL, headers=HEADERS, timeout=90) +r.raise_for_status() +(OUT / 'tier.html').write_bytes(r.content) + +soup = BeautifulSoup(r.text, 'lxml') +anchors = [] +for a in soup.find_all('a', href=True): + href = str(a.get('href')) + if '/pokemon-champions/pokedex/' not in href: + continue + text = a.get_text(' ', strip=True) + alts = [str(img.get('alt', '')).strip() for img in a.find_all('img')] + anchors.append({'href': href, 'text': text, 'alts': alts, 'html': str(a)[:1000]}) + +text = soup.get_text(' ', strip=True) +timestamps = re.findall(r'2026[^\n]{0,80}?\d{1,2}:\d{2}', text) +summary = { + 'status': r.status_code, + 'bytes': len(r.content), + 'title': soup.title.get_text(' ', strip=True) if soup.title else '', + 'anchor_count': len(anchors), + 'anchors_first_120': anchors[:120], + 'timestamps': timestamps[:20], + 'text_prefix': text[:5000], +} +(OUT / 'debug.json').write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding='utf-8') +print(json.dumps({'status': r.status_code, 'bytes': len(r.content), 'anchor_count': len(anchors), 'timestamps': timestamps[:5]}, ensure_ascii=False)) From 10582a891fa1382599c47f0d15b288256fa680cd Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:35:41 +0900 Subject: [PATCH 03/54] Run OP.GG scraper diagnostic in Actions --- .github/workflows/pokemon-audit.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index 1c30097..37f2ef8 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -11,12 +11,15 @@ jobs: timeout-minutes: 30 steps: - uses: actions/checkout@v4 - - name: Smoke - run: | - mkdir -p out - echo ok > out/smoke.txt + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install scraper dependencies + run: python -m pip install --disable-pip-version-check requests beautifulsoup4 lxml + - name: Fetch and inspect OP.GG tier page + run: python pokemon_audit/scrape_debug.py - uses: actions/upload-artifact@v4 with: - name: pokemon-audit-smoke + name: pokemon-audit-debug path: out/ if-no-files-found: error From 6e2532898f259ef22fd260d2afdca2245c516f6c Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:37:12 +0900 Subject: [PATCH 04/54] Add OP.GG individual page diagnostic --- pokemon_audit/detail_debug.py | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 pokemon_audit/detail_debug.py diff --git a/pokemon_audit/detail_debug.py b/pokemon_audit/detail_debug.py new file mode 100644 index 0000000..736682c --- /dev/null +++ b/pokemon_audit/detail_debug.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +import json +import re +from pathlib import Path + +import requests +from bs4 import BeautifulSoup + +OUT = Path('out') +OUT.mkdir(exist_ok=True) +URLS = { + 'ja': 'https://op.gg/ja/pokemon-champions/pokedex/garchomp', + 'en': 'https://op.gg/en/pokemon-champions/pokedex/garchomp', +} +HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/150 Safari/537.36', + 'Accept-Language': 'en,ja;q=0.7', + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache', +} + +for lang, url in URLS.items(): + r = requests.get(url, headers=HEADERS, timeout=90) + r.raise_for_status() + (OUT / f'garchomp_{lang}.html').write_bytes(r.content) + soup = BeautifulSoup(r.text, 'lxml') + tables = [] + for idx, table in enumerate(soup.find_all('table')): + headings = [] + for prev in table.find_all_previous(['h1','h2','h3','h4','h5','h6','strong'], limit=8): + txt = prev.get_text(' ', strip=True) + if txt and txt not in headings: + headings.append(txt) + rows = [] + for tr in table.find_all('tr')[:12]: + cells = [x.get_text(' ', strip=True) for x in tr.find_all(['th','td'])] + alts = [img.get('alt','') for img in tr.find_all('img')] + hrefs = [a.get('href','') for a in tr.find_all('a', href=True)] + if cells or alts or hrefs: + rows.append({'cells': cells, 'alts': alts, 'hrefs': hrefs}) + tables.append({'index': idx, 'headings': headings, 'rows': rows, 'html_prefix': str(table)[:3000]}) + text = soup.get_text(' ', strip=True) + report = { + 'url': url, + 'status': r.status_code, + 'bytes': len(r.content), + 'title': soup.title.get_text(' ', strip=True) if soup.title else '', + 'tables': tables, + 'table_count': len(tables), + 'timestamps': re.findall(r'20\d{2}[/-]\d{1,2}[/-]\d{1,2}[^0-9]{0,10}\d{1,2}:\d{2}', text)[:20], + 'text_excerpt': text[0:20000], + } + (OUT / f'garchomp_{lang}_debug.json').write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding='utf-8') + print(lang, r.status_code, len(r.content), len(tables)) From d2d94ff5922fb1eed9fa3d8cf596b662532ab3df Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:37:28 +0900 Subject: [PATCH 05/54] Inspect OP.GG ranking and individual page HTML --- .github/workflows/pokemon-audit.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index 37f2ef8..5a62c59 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -16,8 +16,10 @@ jobs: python-version: '3.12' - name: Install scraper dependencies run: python -m pip install --disable-pip-version-check requests beautifulsoup4 lxml - - name: Fetch and inspect OP.GG tier page - run: python pokemon_audit/scrape_debug.py + - name: Fetch and inspect OP.GG pages + run: | + python pokemon_audit/scrape_debug.py + python pokemon_audit/detail_debug.py - uses: actions/upload-artifact@v4 with: name: pokemon-audit-debug From c88c792e924f3e93f14aba1df7054638b274f6b2 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:49:01 +0900 Subject: [PATCH 06/54] Add Next.js RSC extraction helpers for formal OP.GG snapshot --- pokemon_audit/rsc_utils.py | 86 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 pokemon_audit/rsc_utils.py diff --git a/pokemon_audit/rsc_utils.py b/pokemon_audit/rsc_utils.py new file mode 100644 index 0000000..22d51bf --- /dev/null +++ b/pokemon_audit/rsc_utils.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +import json +import re +from datetime import datetime +from typing import Any + +_RSC_RE = re.compile(r'self\.__next_f\.push\(\[1,("(?:\\.|[^"\\])*")\]\)') + + +def decode_rsc_text(html: str) -> str: + chunks: list[str] = [] + for match in _RSC_RE.finditer(html): + try: + chunks.append(json.loads(match.group(1))) + except json.JSONDecodeError: + continue + if not chunks: + raise ValueError("Next.js RSC payload not found") + return "".join(chunks) + + +def extract_json_after_key(text: str, key: str, start_at: int = 0) -> Any: + needle = f'"{key}"' + idx = text.find(needle, start_at) + if idx < 0: + raise KeyError(key) + colon = text.find(":", idx + len(needle)) + if colon < 0: + raise ValueError(f"missing colon after {key}") + pos = colon + 1 + while pos < len(text) and text[pos].isspace(): + pos += 1 + if pos >= len(text): + raise ValueError(f"missing value after {key}") + opener = text[pos] + if opener not in "[{": + return json.JSONDecoder().raw_decode(text[pos:])[0] + closer = "}" if opener == "{" else "]" + depth = 0 + in_string = False + escaped = False + for i in range(pos, len(text)): + ch = text[i] + if in_string: + if escaped: + escaped = False + elif ch == "\\": + escaped = True + elif ch == '"': + in_string = False + continue + if ch == '"': + in_string = True + elif ch == opener: + depth += 1 + elif ch == closer: + depth -= 1 + if depth == 0: + return json.loads(text[pos : i + 1]) + raise ValueError(f"unterminated JSON value for {key}") + + +def extract_string_after_key(text: str, key: str, start_at: int = 0) -> str: + needle = f'"{key}"' + idx = text.find(needle, start_at) + if idx < 0: + raise KeyError(key) + colon = text.find(":", idx + len(needle)) + if colon < 0: + raise ValueError(f"missing colon after {key}") + pos = colon + 1 + while pos < len(text) and text[pos].isspace(): + pos += 1 + value, _ = json.JSONDecoder().raw_decode(text[pos:]) + if not isinstance(value, str): + raise TypeError(f"{key} is not a string") + return value + + +def parse_en_timestamp(value: str) -> datetime: + return datetime.strptime(value.strip(), "%b %d, %Y, %I:%M %p") + + +def parse_ja_timestamp(value: str) -> datetime: + return datetime.strptime(value.strip(), "%Y年%m月%d日 %H:%M") From d273f4566ee67c094cf1978764a601fe8c99cf02 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:50:15 +0900 Subject: [PATCH 07/54] Add synchronized OP.GG top-100 snapshot collector --- pokemon_audit/fetch_snapshot.py | 237 ++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 pokemon_audit/fetch_snapshot.py diff --git a/pokemon_audit/fetch_snapshot.py b/pokemon_audit/fetch_snapshot.py new file mode 100644 index 0000000..db15fc8 --- /dev/null +++ b/pokemon_audit/fetch_snapshot.py @@ -0,0 +1,237 @@ +from __future__ import annotations + +import concurrent.futures +import csv +import hashlib +import json +import random +import re +import shutil +import time +from datetime import datetime, timezone, timedelta +from pathlib import Path + +import requests +from bs4 import BeautifulSoup + +from rsc_utils import decode_rsc_text, extract_json_after_key, extract_string_after_key, parse_en_timestamp, parse_ja_timestamp + +BASE = Path('/mnt/data') +ROOT = BASE / 'formal_inputs' / 'formal_snapshot' +TIER_URL = 'https://op.gg/ja/pokemon-champions/tier' +EN_DETAIL_BASE = 'https://op.gg/en/pokemon-champions/pokedex/' +HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/150 Safari/537.36', + 'Accept-Language': 'ja,en;q=0.8', + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache', +} +JST = timezone(timedelta(hours=9)) +JA_TS_RE = re.compile(r'20\d{2}年\d{1,2}月\d{1,2}日\s+\d{1,2}:\d{2}') +RANK_RE = re.compile(r'#\s*(\d{1,3})') + + +def sha256_bytes(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def get(url: str, attempts: int = 7) -> requests.Response: + last: Exception | None = None + for attempt in range(attempts): + try: + response = requests.get(url, headers=HEADERS, timeout=90) + if response.status_code == 200 and len(response.content) > 20_000: + return response + last = RuntimeError(f'HTTP {response.status_code}, {len(response.content)} bytes') + except Exception as exc: + last = exc + time.sleep(min(25.0, 1.4 * (2 ** attempt)) + random.random()) + raise RuntimeError(f'fetch failed: {url}: {last}') + + +def parse_tier(html: str) -> tuple[str, list[dict[str, object]]]: + soup = BeautifulSoup(html, 'lxml') + text = soup.get_text(' ', strip=True) + stamps = JA_TS_RE.findall(text) + if not stamps: + raise RuntimeError('tier timestamp not found') + timestamp = stamps[0] + rows_by_rank: dict[int, dict[str, object]] = {} + for anchor in soup.find_all('a', href=True): + href = str(anchor.get('href') or '') + if '/ja/pokemon-champions/pokedex/' not in href: + continue + match = RANK_RE.search(anchor.get_text(' ', strip=True)) + if not match: + continue + rank = int(match.group(1)) + if rank < 1 or rank > 100: + continue + slug = href.rstrip('/').split('/')[-1] + images = anchor.find_all('img') + name_ja = '' + image_url = '' + for image in images: + alt = str(image.get('alt') or '').strip() + src = str(image.get('src') or '').strip() + if alt and not name_ja: + name_ja = alt + if src and 'pokemon/images/pokemon/' in src and not image_url: + image_url = src.replace('&', '&') + if not name_ja: + spans = [x.get_text(' ', strip=True) for x in anchor.find_all('span')] + candidates = [x for x in spans if x and not x.startswith('#') and x not in {'=', '↑', '↓'}] + name_ja = candidates[0] if candidates else slug + row = { + 'rank': rank, + 'slug': slug, + 'name_ja': name_ja, + 'detail_url_en': EN_DETAIL_BASE + slug, + 'image_url': image_url, + } + existing = rows_by_rank.get(rank) + if existing and existing['slug'] != slug: + raise RuntimeError(f'duplicate rank {rank}: {existing["slug"]} vs {slug}') + rows_by_rank[rank] = row + if sorted(rows_by_rank) != list(range(1, 101)): + missing = sorted(set(range(1, 101)) - set(rows_by_rank)) + raise RuntimeError(f'top100 incomplete: {len(rows_by_rank)} rows, missing={missing}') + return timestamp, [rows_by_rank[i] for i in range(1, 101)] + + +def detail_metadata(html: str) -> dict[str, object]: + rsc = decode_rsc_text(html) + updated_at = extract_string_after_key(rsc, 'updatedAt') + single_detail = extract_json_after_key(rsc, 'singleDetail') + lookup_data = extract_json_after_key(rsc, 'lookupData') + pokemon = single_detail.get('pokemon') or {} + lookup_pokemon = lookup_data.get('pokemon') or [] + target = next((item for item in lookup_pokemon if item.get('id') == pokemon.get('id') and item.get('form', 0) == pokemon.get('form', 0)), None) + if target is None: + target = next((item for item in lookup_pokemon if item.get('key') == pokemon.get('key')), None) + return { + 'updated_at_en': updated_at, + 'pokemon_id': pokemon.get('id'), + 'pokemon_form': pokemon.get('form', 0), + 'pokemon_key': pokemon.get('key'), + 'name_en': (target or {}).get('name', ''), + 'types': '|'.join((target or {}).get('types') or []), + 'single_detail_sha256': hashlib.sha256(json.dumps(single_detail, sort_keys=True, separators=(',', ':')).encode()).hexdigest(), + } + + +def write_csv(path: Path, rows: list[dict[str, object]]) -> None: + if not rows: + raise ValueError(f'no rows for {path}') + with path.open('w', encoding='utf-8-sig', newline='') as handle: + writer = csv.DictWriter(handle, fieldnames=list(rows[0])) + writer.writeheader() + writer.writerows(rows) + + +def run_attempt(attempt: int) -> dict[str, object]: + work = BASE / 'formal_inputs' / f'.snapshot_attempt_{attempt}' + if work.exists(): + shutil.rmtree(work) + details_dir = work / 'raw_html' / 'details_en' + details_dir.mkdir(parents=True, exist_ok=True) + + tier_start = get(TIER_URL) + timestamp_ja, top100 = parse_tier(tier_start.text) + target_dt = parse_ja_timestamp(timestamp_ja) + (work / 'raw_html' / 'tier_start.html').write_bytes(tier_start.content) + + def fetch_one(row: dict[str, object]) -> dict[str, object]: + rank = int(row['rank']) + slug = str(row['slug']) + response = get(str(row['detail_url_en'])) + metadata = detail_metadata(response.text) + detail_dt = parse_en_timestamp(str(metadata['updated_at_en'])) + if detail_dt != target_dt: + raise RuntimeError(f'detail timestamp mismatch rank={rank} slug={slug}: {metadata["updated_at_en"]} != {timestamp_ja}') + filename = f'{rank:03d}_{slug}.html' + target = details_dir / filename + target.write_bytes(response.content) + return { + **row, + **metadata, + 'snapshot_jst': timestamp_ja, + 'bytes': len(response.content), + 'sha256': sha256_bytes(response.content), + 'filename': str(target.relative_to(work)), + } + + captures: list[dict[str, object]] = [] + with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor: + futures = {executor.submit(fetch_one, row): row for row in top100} + for future in concurrent.futures.as_completed(futures): + captures.append(future.result()) + captures.sort(key=lambda row: int(row['rank'])) + + tier_end = get(TIER_URL) + timestamp_end, top100_end = parse_tier(tier_end.text) + (work / 'raw_html' / 'tier_end.html').write_bytes(tier_end.content) + start_sig = [(row['rank'], row['slug']) for row in top100] + end_sig = [(row['rank'], row['slug']) for row in top100_end] + if timestamp_end != timestamp_ja or end_sig != start_sig: + raise RuntimeError(f'tier changed during capture: start={timestamp_ja}, end={timestamp_end}, same_top100={end_sig == start_sig}') + if len(captures) != 100: + raise RuntimeError(f'detail capture count {len(captures)}') + + by_rank = {int(row['rank']): row for row in captures} + top_enriched = [] + for row in top100: + capture = by_rank[int(row['rank'])] + top_enriched.append({ + 'rank': row['rank'], + 'slug': row['slug'], + 'name_ja': row['name_ja'], + 'name_en': capture['name_en'], + 'types': capture['types'], + 'snapshot_jst': timestamp_ja, + 'tier_url': TIER_URL, + 'detail_url': row['detail_url_en'], + 'image_url': row['image_url'], + }) + write_csv(work / 'top100.csv', top_enriched) + write_csv(work / 'detail_capture.csv', captures) + + manifest = { + 'schema_version': 1, + 'source': 'OP.GG Pokémon Champions', + 'tier_url': TIER_URL, + 'snapshot_jst': timestamp_ja, + 'snapshot_iso_jst': target_dt.replace(tzinfo=JST).isoformat(), + 'captured_at_jst': datetime.now(JST).replace(microsecond=0).isoformat(), + 'top100_count': len(top_enriched), + 'detail_count': len(captures), + 'start_end_tier_identical': True, + 'all_detail_timestamps_match': True, + 'tier_start_sha256': sha256_bytes(tier_start.content), + 'tier_end_sha256': sha256_bytes(tier_end.content), + 'detail_bytes': sum(int(row['bytes']) for row in captures), + 'attempt': attempt, + } + (work / 'snapshot_manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + return {'work': work, 'manifest': manifest} + + +def main() -> None: + last: Exception | None = None + for attempt in range(1, 4): + try: + result = run_attempt(attempt) + if ROOT.exists(): + shutil.rmtree(ROOT) + result['work'].rename(ROOT) + print(json.dumps(result['manifest'], ensure_ascii=False)) + return + except Exception as exc: + last = exc + print(f'attempt {attempt} failed: {exc}', flush=True) + time.sleep(5 * attempt) + raise RuntimeError(f'formal snapshot failed after retries: {last}') + + +if __name__ == '__main__': + main() From db2adaf09b821354a838999ed84770ea1058eab2 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:50:51 +0900 Subject: [PATCH 08/54] Parse synchronized OP.GG pages into formal component evidence --- pokemon_audit/parse_snapshot.py | 198 ++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 pokemon_audit/parse_snapshot.py diff --git a/pokemon_audit/parse_snapshot.py b/pokemon_audit/parse_snapshot.py new file mode 100644 index 0000000..632637f --- /dev/null +++ b/pokemon_audit/parse_snapshot.py @@ -0,0 +1,198 @@ +from __future__ import annotations + +import csv +import json +from pathlib import Path +from typing import Any + +from rsc_utils import decode_rsc_text, extract_json_after_key, extract_string_after_key + +BASE = Path('/mnt/data') +ROOT = BASE / 'formal_inputs' / 'formal_snapshot' +OUT = BASE / 'formal_parsed' +OUT.mkdir(parents=True, exist_ok=True) + +MOVE_USAGE_THRESHOLD = 5.0 +WIN_MOVE_THRESHOLD = 1.0 +OTHER_THRESHOLD = 5.0 + + +def read_csv(path: Path) -> list[dict[str, str]]: + with path.open(encoding='utf-8-sig', newline='') as handle: + return list(csv.DictReader(handle)) + + +def write_csv(path: Path, rows: list[dict[str, Any]], fieldnames: list[str] | None = None) -> None: + if fieldnames is None: + fieldnames = list(rows[0]) if rows else [] + with path.open('w', encoding='utf-8-sig', newline='') as handle: + writer = csv.DictWriter(handle, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(rows) + + +def index_by_id(rows: list[dict[str, Any]]) -> dict[int, dict[str, Any]]: + return {int(row['id']): row for row in rows if row.get('id') is not None} + + +def spread_points(value: str) -> list[int]: + parts = value.split('-') + if len(parts) != 6: + raise ValueError(f'invalid training spread {value}') + points = [int(part, 16) for part in parts] + if any(point < 0 or point > 32 for point in points): + raise ValueError(f'invalid point value in {value}: {points}') + if sum(points) > 66: + raise ValueError(f'training point sum exceeds 66: {value}: {points}') + return points + + +def main() -> None: + manifest = json.loads((ROOT / 'snapshot_manifest.json').read_text(encoding='utf-8')) + top100 = read_csv(ROOT / 'top100.csv') + if len(top100) != 100: + raise RuntimeError(f'top100 count {len(top100)}') + + all_components: list[dict[str, Any]] = [] + opponents: list[dict[str, Any]] = [] + diagnostics: list[dict[str, Any]] = [] + + for top in top100: + rank = int(top['rank']) + slug = top['slug'] + html_path = ROOT / 'raw_html' / 'details_en' / f'{rank:03d}_{slug}.html' + html = html_path.read_text(encoding='utf-8') + rsc = decode_rsc_text(html) + updated_at = extract_string_after_key(rsc, 'updatedAt') + detail = extract_json_after_key(rsc, 'singleDetail') + lookup = extract_json_after_key(rsc, 'lookupData') + moves = index_by_id(lookup.get('moves') or []) + abilities = index_by_id(lookup.get('abilities') or []) + items = index_by_id(lookup.get('items') or []) + natures = index_by_id(lookup.get('natures') or []) + pokemon_rows = lookup.get('pokemon') or [] + target = next((row for row in pokemon_rows if row.get('id') == detail.get('pokemon', {}).get('id') and row.get('form', 0) == detail.get('pokemon', {}).get('form', 0)), None) or next((row for row in pokemon_rows if row.get('key') == slug), None) + name_en = (target or {}).get('name') or top.get('name_en') or slug + types = (target or {}).get('types') or [x for x in top.get('types', '').split('|') if x] + + normal_move = {int(row['id']): float(row.get('usagePercent') or 0) for row in detail.get('moves') or []} + win_move = {int(row['id']): float(row.get('usagePercent') or 0) for row in (detail.get('win') or {}).get('moves') or []} + move_ids = sorted(set(normal_move) | set(win_move), key=lambda mid: (-normal_move.get(mid, 0), -win_move.get(mid, 0), mid)) + move_components: list[dict[str, Any]] = [] + for component_rank, move_id in enumerate(move_ids, 1): + meta = moves.get(move_id) + if meta is None: + raise RuntimeError(f'move lookup missing rank={rank} slug={slug} id={move_id}') + usage = normal_move.get(move_id, 0.0) + win_usage = win_move.get(move_id, 0.0) + selected = usage >= MOVE_USAGE_THRESHOLD or win_usage >= WIN_MOVE_THRESHOLD + row = { + 'opponent_rank': rank, 'slug': slug, 'pokemon_ja': top['name_ja'], 'pokemon_en': name_en, + 'snapshot_jst': manifest['snapshot_jst'], 'category': 'move', 'component_rank': component_rank, + 'component_id': move_id, 'name': meta['name'], 'key': meta.get('key', ''), + 'usage_percent': usage, 'winning_usage_percent': win_usage, 'selected_by_threshold': selected, + 'type': meta.get('type', ''), 'move_category': meta.get('category', ''), 'power': meta.get('power', ''), + 'accuracy': meta.get('accuracy', ''), 'priority': meta.get('priority', 0), 'spread': '', + 'hp_points': '', 'attack_points': '', 'defense_points': '', 'sp_atk_points': '', 'sp_def_points': '', 'speed_points': '', + 'source_file': str(html_path.relative_to(BASE)), 'source_url': top['detail_url'], + } + move_components.append(row) + all_components.append(row) + + category_specs = [ + ('held_item', detail.get('items') or [], items), + ('ability', detail.get('abilities') or [], abilities), + ('stat_alignment', detail.get('natures') or [], natures), + ] + grouped: dict[str, list[dict[str, Any]]] = {'move': move_components} + for category, source_rows, lookup_index in category_specs: + component_rows: list[dict[str, Any]] = [] + for component_rank, source in enumerate(source_rows, 1): + component_id = int(source['id']) + meta = lookup_index.get(component_id) + if meta is None: + raise RuntimeError(f'{category} lookup missing rank={rank} id={component_id}') + usage = float(source.get('usagePercent') or 0) + row = { + 'opponent_rank': rank, 'slug': slug, 'pokemon_ja': top['name_ja'], 'pokemon_en': name_en, + 'snapshot_jst': manifest['snapshot_jst'], 'category': category, 'component_rank': component_rank, + 'component_id': component_id, 'name': meta['name'], 'key': meta.get('key', ''), + 'usage_percent': usage, 'winning_usage_percent': '', 'selected_by_threshold': usage >= OTHER_THRESHOLD, + 'type': '', 'move_category': '', 'power': '', 'accuracy': '', 'priority': '', 'spread': '', + 'hp_points': '', 'attack_points': '', 'defense_points': '', 'sp_atk_points': '', 'sp_def_points': '', 'speed_points': '', + 'source_file': str(html_path.relative_to(BASE)), 'source_url': top['detail_url'], + } + component_rows.append(row) + all_components.append(row) + grouped[category] = component_rows + + training_components: list[dict[str, Any]] = [] + for component_rank, source in enumerate(detail.get('training') or [], 1): + spread = str(source['spread']) + points = spread_points(spread) + usage = float(source.get('usagePercent') or 0) + row = { + 'opponent_rank': rank, 'slug': slug, 'pokemon_ja': top['name_ja'], 'pokemon_en': name_en, + 'snapshot_jst': manifest['snapshot_jst'], 'category': 'stat_points', 'component_rank': component_rank, + 'component_id': spread, 'name': '', 'key': '', 'usage_percent': usage, 'winning_usage_percent': '', + 'selected_by_threshold': usage >= OTHER_THRESHOLD, 'type': '', 'move_category': '', 'power': '', + 'accuracy': '', 'priority': '', 'spread': spread, + 'hp_points': points[0], 'attack_points': points[1], 'defense_points': points[2], + 'sp_atk_points': points[3], 'sp_def_points': points[4], 'speed_points': points[5], + 'source_file': str(html_path.relative_to(BASE)), 'source_url': top['detail_url'], + } + training_components.append(row) + all_components.append(row) + grouped['stat_points'] = training_components + + top_four = sorted(move_components, key=lambda row: (-float(row['usage_percent']), int(row['component_rank'])))[:4] + if len(top_four) != 4: + raise RuntimeError(f'less than four moves for rank={rank} {slug}') + if any(not bool(row['selected_by_threshold']) for row in top_four): + raise RuntimeError(f'top-four move under threshold for rank={rank} {slug}: {top_four}') + selected_parts: dict[str, dict[str, Any]] = {} + for category in ['held_item', 'ability', 'stat_alignment', 'stat_points']: + eligible = [row for row in grouped[category] if bool(row['selected_by_threshold'])] + if not eligible: + raise RuntimeError(f'no threshold-passing {category} for rank={rank} {slug}') + selected_parts[category] = sorted(eligible, key=lambda row: (-float(row['usage_percent']), int(row['component_rank'])))[0] + opponent = { + 'rank': rank, 'slug': slug, 'pokemon_id': detail.get('pokemon', {}).get('id'), + 'pokemon_form': detail.get('pokemon', {}).get('form', 0), 'pokemon_key': detail.get('pokemon', {}).get('key'), + 'pokemon_ja': top['name_ja'], 'pokemon_en': name_en, 'types': types, 'updated_at_en': updated_at, + 'snapshot_jst': manifest['snapshot_jst'], 'detail_url': top['detail_url'], 'source_file': str(html_path.relative_to(BASE)), + 'moves': [row['name'] for row in top_four], 'move_usage': [row['usage_percent'] for row in top_four], + 'eligible_attack_moves': [row['name'] for row in move_components if bool(row['selected_by_threshold']) and row['move_category'] != 'status'], + 'item': selected_parts['held_item']['name'], 'item_usage_percent': selected_parts['held_item']['usage_percent'], + 'ability': selected_parts['ability']['name'], 'ability_usage_percent': selected_parts['ability']['usage_percent'], + 'nature': selected_parts['stat_alignment']['name'], 'nature_usage_percent': selected_parts['stat_alignment']['usage_percent'], + 'spread': selected_parts['stat_points']['spread'], 'spread_usage_percent': selected_parts['stat_points']['usage_percent'], + 'hp_points': selected_parts['stat_points']['hp_points'], 'attack_points': selected_parts['stat_points']['attack_points'], + 'defense_points': selected_parts['stat_points']['defense_points'], 'sp_atk_points': selected_parts['stat_points']['sp_atk_points'], + 'sp_def_points': selected_parts['stat_points']['sp_def_points'], 'speed_points': selected_parts['stat_points']['speed_points'], + 'mega_usage': (detail.get('mega') or {}).get('use') or [], 'evidence_type': 'current_most_frequent_completion', + } + opponents.append(opponent) + diagnostics.append({ + 'rank': rank, 'slug': slug, 'move_count': len(move_components), + 'selected_move_count': sum(bool(row['selected_by_threshold']) for row in move_components), + 'item_count': len(grouped['held_item']), 'ability_count': len(grouped['ability']), + 'nature_count': len(grouped['stat_alignment']), 'spread_count': len(grouped['stat_points']), + 'fixed_moves': ' | '.join(opponent['moves']), 'fixed_item': opponent['item'], + 'fixed_ability': opponent['ability'], 'fixed_nature': opponent['nature'], 'fixed_spread': opponent['spread'], + }) + + if len(opponents) != 100 or {row['rank'] for row in opponents} != set(range(1, 101)): + raise RuntimeError('opponent parse is not exactly ranks 1..100') + write_csv(OUT / 'component_evidence.csv', all_components) + write_csv(OUT / 'parse_diagnostics.csv', diagnostics) + (OUT / 'opponent_usage.json').write_text(json.dumps({ + 'schema_version': 1, 'snapshot_jst': manifest['snapshot_jst'], + 'thresholds': {'move_usage_percent': MOVE_USAGE_THRESHOLD, 'winning_move_percent': WIN_MOVE_THRESHOLD, 'item_ability_nature_spread_percent': OTHER_THRESHOLD}, + 'opponents': opponents, + }, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + print(json.dumps({'opponents': len(opponents), 'components': len(all_components), 'snapshot_jst': manifest['snapshot_jst']}, ensure_ascii=False)) + + +if __name__ == '__main__': + main() From fd8b04b664ebaacb134786efbf9012947a581182 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:51:20 +0900 Subject: [PATCH 09/54] Build one formal most-frequent build per top-100 opponent --- pokemon_audit/build_evidence.py | 153 ++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 pokemon_audit/build_evidence.py diff --git a/pokemon_audit/build_evidence.py b/pokemon_audit/build_evidence.py new file mode 100644 index 0000000..866e291 --- /dev/null +++ b/pokemon_audit/build_evidence.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +import csv +import hashlib +import json +import re +import subprocess +import unicodedata +from pathlib import Path +from typing import Any + +BASE = Path('/mnt/data') +PARSED = BASE / 'formal_parsed' +OUT = BASE / 'formal_audit_data' +OUT.mkdir(parents=True, exist_ok=True) +BATTLE_REPO = BASE / 'battledata_repo' + + +def idnorm(value: str) -> str: + value = unicodedata.normalize('NFKC', str(value)).lower() + return re.sub(r'[^a-z0-9]+', '', value) + + +def add_key(index: dict[str, tuple[Path, dict[str, Any]]], key: Any, value: tuple[Path, dict[str, Any]]) -> None: + if key is None: + return + normalized = idnorm(str(key)) + if normalized: + index.setdefault(normalized, value) + + +def build_index() -> tuple[dict[str, tuple[Path, dict[str, Any]]], dict[str, str]]: + index: dict[str, tuple[Path, dict[str, Any]]] = {} + root = BATTLE_REPO / 'data' / 'api' / 'pokemon' + if not root.exists(): + raise FileNotFoundError(root) + for path in root.glob('*.json'): + try: + data = json.loads(path.read_text(encoding='utf-8')) + except Exception: + continue + value = (path, data) + for key in [path.stem, data.get('slug'), data.get('showdownId'), data.get('showdownName'), data.get('name'), data.get('battleName')]: + add_key(index, key, value) + summary = data.get('summary') or {} + for form in summary.get('forms') or []: + for key in [form.get('slug'), form.get('form_name'), form.get('saved_name'), form.get('title')]: + add_key(index, key, value) + aliases: dict[str, str] = {} + lookup = BATTLE_REPO / 'data' / 'api' / 'lookup.json' + if lookup.exists(): + raw = json.loads(lookup.read_text(encoding='utf-8')).get('aliases') or {} + aliases = {idnorm(k): str(v) for k, v in raw.items()} + return index, aliases + + +def resolve_record(opponent: dict[str, Any], index: dict[str, tuple[Path, dict[str, Any]]], aliases: dict[str, str]) -> tuple[Path, dict[str, Any], str]: + raw_candidates = [opponent.get('slug'), opponent.get('pokemon_key'), opponent.get('pokemon_en')] + slug = str(opponent.get('slug') or '') + raw_candidates.extend([ + slug.replace('-alolan', '-alola'), slug.replace('-galarian', '-galar'), slug.replace('-hisuian', '-hisui'), + slug.replace('alolan-', ''), slug.replace('galarian-', ''), slug.replace('hisuian-', ''), + ]) + for raw in raw_candidates: + key = idnorm(str(raw or '')) + if not key: + continue + if key in index: + path, data = index[key] + return path, data, f'direct:{raw}' + alias = aliases.get(key) + if alias and idnorm(alias) in index: + path, data = index[idnorm(alias)] + return path, data, f'alias:{raw}->{alias}' + raise KeyError(f"mapping not found rank={opponent.get('rank')} slug={opponent.get('slug')} english={opponent.get('pokemon_en')}") + + +def csv_write(path: Path, rows: list[dict[str, Any]]) -> None: + if not rows: + raise ValueError(f'empty rows for {path}') + with path.open('w', encoding='utf-8-sig', newline='') as handle: + writer = csv.DictWriter(handle, fieldnames=list(rows[0])) + writer.writeheader() + writer.writerows(rows) + + +def main() -> None: + usage = json.loads((PARSED / 'opponent_usage.json').read_text(encoding='utf-8')) + index, aliases = build_index() + repo_commit = subprocess.check_output(['git', '-C', str(BATTLE_REPO), 'rev-parse', 'HEAD'], text=True).strip() + build_rows: list[dict[str, Any]] = [] + output_opponents: list[dict[str, Any]] = [] + mapping_rows: list[dict[str, Any]] = [] + for opponent in usage['opponents']: + path, record, match_method = resolve_record(opponent, index, aliases) + engine_species = record.get('showdownName') or record.get('name') or record.get('battleName') + showdown_id = record.get('showdownId') or record.get('slug') + if not engine_species: + raise RuntimeError(f'engine species missing in {path}') + fixed_moves = list(opponent['moves']) + if len(fixed_moves) != 4: + raise RuntimeError(f'fixed move count != 4 rank={opponent["rank"]}') + rank = int(opponent['rank']) + snapshot_token = re.sub(r'[^0-9]+', '', usage['snapshot_jst']) + build_id = f'M4_{snapshot_token}_R{rank:03d}_MOSTFREQ' + build = { + 'build_id': build_id, 'opponent_rank': rank, 'slug': opponent['slug'], + 'pokemon_ja': opponent['pokemon_ja'], 'pokemon_en': opponent['pokemon_en'], + 'engine_species': engine_species, 'showdown_id': showdown_id, + 'moves': fixed_moves, 'eligible_moves': fixed_moves, + 'move_1': fixed_moves[0], 'move_2': fixed_moves[1], 'move_3': fixed_moves[2], 'move_4': fixed_moves[3], + 'item': opponent['item'], 'ability': opponent['ability'], 'nature': opponent['nature'], 'spread': opponent['spread'], + 'hp_points': int(opponent['hp_points']), 'attack_points': int(opponent['attack_points']), + 'defense_points': int(opponent['defense_points']), 'sp_atk_points': int(opponent['sp_atk_points']), + 'sp_def_points': int(opponent['sp_def_points']), 'speed_points': int(opponent['speed_points']), + 'evidence_type': 'current_most_frequent_completion', 'snapshot_jst': usage['snapshot_jst'], + 'source_url': opponent['detail_url'], 'source_file': opponent['source_file'], + 'mapping_file': str(path.relative_to(BATTLE_REPO)), 'mapping_commit': repo_commit, 'mapping_method': match_method, + 'item_usage_percent': opponent['item_usage_percent'], 'ability_usage_percent': opponent['ability_usage_percent'], + 'nature_usage_percent': opponent['nature_usage_percent'], 'spread_usage_percent': opponent['spread_usage_percent'], + 'move_usage_percent': ' | '.join(str(v) for v in opponent['move_usage']), + } + build_rows.append(build) + output_opponents.append({ + 'rank': rank, 'slug': opponent['slug'], 'pokemon_ja': opponent['pokemon_ja'], 'pokemon_en': opponent['pokemon_en'], + 'engine_species': engine_species, 'showdown_id': showdown_id, 'moves': fixed_moves, 'build_id': build_id, + }) + mapping_rows.append({ + 'rank': rank, 'slug': opponent['slug'], 'pokemon_en': opponent['pokemon_en'], + 'engine_species': engine_species, 'showdown_id': showdown_id, 'mapping_method': match_method, + 'mapping_file': str(path.relative_to(BATTLE_REPO)), + }) + + if len(build_rows) != 100 or len({row['opponent_rank'] for row in build_rows}) != 100: + raise RuntimeError('build evidence is not exactly one build for each rank 1..100') + if any(sum(int(row[key]) for key in ['hp_points','attack_points','defense_points','sp_atk_points','sp_def_points','speed_points']) > 66 for row in build_rows): + raise RuntimeError('invalid stat point sum') + + csv_write(OUT / 'build_evidence.csv', build_rows) + csv_write(OUT / 'species_mapping.csv', mapping_rows) + data = { + 'schema_version': 1, 'snapshot_jst': usage['snapshot_jst'], 'thresholds': usage['thresholds'], + 'mapping_repository': 'Gheist23/pokemonbattledata', 'mapping_commit': repo_commit, + 'opponents': output_opponents, 'builds': build_rows, + } + (OUT / 'opponent_builds.json').write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + (OUT / 'component_evidence.csv').write_bytes((PARSED / 'component_evidence.csv').read_bytes()) + digest = hashlib.sha256((OUT / 'opponent_builds.json').read_bytes()).hexdigest() + print(json.dumps({'builds': len(build_rows), 'mapping_commit': repo_commit, 'sha256': digest}, ensure_ascii=False)) + + +if __name__ == '__main__': + main() From 7aa91124a5840cecdc2d4471d788460067d5cd69 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:51:50 +0900 Subject: [PATCH 10/54] Add fixed six-member formal team configuration --- pokemon_audit/team_config_formal.json | 112 ++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 pokemon_audit/team_config_formal.json diff --git a/pokemon_audit/team_config_formal.json b/pokemon_audit/team_config_formal.json new file mode 100644 index 0000000..692f17b --- /dev/null +++ b/pokemon_audit/team_config_formal.json @@ -0,0 +1,112 @@ +{ + "schema_version": 1, + "level": 50, + "battle_format": "Singles", + "members": [ + { + "order": 1, + "id": "archaludon", + "name_ja": "ブリジュラス", + "base_species": "Archaludon", + "standing_species": "Archaludon", + "switch_entry_species": "Archaludon", + "post_survival_species": "Archaludon", + "item": "Sitrus Berry", + "ability": "Sturdy", + "standing_ability": "Sturdy", + "post_survival_ability": "Sturdy", + "nature": "Timid", + "points": {"hp": 0, "atk": 0, "def": 1, "spa": 32, "spd": 1, "spe": 32}, + "expected_base_stats": {"hp": 165, "atk": 112, "def": 151, "spa": 177, "spd": 86, "spe": 150}, + "moves": ["Draco Meteor", "Thunderbolt", "Flash Cannon", "Dark Pulse"] + }, + { + "order": 2, + "id": "hippowdon", + "name_ja": "カバルドン", + "base_species": "Hippowdon", + "standing_species": "Hippowdon", + "switch_entry_species": "Hippowdon", + "post_survival_species": "Hippowdon", + "item": "Leftovers", + "ability": "Sand Stream", + "standing_ability": "Sand Stream", + "post_survival_ability": "Sand Stream", + "nature": "Impish", + "points": {"hp": 32, "atk": 0, "def": 12, "spa": 0, "spd": 22, "spe": 0}, + "expected_base_stats": {"hp": 215, "atk": 132, "def": 165, "spa": 79, "spd": 114, "spe": 67}, + "moves": ["Earthquake", "Yawn", "Slack Off", "Stealth Rock"] + }, + { + "order": 3, + "id": "garchomp", + "name_ja": "ガブリアス", + "base_species": "Garchomp", + "standing_species": "Garchomp-Mega", + "switch_entry_species": "Garchomp", + "post_survival_species": "Garchomp-Mega", + "item": "Garchompite", + "ability": "Rough Skin", + "standing_ability": "Sand Force", + "post_survival_ability": "Sand Force", + "nature": "Adamant", + "points": {"hp": 2, "atk": 32, "def": 0, "spa": 0, "spd": 0, "spe": 32}, + "expected_base_stats": {"hp": 185, "atk": 200, "def": 115, "spa": 90, "spd": 105, "spe": 154}, + "expected_mega_stats": {"hp": 185, "atk": 244, "def": 135, "spa": 126, "spd": 115, "spe": 144}, + "moves": ["Earthquake", "Thunder Fang", "Swords Dance", "Scale Shot"] + }, + { + "order": 4, + "id": "delphox", + "name_ja": "マフォクシー", + "base_species": "Delphox", + "standing_species": "Delphox-Mega", + "switch_entry_species": "Delphox", + "post_survival_species": "Delphox-Mega", + "item": "Delphoxite", + "ability": "Magician", + "standing_ability": "Levitate", + "post_survival_ability": "Levitate", + "nature": "Timid", + "points": {"hp": 25, "atk": 0, "def": 8, "spa": 4, "spd": 0, "spe": 29}, + "expected_base_stats": {"hp": 175, "atk": 80, "def": 100, "spa": 138, "spd": 120, "spe": 168}, + "expected_mega_stats": {"hp": 175, "atk": 80, "def": 100, "spa": 183, "spd": 145, "spe": 201}, + "moves": ["Flamethrower", "Nasty Plot", "Psyshock", "Dazzling Gleam"] + }, + { + "order": 5, + "id": "dragonite", + "name_ja": "カイリュー", + "base_species": "Dragonite", + "standing_species": "Dragonite-Mega", + "switch_entry_species": "Dragonite", + "post_survival_species": "Dragonite-Mega", + "item": "Dragoninite", + "ability": "Multiscale", + "standing_ability": "Multiscale", + "post_survival_ability": "Multiscale", + "nature": "Modest", + "points": {"hp": 17, "atk": 0, "def": 0, "spa": 17, "spd": 0, "spe": 32}, + "expected_base_stats": {"hp": 183, "atk": 138, "def": 115, "spa": 150, "spd": 120, "spe": 132}, + "expected_mega_stats": {"hp": 183, "atk": 129, "def": 135, "spa": 200, "spd": 145, "spe": 152}, + "moves": ["Draco Meteor", "Flamethrower", "Air Slash", "Roost"] + }, + { + "order": 6, + "id": "greninja", + "name_ja": "ゲッコウガ", + "base_species": "Greninja", + "standing_species": "Greninja", + "switch_entry_species": "Greninja", + "post_survival_species": "Greninja", + "item": "Choice Scarf", + "ability": "Protean", + "standing_ability": "Protean", + "post_survival_ability": "Protean", + "nature": "Modest", + "points": {"hp": 2, "atk": 0, "def": 0, "spa": 32, "spd": 0, "spe": 32}, + "expected_base_stats": {"hp": 149, "atk": 103, "def": 87, "spa": 170, "spd": 91, "spe": 174}, + "moves": ["Water Shuriken", "Surf", "Sludge Wave", "Dark Pulse"] + } + ] +} From d17f073b49f01fa2c7565811d025934fb7d07f53 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:52:09 +0900 Subject: [PATCH 11/54] Run synchronized top-100 snapshot and formal evidence build --- .github/workflows/pokemon-audit.yml | 40 ++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index 5a62c59..19ce610 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -8,20 +8,46 @@ on: jobs: audit: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' - - name: Install scraper dependencies + - name: Install Python dependencies run: python -m pip install --disable-pip-version-check requests beautifulsoup4 lxml - - name: Fetch and inspect OP.GG pages + - name: Prepare fixed work directories run: | - python pokemon_audit/scrape_debug.py - python pokemon_audit/detail_debug.py + sudo rm -rf /mnt/data/formal_inputs /mnt/data/formal_parsed /mnt/data/formal_audit_data /mnt/data/battledata_repo + sudo mkdir -p /mnt/data/formal_audit_data + sudo chown -R "$USER":"$USER" /mnt/data + cp pokemon_audit/team_config_formal.json /mnt/data/formal_audit_data/team_config_formal.json + - name: Clone mapping repository at a recorded commit + run: | + git clone --depth 1 --filter=blob:none --sparse https://github.com/Gheist23/pokemonbattledata.git /mnt/data/battledata_repo + git -C /mnt/data/battledata_repo sparse-checkout set data/api + git -C /mnt/data/battledata_repo rev-parse HEAD | tee /mnt/data/formal_audit_data/mapping_commit.txt + - name: Capture synchronized formal snapshot + run: PYTHONPATH=pokemon_audit python pokemon_audit/fetch_snapshot.py + - name: Parse formal evidence + run: PYTHONPATH=pokemon_audit python pokemon_audit/parse_snapshot.py + - name: Build one formal build per opponent + run: PYTHONPATH=pokemon_audit python pokemon_audit/build_evidence.py + - name: Show formal snapshot summary + run: | + cat /mnt/data/formal_inputs/formal_snapshot/snapshot_manifest.json + python - <<'PY' + import json + p='/mnt/data/formal_audit_data/opponent_builds.json' + d=json.load(open(p)) + print({'opponents':len(d['opponents']),'builds':len(d['builds']),'snapshot':d['snapshot_jst']}) + PY - uses: actions/upload-artifact@v4 with: - name: pokemon-audit-debug - path: out/ + name: pokemon-audit-formal-stage1 + path: | + /mnt/data/formal_inputs/ + /mnt/data/formal_parsed/ + /mnt/data/formal_audit_data/ if-no-files-found: error + compression-level: 6 From e2e5993dee2b3a99933b1b39b6aa5537fbf9c493 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:53:10 +0900 Subject: [PATCH 12/54] Add fixed Champions engine and input validator --- pokemon_audit/validate_engine_inputs.js | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 pokemon_audit/validate_engine_inputs.js diff --git a/pokemon_audit/validate_engine_inputs.js b/pokemon_audit/validate_engine_inputs.js new file mode 100644 index 0000000..6b24ce5 --- /dev/null +++ b/pokemon_audit/validate_engine_inputs.js @@ -0,0 +1,61 @@ +'use strict'; +const fs = require('fs'); +const child = require('child_process'); +const calc = require('/mnt/data/damage_calc_repo/calc/dist'); +const data = JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/opponent_builds.json', 'utf8')); +const team = JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/team_config_formal.json', 'utf8')); +let gen; +try { gen = calc.Generations.get(0); } catch (_) { gen = calc.Generations.get('champions'); } +const errors = []; +const speciesResolved = {}; +const moveMeta = {}; +function getSpecies(name) { try { const s = gen.species.get(calc.toID(name)); return s && s.name ? s : null; } catch (_) { return null; } } +function resolveSpecies(raw) { + const v = String(raw || ''); + const candidates = [v, + v.replace(/ Alolan$/i, '-Alola').replace(/^Alolan /i, '').replace(/ Alola$/i, '-Alola'), + v.replace(/ Galarian$/i, '-Galar').replace(/^Galarian /i, '').replace(/ Galar$/i, '-Galar'), + v.replace(/ Hisuian$/i, '-Hisui').replace(/^Hisuian /i, '').replace(/ Hisui$/i, '-Hisui'), + v.replace(/ Female$/i, '-F').replace(/ Male$/i, '-M'), + v.replace(/ Shield Forme$/i, '-Shield').replace(/ Blade Forme$/i, '-Blade'), + v.replace(/ Wash Rotom$/i, 'Rotom-Wash').replace(/ Heat Rotom$/i, 'Rotom-Heat'), + v.replace(/ Frost Rotom$/i, 'Rotom-Frost').replace(/ Mow Rotom$/i, 'Rotom-Mow').replace(/ Fan Rotom$/i, 'Rotom-Fan'), + v.replace(/ /g, '-')]; + for (const c of candidates) { const s = getSpecies(c); if (s) return s.name; } + return null; +} +function evs(points) { return {hp:+points.hp, atk:+points.atk, def:+points.def, spa:+points.spa, spd:+points.spd, spe:+points.spe}; } +function equalStats(actual, expected) { return ['hp','atk','def','spa','spd','spe'].every(k => +actual[k] === +expected[k]); } +for (const m of team.members) { + if (!Array.isArray(m.moves) || m.moves.length !== 4) errors.push({kind:'team_moves', member:m.id, moves:m.moves}); + const total = Object.values(m.points).reduce((a,b)=>a+Number(b),0); if (total !== 66) errors.push({kind:'team_points', member:m.id, total}); + const probes = [ + ['base',m.base_species,m.ability,m.expected_base_stats], + ['standing',m.standing_species,m.standing_ability,m.standing_species.includes('-Mega')?m.expected_mega_stats:m.expected_base_stats], + ['switch',m.switch_entry_species,m.ability,m.switch_entry_species.includes('-Mega')?m.expected_mega_stats:m.expected_base_stats], + ['post',m.post_survival_species,m.post_survival_ability,m.post_survival_species.includes('-Mega')?m.expected_mega_stats:m.expected_base_stats]]; + for (const [kind,species,ability,expected] of probes) { + if (!expected) continue; + try { const p=new calc.Pokemon(gen,species,{level:50,item:m.item,ability,nature:m.nature,evs:evs(m.points)}); if(!equalStats(p.rawStats,expected))errors.push({kind:'team_stats',member:m.id,state:kind,species,actual:p.rawStats,expected}); } + catch(error){errors.push({kind:'team_state',member:m.id,state:kind,species,ability,error:String(error)});} + } + for(const moveName of m.moves){const move=gen.moves.get(calc.toID(moveName));if(!move||!move.name)errors.push({kind:'team_move',member:m.id,move:moveName});} +} +for(const op of data.opponents){ + const resolved=resolveSpecies(op.engine_species)||resolveSpecies(op.pokemon_en); + if(!resolved)errors.push({kind:'species',rank:op.rank,slug:op.slug,raw:op.engine_species,english:op.pokemon_en});else speciesResolved[String(op.rank)]=resolved; + if(!Array.isArray(op.moves)||op.moves.length!==4)errors.push({kind:'opponent_moves',rank:op.rank,moves:op.moves}); + for(const name of op.moves||[]){try{const mv=gen.moves.get(calc.toID(name));if(!mv||!mv.name)errors.push({kind:'move',rank:op.rank,name});else moveMeta[name]={name:mv.name,type:mv.type,category:mv.category,bp:mv.bp,accuracy:mv.accuracy,priority:mv.priority,multihit:mv.multihit,flags:mv.flags};}catch(error){errors.push({kind:'move',rank:op.rank,name,error:String(error)});}} +} +for(const b of data.builds){ + const base=speciesResolved[String(b.opponent_rank)];if(!base)continue;let forme=base;try{forme=calc.Pokemon.getForme(gen,base,b.item,b.moves[0])||base}catch(_){} + const sd=getSpecies(forme)||getSpecies(base);let ability=b.ability;if(sd&&sd.abilities&&!Object.values(sd.abilities).includes(ability))ability=Object.values(sd.abilities).filter(Boolean)[0]||ability; + const e={hp:+b.hp_points,atk:+b.attack_points,def:+b.defense_points,spa:+b.sp_atk_points,spd:+b.sp_def_points,spe:+b.speed_points}; + try{const p=new calc.Pokemon(gen,forme,{level:50,item:b.item,ability,nature:b.nature,evs:e});if(!p.rawStats||!p.rawStats.hp)throw new Error('missing raw stats');} + catch(error){errors.push({kind:'build',build_id:b.build_id,base,forme,item:b.item,ability,nature:b.nature,error:String(error)});} +} +const engineCommit=child.execFileSync('git',['-C','/mnt/data/damage_calc_repo','rev-parse','HEAD'],{encoding:'utf8'}).trim(); +const output={generation:{num:gen.num,name:gen.name},engine_commit:engineCommit,team_count:team.members.length,opponent_count:data.opponents.length,build_count:data.builds.length,speciesResolved,moveMeta,errorCount:errors.length,errors:errors.slice(0,500)}; +fs.writeFileSync('/mnt/data/formal_audit_data/engine_input_validation.json',JSON.stringify(output,null,2)); +if(errors.length)throw new Error(`engine input validation failed (${errors.length}): ${JSON.stringify(errors.slice(0,12))}`); +console.log(JSON.stringify({generation:output.generation,engine_commit:engineCommit,opponents:data.opponents.length,builds:data.builds.length})); From 9eb90d8d6cf53eed5dc74b8ecb6048f25fb850a8 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:53:54 +0900 Subject: [PATCH 13/54] Preserve stage-one logs and partial formal evidence on failure --- .github/workflows/pokemon-audit.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index 19ce610..d7bc826 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -19,7 +19,7 @@ jobs: - name: Prepare fixed work directories run: | sudo rm -rf /mnt/data/formal_inputs /mnt/data/formal_parsed /mnt/data/formal_audit_data /mnt/data/battledata_repo - sudo mkdir -p /mnt/data/formal_audit_data + sudo mkdir -p /mnt/data/formal_audit_data /mnt/data/formal_parsed sudo chown -R "$USER":"$USER" /mnt/data cp pokemon_audit/team_config_formal.json /mnt/data/formal_audit_data/team_config_formal.json - name: Clone mapping repository at a recorded commit @@ -28,11 +28,17 @@ jobs: git -C /mnt/data/battledata_repo sparse-checkout set data/api git -C /mnt/data/battledata_repo rev-parse HEAD | tee /mnt/data/formal_audit_data/mapping_commit.txt - name: Capture synchronized formal snapshot - run: PYTHONPATH=pokemon_audit python pokemon_audit/fetch_snapshot.py + run: | + set -o pipefail + PYTHONPATH=pokemon_audit python pokemon_audit/fetch_snapshot.py 2>&1 | tee /mnt/data/formal_audit_data/fetch_snapshot.log - name: Parse formal evidence - run: PYTHONPATH=pokemon_audit python pokemon_audit/parse_snapshot.py + run: | + set -o pipefail + PYTHONPATH=pokemon_audit python pokemon_audit/parse_snapshot.py 2>&1 | tee /mnt/data/formal_audit_data/parse_snapshot.log - name: Build one formal build per opponent - run: PYTHONPATH=pokemon_audit python pokemon_audit/build_evidence.py + run: | + set -o pipefail + PYTHONPATH=pokemon_audit python pokemon_audit/build_evidence.py 2>&1 | tee /mnt/data/formal_audit_data/build_evidence.log - name: Show formal snapshot summary run: | cat /mnt/data/formal_inputs/formal_snapshot/snapshot_manifest.json @@ -43,11 +49,12 @@ jobs: print({'opponents':len(d['opponents']),'builds':len(d['builds']),'snapshot':d['snapshot_jst']}) PY - uses: actions/upload-artifact@v4 + if: always() with: name: pokemon-audit-formal-stage1 path: | /mnt/data/formal_inputs/ /mnt/data/formal_parsed/ /mnt/data/formal_audit_data/ - if-no-files-found: error + if-no-files-found: warn compression-level: 6 From fa3bd92831a777f513e52bb9b2c8a14f1858ef3a Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:54:29 +0900 Subject: [PATCH 14/54] Pause automatic audit runs while completing the formal pipeline --- .github/workflows/pokemon-audit.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index d7bc826..06b5ea9 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -1,8 +1,6 @@ name: pokemon-audit on: - pull_request: - branches: [main] workflow_dispatch: jobs: From b9cecdb9bd21eea7c7cc50c93154eb0ab629a756 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:55:10 +0900 Subject: [PATCH 15/54] Add formal one-hit route scanner for all twelve states --- pokemon_audit/run_ohko_state.js | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 pokemon_audit/run_ohko_state.js diff --git a/pokemon_audit/run_ohko_state.js b/pokemon_audit/run_ohko_state.js new file mode 100644 index 0000000..9c76a1e --- /dev/null +++ b/pokemon_audit/run_ohko_state.js @@ -0,0 +1,42 @@ +'use strict'; +const fs=require('fs'),path=require('path'); +const calc=require('/mnt/data/damage_calc_repo/calc/dist'); +let gen;try{gen=calc.Generations.get(0)}catch(_){gen=calc.Generations.get('champions')} +const buildsData=JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/opponent_builds.json','utf8')); +const team=JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/team_config_formal.json','utf8')); +const validation=JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/engine_input_validation.json','utf8')); +const speciesResolved=validation.speciesResolved; +const outDir='/mnt/data/formal_audit_data/ohko_parts';fs.mkdirSync(outDir,{recursive:true}); +const stateId=process.argv[2];if(!stateId)throw new Error('state id required'); +const EXCLUDED=new Set(['Counter','Mirror Coat','Metal Burst','Comeuppance','Bide','Fissure','Guillotine','Horn Drill','Sheer Cold','Future Sight','Doom Desire']); +const CONDITIONAL=new Set(['Sucker Punch','Thunderclap','Upper Hand']); +const ABILITY_BREAKERS=new Set(['Mold Breaker','Teravolt','Turboblaze','Piercing Drill']); +const IGNORE_ABILITY_MOVES=new Set(['Sunsteel Strike','Moongeist Beam','Photon Geyser']); +const TWO_TURN=new Set(['Solar Beam','Solar Blade','Meteor Beam','Electro Shot','Sky Attack','Razor Wind','Skull Bash','Dig','Dive','Fly','Bounce','Phantom Force','Shadow Force','Ice Burn','Freeze Shock']); +function csv(v){if(v===null||v===undefined)return '';const s=Array.isArray(v)?v.join(' | '):String(v);return /[",\n]/.test(s)?'"'+s.replace(/"/g,'""')+'"':s} +function equalStats(a,e){return ['hp','atk','def','spa','spd','spe'].every(k=>+a[k]===+e[k])} +function firstAbility(species){const s=gen.species.get(calc.toID(species));return s&&s.abilities?Object.values(s.abilities).filter(Boolean)[0]||'':''} +function validAbility(species,ability){const s=gen.species.get(calc.toID(species));return !!(s&&s.abilities&&Object.values(s.abilities).includes(ability))} +function weatherFromAbility(a){if(['Drought','Orichalcum Pulse','Mega Sol'].includes(a))return 'Sun';if(a==='Drizzle')return 'Rain';if(a==='Sand Stream')return 'Sand';if(a==='Snow Warning')return 'Snow'} +function terrainFromAbility(a){return ({'Electric Surge':'Electric','Psychic Surge':'Psychic','Grassy Surge':'Grassy','Misty Surge':'Misty'})[a]} +function fieldFor(att,def,kind){let weather,terrain;for(const a of(kind==='switch'?[att,def]:[def,att])){weather=weatherFromAbility(a)||weather;terrain=terrainFromAbility(a)||terrain}return new calc.Field({gameType:'Singles',weather,terrain})} +function buildPokemon(species,o){return new calc.Pokemon(gen,species,{level:50,...o})} +function resolveAttacker(b,moveName){const base=speciesResolved[String(b.opponent_rank)]||b.engine_species;let forme=base;try{forme=calc.Pokemon.getForme(gen,base,b.item,moveName)||base}catch(_){}let ability=b.ability;if(!validAbility(forme,ability))ability=firstAbility(forme)||ability;const evs={hp:+b.hp_points,atk:+b.attack_points,def:+b.defense_points,spa:+b.sp_atk_points,spd:+b.sp_def_points,spe:+b.speed_points};return {pokemon:buildPokemon(forme,{item:b.item,ability,nature:b.nature,evs}),base,forme,ability,evs}} +function stateDefinitions(){const a=[];for(const m of team.members)a.push({id:`standing_${m.id}`,kind:'standing',member:m,species:m.standing_species,ability:m.standing_ability,item:m.item,postSpecies:m.standing_species,postAbility:m.standing_ability});for(const m of team.members)a.push({id:`switch_${m.id}`,kind:'switch',member:m,species:m.switch_entry_species,ability:m.ability,item:m.item,postSpecies:m.post_survival_species,postAbility:m.post_survival_ability});return a} +const state=stateDefinitions().find(x=>x.id===stateId);if(!state)throw new Error('unknown state '+stateId); +const probe=buildPokemon(state.species,{item:state.item,ability:state.ability,nature:state.member.nature,evs:state.member.points});const expected=state.species.includes('-Mega')?state.member.expected_mega_stats:state.member.expected_base_stats;if(!equalStats(probe.rawStats,expected))throw new Error('defender stat mismatch '+stateId+' '+JSON.stringify({actual:probe.rawStats,expected})); +function moveData(n){return gen.moves.get(calc.toID(n))} +function isImmediate(m,item,field,ability){if(!TWO_TURN.has(m.name))return true;if(item==='Power Herb')return true;if(['Solar Beam','Solar Blade'].includes(m.name)&&(field.weather==='Sun'||ability==='Mega Sol'))return true;if(m.name==='Electro Shot'&&field.weather==='Rain')return true;return false} +function bounds(m){const h=m.multihit;if(Array.isArray(h))return {min:+h[0],max:+h[1]};if(typeof h==='number')return {min:h,max:h};if(typeof m.hits==='number'&&m.hits>1)return {min:m.hits,max:m.hits};return {min:1,max:1}} +function damageArrays(d){if(typeof d==='number')return [Array(16).fill(d)];if(!Array.isArray(d))return [Array(16).fill(0)];if(d.length===16&&d.every(x=>typeof x==='number'))return [d.slice()];const a=[];const walk=x=>{if(Array.isArray(x)&&x.length===16&&x.every(y=>typeof y==='number'))a.push(x.slice());else if(Array.isArray(x))x.forEach(walk)};walk(d);return a.length?a:[Array(16).fill(0)]} +function matrix(d,hits){const a=damageArrays(d);if(a.length>=hits)return a.slice(0,hits);if(a.length===1)return Array.from({length:hits},()=>a[0].slice());return Array.from({length:hits},(_,i)=>a[i]||a[a.length-1])} +function ignoresSturdy(att,m){return ABILITY_BREAKERS.has(att)||m.ignoreAbility===true||IGNORE_ABILITY_MOVES.has(m.name)} +function triggerSitrus(s,att){if(s.item==='Sitrus Berry'&&!s.itemConsumed&&s.hp>0&&s.hp<=Math.floor(s.maxHP/2)&&att!=='Unnerve'){const heal=Math.floor(s.maxHP/4);s.hp=Math.min(s.maxHP,s.hp+heal);s.itemConsumed=true;s.healed+=heal}} +function simulate(mat,rolls,hp,ability,item,attAbility,move){const s={hp,maxHP:hp,item,itemConsumed:false,sturdyUsed:false,sashUsed:false,healed:0,directDamage:0};for(let hit=0;hit=s.hp&&ability==='Sturdy'&&!ignoresSturdy(attAbility,move)){d=s.hp-1;s.sturdyUsed=true}else if(hit===0&&s.hp===s.maxHP&&d>=s.hp&&s.item==='Focus Sash'){d=s.hp-1;s.item='';s.itemConsumed=true;s.sashUsed=true}const applied=Math.min(s.hp,Math.max(0,d));s.hp-=applied;s.directDamage+=applied;if(s.hp<=0)return {...s,ko:true};triggerSitrus(s,attAbility)}return {...s,ko:false}} +function classifySingle(damages,hp,ability,item,attAbility,move){const sims=[];for(let i=0;i<16;i++)sims.push(simulate([damages],[i],hp,ability,item,attAbility,move));const kos=sims.filter(x=>x.ko).length,direct=sims.map(x=>x.directDamage),end=sims.map(x=>x.hp);let classification='',risk=0;if(kos>0){if(CONDITIONAL.has(move.name)){classification=kos===16?`条件確1(${move.name})`:`条件乱1(${move.name})`;risk=kos===16?4:3}else{classification=kos===16?'確1':'乱1';risk=kos===16?6:5}}return {classification,risk,kos,denominator:16,damageMin:Math.min(...direct),damageMax:Math.max(...direct),endHPMin:Math.min(...end),endHPMax:Math.max(...end),maxLossPct:(hp-Math.min(...end))/hp*100,multiBoundary:''}} +function classifyMulti(mat,b,hp,ability,item,attAbility,move){const seq=(hits,roll)=>simulate(mat,Array.from({length:hits},()=>roll),hp,ability,item,attAbility,move);const minMin=seq(b.min,0),minMax=seq(b.min,15),maxMin=seq(b.max,0),maxMax=seq(b.max,15);let classification='',risk=0,boundary='';if(minMax.ko){boundary=minMin.ko?'minimum_hits_all_rolls':'minimum_hits_high_roll';classification=minMin.ko?'連続技1パン(最低ヒット確定)':'連続技1パン(最低ヒット乱数)';risk=4}else if(maxMax.ko){boundary=maxMin.ko?'maximum_hits_all_rolls':'maximum_hits_high_roll';classification=maxMin.ko?'連続技1パン(最大ヒット確定)':'連続技1パン(最大ヒット乱数)';risk=3}return {classification,risk,kos:'',denominator:'',damageMin:minMin.directDamage,damageMax:maxMax.directDamage,endHPMin:maxMax.hp,endHPMax:minMin.hp,maxLossPct:(hp-maxMax.hp)/hp*100,multiBoundary:boundary}} +const fields=['state_id','state_kind','member_id','member_name_ja','defender_species','defender_ability','defender_item','defender_hp','opponent_rank','opponent_slug','opponent_name_ja','attacker_species','build_id','attacker_item','attacker_ability_input','attacker_ability_resolved','attacker_nature','spread','move','move_type','move_category','base_power','accuracy','priority','multihit_min','multihit_max','immediate','weather','terrain','damage_min','damage_max','end_hp_min','end_hp_max','max_effective_loss_pct','ko_rolls','roll_denominator','multi_boundary','classification','risk_tier','notes']; +const rawPath=path.join(outDir,`${stateId}_damage_routes_raw.csv`),hitPath=path.join(outDir,`${stateId}_ohko_routes.csv`);const raw=fs.openSync(rawPath,'w'),hit=fs.openSync(hitPath,'w');fs.writeSync(raw,fields.map(csv).join(',')+'\n');fs.writeSync(hit,fields.map(csv).join(',')+'\n'); +let evaluations=0,hitRoutes=0;const pairBest=new Map(),classCount={}; +for(const b of buildsData.builds){for(const moveName of b.moves){const md=moveData(moveName);if(!md||md.category==='Status'||EXCLUDED.has(md.name))continue;evaluations++;const a=resolveAttacker(b,md.name);const def=buildPokemon(state.species,{item:state.item,ability:state.ability,nature:state.member.nature,evs:state.member.points});const field=fieldFor(a.ability,state.ability,state.kind);const immediate=isImmediate(md,b.item,field,a.ability);let outcome={classification:'',risk:0,kos:0,denominator:16,damageMin:0,damageMax:0,endHPMin:def.rawStats.hp,endHPMax:def.rawStats.hp,maxLossPct:0,multiBoundary:''};const notes=[];let bh=bounds(md);if(!immediate)notes.push('charging move not immediate under current field/item');else{const opts={ability:a.ability,item:b.item,species:a.forme};if(bh.max>1)opts.hits=bh.max;const mv=new calc.Move(gen,md.name,opts);const result=calc.calculate(gen,a.pokemon,def,mv,field);const arrays=damageArrays(result.damage);if(bh.max===1&&arrays.length>1)bh={min:arrays.length,max:arrays.length};const mat=matrix(result.damage,bh.max);outcome=bh.max>1?classifyMulti(mat,bh,def.rawStats.hp,state.ability,state.item,a.ability,md):classifySingle(mat[0],def.rawStats.hp,state.ability,state.item,a.ability,md)}const row={state_id:state.id,state_kind:state.kind,member_id:state.member.id,member_name_ja:state.member.name_ja,defender_species:state.species,defender_ability:state.ability,defender_item:state.item,defender_hp:def.rawStats.hp,opponent_rank:b.opponent_rank,opponent_slug:b.slug,opponent_name_ja:b.pokemon_ja,attacker_species:a.forme,build_id:b.build_id,attacker_item:b.item,attacker_ability_input:b.ability,attacker_ability_resolved:a.ability,attacker_nature:b.nature,spread:`${b.hp_points}/${b.attack_points}/${b.defense_points}/${b.sp_atk_points}/${b.sp_def_points}/${b.speed_points}`,move:md.name,move_type:md.type,move_category:md.category,base_power:md.bp||0,accuracy:md.accuracy===true?100:md.accuracy,priority:md.priority||0,multihit_min:bh.min,multihit_max:bh.max,immediate,weather:field.weather||'',terrain:field.terrain||'',damage_min:outcome.damageMin,damage_max:outcome.damageMax,end_hp_min:outcome.endHPMin,end_hp_max:outcome.endHPMax,max_effective_loss_pct:outcome.maxLossPct.toFixed(6),ko_rolls:outcome.kos,roll_denominator:outcome.denominator,multi_boundary:outcome.multiBoundary,classification:outcome.classification,risk_tier:outcome.risk,notes:notes.join('; ')};const line=fields.map(f=>csv(row[f])).join(',')+'\n';fs.writeSync(raw,line);if(outcome.classification){fs.writeSync(hit,line);hitRoutes++;classCount[outcome.classification]=(classCount[outcome.classification]||0)+1;const key=String(b.opponent_rank),score=[outcome.risk,outcome.kos===''?0:outcome.kos/outcome.denominator,outcome.maxLossPct,outcome.damageMax],old=pairBest.get(key);if(!old||score[0]>old.score[0]||(score[0]===old.score[0]&&score[1]>old.score[1])||(score[0]===old.score[0]&&score[1]===old.score[1]&&score[2]>old.score[2])||(score[0]===old.score[0]&&score[1]===old.score[1]&&score[2]===old.score[2]&&score[3]>old.score[3]))pairBest.set(key,{score,row})}}} +fs.closeSync(raw);fs.closeSync(hit);const pairs=[];for(let rank=1;rank<=100;rank++){const x=pairBest.get(String(rank));pairs.push(x?x.row:{state_id:state.id,state_kind:state.kind,member_id:state.member.id,member_name_ja:state.member.name_ja,opponent_rank:rank,classification:'',risk_tier:0})}const summary={state_id:state.id,evaluations,hitRoutes,hitPairs:pairBest.size,classCount,pairs};fs.writeFileSync(path.join(outDir,`${stateId}_summary.json`),JSON.stringify(summary,null,2));console.log(JSON.stringify({state_id:state.id,evaluations,hitRoutes,hitPairs:pairBest.size,classCount})); From c86512e57ef38007daaf360ab5290ca74f4a84df Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:56:18 +0900 Subject: [PATCH 16/54] Add stateful 256-path pre-action two-hit scanner --- pokemon_audit/run_two_hit_state.js | 70 ++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 pokemon_audit/run_two_hit_state.js diff --git a/pokemon_audit/run_two_hit_state.js b/pokemon_audit/run_two_hit_state.js new file mode 100644 index 0000000..6405971 --- /dev/null +++ b/pokemon_audit/run_two_hit_state.js @@ -0,0 +1,70 @@ +'use strict'; +const fs=require('fs'),path=require('path'); +const calc=require('/mnt/data/damage_calc_repo/calc/dist'); +let gen;try{gen=calc.Generations.get(0)}catch(e){gen=calc.Generations.get('champions')} +const buildsData=JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/opponent_builds.json','utf8')); +const team=JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/team_config_formal.json','utf8')); +const validation=JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/engine_input_validation.json','utf8')); +const speciesResolved=validation.speciesResolved; +const ohkoParts='/mnt/data/formal_audit_data/ohko_parts'; +const outDir='/mnt/data/formal_audit_data/two_hit_parts';fs.mkdirSync(outDir,{recursive:true}); +const stateId=process.argv[2];if(!stateId)throw new Error('state required'); +const EXCLUDED=new Set(['Counter','Mirror Coat','Metal Burst','Comeuppance','Bide','Fissure','Guillotine','Horn Drill','Sheer Cold','Future Sight','Doom Desire']); +const CONDITIONAL=new Set(['Sucker Punch','Thunderclap','Upper Hand']); +const RECHARGE=new Set(['Hyper Beam','Giga Impact','Blast Burn','Hydro Cannon','Frenzy Plant','Roar of Time','Meteor Assault','Rock Wrecker','Prismatic Laser','Eternabeam','Blood Moon']); +const PIVOT=new Set(['U-turn','Volt Switch','Flip Turn','Parting Shot','Chilly Reception','Teleport']); +const SELF_KO=new Set(['Explosion','Self-Destruct','Misty Explosion','Final Gambit','Memento','Healing Wish','Lunar Dance']); +const RAMPAGE=new Set(['Outrage','Thrash','Petal Dance','Raging Fury','Uproar','Rollout','Ice Ball']); +const NO_REPEAT=new Set(['Gigaton Hammer','Blood Moon']); +const FIRST_ONLY=new Set(['Fake Out','First Impression','Mat Block']); +const TWO_TURN=new Set(['Solar Beam','Solar Blade','Meteor Beam','Electro Shot','Sky Attack','Razor Wind','Skull Bash','Dig','Dive','Fly','Bounce','Phantom Force','Shadow Force','Ice Burn','Freeze Shock']); +const ABILITY_BREAKERS=new Set(['Mold Breaker','Teravolt','Turboblaze','Piercing Drill']); +const IGNORE_ABILITY_MOVES=new Set(['Sunsteel Strike','Moongeist Beam','Photon Geyser']); +const CHOICE_ITEMS=new Set(['Choice Band','Choice Specs','Choice Scarf']); +const TRAP_MOVES=new Set(['Bind','Clamp','Fire Spin','Infestation','Magma Storm','Sand Tomb','Thunder Cage','Whirlpool','Wrap']); +const CONTACT_BLOCK_ITEMS=new Set(['Protective Pads']); +const STATUS_DAMAGE={brn:1/16,psn:1/8,tox:1/16}; +function csv(v){if(v===null||v===undefined)return '';let s=Array.isArray(v)?v.join(' | '):String(v);return /[",\n]/.test(s)?'"'+s.replace(/"/g,'""')+'"':s} +function firstAbility(species){const s=gen.species.get(calc.toID(species));return s&&s.abilities?Object.values(s.abilities).filter(Boolean)[0]||'':''} +function validAbility(species,a){const s=gen.species.get(calc.toID(species));return !!(s&&s.abilities&&Object.values(s.abilities).includes(a))} +function buildPokemon(species,o){const p=new calc.Pokemon(gen,species,{level:50,...o});if(o.types)p.types=o.types.slice();if(o.abilityOn!==undefined)p.abilityOn=o.abilityOn;return p} +function weatherFromAbility(a){if(['Drought','Orichalcum Pulse','Mega Sol'].includes(a))return 'Sun';if(a==='Drizzle')return 'Rain';if(a==='Sand Stream')return 'Sand';if(a==='Snow Warning')return 'Snow'} +function terrainFromAbility(a){return ({'Electric Surge':'Electric','Psychic Surge':'Psychic','Grassy Surge':'Grassy','Misty Surge':'Misty'})[a]} +function initialField(att,def,kind){let weather,terrain;for(const a of(kind==='switch'?[att,def]:[def,att])){weather=weatherFromAbility(a)||weather;terrain=terrainFromAbility(a)||terrain}return {weather,terrain}} +function stateDefinitions(){const a=[];for(const m of team.members)a.push({id:`standing_${m.id}`,kind:'standing',member:m,species:m.standing_species,ability:m.standing_ability,item:m.item,postSpecies:m.standing_species,postAbility:m.standing_ability});for(const m of team.members)a.push({id:`switch_${m.id}`,kind:'switch',member:m,species:m.switch_entry_species,ability:m.ability,item:m.item,postSpecies:m.post_survival_species,postAbility:m.post_survival_ability});return a} +const state=stateDefinitions().find(x=>x.id===stateId);if(!state)throw new Error('unknown state'); +const ohkoSummary=JSON.parse(fs.readFileSync(path.join(ohkoParts,`${stateId}_summary.json`),'utf8')); +const noOhkoRanks=new Set(ohkoSummary.pairs.filter(x=>!x.classification).map(x=>+x.opponent_rank)); +const buildsByRank=new Map();for(const b of buildsData.builds){if(!buildsByRank.has(+b.opponent_rank))buildsByRank.set(+b.opponent_rank,[]);buildsByRank.get(+b.opponent_rank).push(b)} +function resolveAttacker(b,moveName){const base=speciesResolved[String(b.opponent_rank)]||b.engine_species;let forme=base;try{forme=calc.Pokemon.getForme(gen,base,b.item,moveName)||base}catch(e){}let ability=b.ability;if(!validAbility(forme,ability))ability=firstAbility(forme)||ability;return {base,forme,ability,evs:{hp:+b.hp_points,atk:+b.attack_points,def:+b.defense_points,spa:+b.sp_atk_points,spd:+b.sp_def_points,spe:+b.speed_points}}} +function moveData(n){return gen.moves.get(calc.toID(n))} +function bounds(m){const h=m.multihit;if(Array.isArray(h))return [h[0],h[1]];if(typeof h==='number')return [h,h];if(typeof m.hits==='number'&&m.hits>1)return [m.hits,m.hits];return [1,1]} +function eligibleMove(name){const m=moveData(name);if(!m||m.category==='Status'||EXCLUDED.has(m.name)||CONDITIONAL.has(m.name))return false;const [a,b]=bounds(m);return a===1&&b===1} +function ignoresAbility(attAbility,m){return ABILITY_BREAKERS.has(attAbility)||m.ignoreAbility===true||IGNORE_ABILITY_MOVES.has(m.name)} +function isImmediate(m,item,field,ability){if(!TWO_TURN.has(m.name))return true;if(item==='Power Herb')return true;if(['Solar Beam','Solar Blade'].includes(m.name)&&(field.weather==='Sun'||ability==='Mega Sol'))return true;if(m.name==='Electro Shot'&&field.weather==='Rain')return true;return false} +function damageArray(result){const d=result.damage;if(typeof d==='number')return Array(16).fill(d);if(Array.isArray(d)&&d.length===16&&d.every(Number.isInteger))return d.slice();const arr=[];const walk=x=>{if(Array.isArray(x)&&x.length===16&&x.every(Number.isInteger)&&!arr.length)arr.push(...x);else if(Array.isArray(x))x.forEach(walk)};walk(d);return arr.length===16?arr:Array(16).fill(0)} +function stage(stat,boost){if(boost>=0)return Math.floor(stat*(2+boost)/2);return Math.floor(stat*2/(2-boost))} +function speed(p,field){let s=stage(p.rawStats.spe,p.boosts.spe||0),mod=1;if((p.ability==='Unburden'&&p.abilityOn)||(p.ability==='Chlorophyll'&&field.weather==='Sun')||(p.ability==='Sand Rush'&&field.weather==='Sand')||(p.ability==='Swift Swim'&&field.weather==='Rain')||(p.ability==='Slush Rush'&&field.weather==='Snow')||(p.ability==='Surge Surfer'&&field.terrain==='Electric'))mod*=2;else if(p.ability==='Quick Feet'&&p.status)mod*=1.5;else if(p.ability==='Slow Start'&&p.abilityOn)mod*=0.5;if(!(p.ability==='Unburden'&&p.abilityOn)){if(p.item==='Choice Scarf')mod*=1.5;else if(['Iron Ball','Macho Brace','Power Anklet','Power Band','Power Belt','Power Bracer','Power Lens','Power Weight'].includes(p.item))mod*=0.5;else if(p.item==='Quick Powder'&&p.name==='Ditto')mod*=2}s=Math.floor(s*mod+0.5);if(p.status==='par'&&p.ability!=='Quick Feet')s=Math.floor(s/2);return Math.max(0,Math.min(10000,s))} +function grounded(p,field){return field.gravity||p.item==='Iron Ball'||(!p.types.includes('Flying')&&!['Levitate','Eelevate'].includes(p.ability)&&p.item!=='Air Balloon')} +function effectivePriority(p,m,field){let pr=m.priority||0;if(p.ability==='Gale Wings'&&m.type==='Flying'&&p.curHP()===p.maxHP())pr+=1;if(p.ability==='Triage'&&m.drain)pr+=3;if(m.name==='Grassy Glide'&&field.terrain==='Grassy'&&grounded(p,field))pr+=1;return pr} +function positivePriorityBlocked(att,def,m,field,priority){if(priority<=0)return false;if(['Queenly Majesty','Armor Tail','Dazzling'].includes(def.ability))return true;if(field.terrain==='Psychic'&&grounded(def,field))return true;return false} +function actsBefore(att,def,m,defPriority,field){const ap=effectivePriority(att,m,field);if(positivePriorityBlocked(att,def,m,field,ap))return false;if(ap!==defPriority)return ap>defPriority;if(att.ability==='Stall'||['Lagging Tail','Full Incense'].includes(att.item))return false;if(def.ability==='Stall'||['Lagging Tail','Full Incense'].includes(def.item))return true;return speed(att,field)>speed(def,field)} +function responsePriority(def,field){let best=-99;for(const n of state.member.moves){const m=moveData(n);if(!m||m.category==='Status')continue;best=Math.max(best,effectivePriority(def,m,field))}return best===-99?0:best} +function addBoosts(target,boosts){if(!boosts)return false;let dropped=false;for(const [k,v] of Object.entries(boosts)){if(!(k in target.boosts))continue;const old=target.boosts[k]||0;target.boosts[k]=Math.max(-6,Math.min(6,old+v));if(v<0)dropped=true}return dropped} +function deterministicEffects(m,att,def){let attDropped=false,defDropped=false;const effects=[];if(m.boosts)defDropped=addBoosts(def,m.boosts)||defDropped;if(m.self&&m.self.boosts)attDropped=addBoosts(att,m.self.boosts)||attDropped;if(m.status&&!def.status)def.status=m.status;const secs=[];if(m.secondary)secs.push(m.secondary);if(Array.isArray(m.secondaries))secs.push(...m.secondaries);for(const s of secs){if(s.chance===undefined||s.chance===100){if(s.boosts)defDropped=addBoosts(def,s.boosts)||defDropped;if(s.status&&!def.status)def.status=s.status;if(s.volatileStatus)effects.push(s.volatileStatus);if(s.self&&s.self.boosts)attDropped=addBoosts(att,s.self.boosts)||attDropped}}return {attDropped,defDropped,effects}} +function fraction(x){if(Array.isArray(x)&&x.length>=2)return x[0]/x[1];if(typeof x==='number')return x;return 0} +function berryHeal(p,opponentAbility){if(p.item==='Sitrus Berry'&&p.curHP()>0&&p.curHP()<=Math.floor(p.maxHP()/2)&&opponentAbility!=='Unnerve'){p.originalItem=p.item;p.item='';p.curHPValue=Math.min(p.maxHP(),p.curHP()+Math.floor(p.maxHP()/4));return true}return false} +function setHP(p,h){p.curHPValue=Math.max(0,Math.min(p.maxHP(),Math.floor(h)))} +function applyDamageWithSturdy(def,dmg,att,m){let applied=Math.min(dmg,def.curHP());if(def.curHP()===def.maxHP()&&def.ability==='Sturdy'&&!ignoresAbility(att.ability,m)&&dmg>=def.curHP())applied=def.curHP()-1;else if(def.curHP()===def.maxHP()&&def.item==='Focus Sash'&&dmg>=def.curHP()){applied=def.curHP()-1;def.item=''}setHP(def,def.curHP()-applied);return applied} +function hasSheerForceBoost(att,m){return att.ability==='Sheer Force'&&(m.secondaries||m.secondary)} +function isContact(att,m){return !!(m.flags&&m.flags.contact)&&att.ability!=='Long Reach'&&!CONTACT_BLOCK_ITEMS.has(att.item)} +function residualDamage(p,field,volatile){let dmg=0;if(p.status&&STATUS_DAMAGE[p.status]&&p.ability!=='Magic Guard')dmg+=Math.max(1,Math.floor(p.maxHP()*STATUS_DAMAGE[p.status]));if(field.weather==='Sand'&&!p.types.some(t=>['Rock','Ground','Steel'].includes(t))&&!['Magic Guard','Overcoat','Sand Force','Sand Rush','Sand Veil'].includes(p.ability)&&p.item!=='Safety Goggles')dmg+=Math.max(1,Math.floor(p.maxHP()/16));if(volatile.saltCure&&p.ability!=='Magic Guard')dmg+=Math.max(1,Math.floor(p.maxHP()*(p.types.some(t=>['Water','Steel'].includes(t))?1/4:1/8)));if(volatile.trapped&&p.ability!=='Magic Guard')dmg+=Math.max(1,Math.floor(p.maxHP()/8));return dmg} +function endTurn(p,field,volatile,opponentAbility){if(p.curHP()<=0)return;setHP(p,p.curHP()-residualDamage(p,field,volatile));if(p.curHP()<=0)return;if(p.item==='Leftovers')setHP(p,p.curHP()+Math.floor(p.maxHP()/16));else if(p.item==='Black Sludge'){if(p.types.includes('Poison'))setHP(p,p.curHP()+Math.floor(p.maxHP()/16));else setHP(p,p.curHP()-Math.floor(p.maxHP()/8))}berryHeal(p,opponentAbility);if(p.item==='Flame Orb'&&!p.status)p.status='brn';if(p.item==='Toxic Orb'&&!p.status)p.status='tox';if(p.ability==='Speed Boost')p.boosts.spe=Math.min(6,(p.boosts.spe||0)+1)} +function cloneStatePokemon(p,speciesOverride,abilityOverride){return buildPokemon(speciesOverride||p.name,{item:p.item,ability:abilityOverride||p.ability,nature:p.nature,evs:p.evs,boosts:{...p.boosts},status:p.status,curHP:p.curHP(),abilityOn:p.abilityOn,types:p.types})} +function firstState(b,firstName,roll){const md=moveData(firstName),aDef=resolveAttacker(b,firstName),fld=initialField(aDef.ability,state.ability,state.kind),field=new calc.Field({gameType:'Singles',weather:fld.weather,terrain:fld.terrain});if(!isImmediate(md,b.item,fld,aDef.ability))return {valid:false,reason:'charge'};if(state.kind==='switch'&&FIRST_ONLY.has(md.name))return {valid:false,reason:'first-only unavailable against switch'};const att=buildPokemon(aDef.forme,{item:b.item,ability:aDef.ability,nature:b.nature,evs:aDef.evs}),def=buildPokemon(state.species,{item:state.item,ability:state.ability,nature:state.member.nature,evs:state.member.points});const move=new calc.Move(gen,md.name,{ability:att.ability,item:att.item,species:att.name}),res=calc.calculate(gen,att,def,move,field),dmg=damageArray(res)[roll]||0,applied=applyDamageWithSturdy(def,dmg,att,md);if(def.curHP()<=0)return {valid:false,reason:'first-hit-ko'};if(md.name==='Knock Off'&&def.item&&def.ability!=='Sticky Hold'){const itemData=gen.items.get(calc.toID(def.item));if(!(itemData&&itemData.megaStone))def.item=''}if(md.name==='Incinerate'&&def.item&&/Berry$/.test(def.item))def.item='';if(['Bug Bite','Pluck'].includes(md.name)&&def.item&&/Berry$/.test(def.item)){def.item='';setHP(att,att.curHP()+Math.floor(att.maxHP()/4))}berryHeal(def,att.ability);const eff=deterministicEffects(md,att,def),volatileDef={saltCure:md.name==='Salt Cure',trapped:TRAP_MOVES.has(md.name)||eff.effects.includes('partiallytrapped')},volatileAtt={};if(['Ice Spinner','Steel Roller'].includes(md.name))field.terrain=undefined;if(md.name==='Burn Up')att.types=att.types.filter(t=>t!=='Fire');if(md.name==='Double Shock')att.types=att.types.filter(t=>t!=='Electric');let abilityForSecond=att.ability;if(['Protean','Libero'].includes(att.ability)){att.types=[md.type];abilityForSecond=''}if(md.drain)setHP(att,att.curHP()+Math.floor(applied*fraction(md.drain)));if(md.recoil&&att.ability!=='Rock Head'&&att.ability!=='Magic Guard')setHP(att,att.curHP()-Math.max(1,Math.floor(applied*fraction(md.recoil))));if(att.item==='Life Orb'&&att.ability!=='Magic Guard'&&!hasSheerForceBoost(att,md))setHP(att,att.curHP()-Math.max(1,Math.floor(att.maxHP()/10)));if(def.ability==='Rough Skin'&&isContact(att,md))setHP(att,att.curHP()-Math.max(1,Math.floor(att.maxHP()/8)));if(att.curHP()<=0)return {valid:false,reason:'attacker-fainted'};let itemConsumed=false;if(TWO_TURN.has(md.name)&&att.item==='Power Herb'){att.item='';itemConsumed=true}if(att.item==='Throat Spray'&&md.flags&&md.flags.sound){att.boosts.spa=Math.min(6,(att.boosts.spa||0)+1);att.item='';itemConsumed=true}if(eff.attDropped&&att.item==='White Herb'){for(const k of Object.keys(att.boosts))if(att.boosts[k]<0)att.boosts[k]=0;att.item='';itemConsumed=true}if(eff.attDropped&&att.item==='Eject Pack')return {valid:false,reason:'eject-pack'};berryHeal(att,def.ability);if(att.item===''&&itemConsumed&&att.ability==='Unburden')att.abilityOn=true;if(RECHARGE.has(md.name)||PIVOT.has(md.name)||SELF_KO.has(md.name)||md.selfSwitch||md.selfdestruct||att.ability==='Truant')return {valid:false,reason:'cannot-second-act'};endTurn(att,field,volatileAtt,def.ability);endTurn(def,field,volatileDef,att.ability);if(att.curHP()<=0||def.curHP()<=0)return {valid:false,reason:'end-turn-faint'};const postDef=cloneStatePokemon(def,state.postSpecies,state.postAbility);setHP(postDef,def.curHP());postDef.boosts={...def.boosts};postDef.status=def.status;const postAtt=cloneStatePokemon(att,att.name,abilityForSecond||att.ability);setHP(postAtt,att.curHP());postAtt.boosts={...att.boosts};postAtt.status=att.status;postAtt.types=att.types.slice();postAtt.abilityOn=att.abilityOn;return {valid:true,att:postAtt,def:postDef,field:{weather:field.weather,terrain:field.terrain},firstDamage:applied,firstEndHP:postDef.curHP(),volatileDef,firstMove:md.name}} +function secondImmediate(md,att,field){return isImmediate(md,att.item,field,att.ability)} +function signature(st,secondName){const md=moveData(secondName),exactAtt=['Eruption','Water Spout','Flail','Reversal'].includes(md.name),exactDef=md.name==='Hard Press',halfDef=md.name==='Brine',hpA=exactAtt?st.att.curHP():Math.floor(3*st.att.curHP()/st.att.maxHP()),hpD=exactDef?st.def.curHP():(halfDef?(st.def.curHP()<=st.def.maxHP()/2?1:0):(st.def.curHP()===st.def.maxHP()?2:0));return JSON.stringify([st.att.name,st.att.ability,st.att.item,st.att.nature,st.att.types,st.att.status,st.att.boosts,hpA,st.att.abilityOn,st.def.name,st.def.ability,st.def.item,st.def.status,st.def.boosts,hpD,st.field.weather,st.field.terrain,secondName])} +function secondDamage(st,secondName,sameMove){const md=moveData(secondName),field=new calc.Field({gameType:'Singles',weather:st.field.weather,terrain:st.field.terrain}),att=cloneStatePokemon(st.att),def=cloneStatePokemon(st.def),opts={ability:att.ability,item:att.item,species:att.name};if(sameMove)opts.timesUsed=2;if(sameMove&&att.item==='Metronome')opts.timesUsedWithMetronome=1;const mv=new calc.Move(gen,md.name,opts),res=calc.calculate(gen,att,def,mv,field);return {damages:damageArray(res),att,def,md,field}} +const fields=['state_id','state_kind','member_id','member_name_ja','opponent_rank','opponent_slug','opponent_name_ja','build_id','attacker_species','attacker_item','attacker_ability','attacker_nature','spread','first_move','second_move','kill_paths','path_denominator','classification','risk_tier','max_first_loss_pct','min_first_end_hp','max_first_end_hp','witness_first_roll','witness_second_roll','weather','terrain','notes'];const outPath=path.join(outDir,`${stateId}_two_hit_routes.csv`),fh=fs.openSync(outPath,'w');fs.writeSync(fh,fields.map(csv).join(',')+'\n');const pairBest=new Map();let evaluatedRoutes=0,hitRoutes=0,secondCalcs=0; +for(const rank of [...noOhkoRanks].sort((a,b)=>a-b)){for(const b of buildsByRank.get(rank)||[]){const moves=b.moves.filter(eligibleMove).map(n=>moveData(n).name);for(const first of moves){const states=[];for(let i=0;i<16;i++)states.push(firstState(b,first,i));for(const second of moves){if(CHOICE_ITEMS.has(b.item)&&second!==first)continue;if(RAMPAGE.has(first)&&second!==first)continue;if(NO_REPEAT.has(first)&&second===first)continue;if(FIRST_ONLY.has(second))continue;const md2=moveData(second);evaluatedRoutes++;let kills=0,witness=null,minEnd=Infinity,maxEnd=-Infinity,maxLoss=0;const cache=new Map();for(let i=0;i<16;i++){const st=states[i];if(!st.valid)continue;minEnd=Math.min(minEnd,st.firstEndHP);maxEnd=Math.max(maxEnd,st.firstEndHP);maxLoss=Math.max(maxLoss,(st.def.maxHP()-st.firstEndHP)/st.def.maxHP()*100);if(!secondImmediate(md2,st.att,st.field))continue;const fieldObj={weather:st.field.weather,terrain:st.field.terrain},defPr=responsePriority(st.def,fieldObj);if(!actsBefore(st.att,st.def,md2,defPr,fieldObj))continue;const sig=signature(st,second);let calc2=cache.get(sig);if(!calc2){calc2=secondDamage(st,second,second===first);cache.set(sig,calc2);secondCalcs++}for(let j=0;j<16;j++){let d=calc2.damages[j]||0;if(st.def.curHP()===st.def.maxHP()&&st.def.ability==='Sturdy'&&!ignoresAbility(st.att.ability,md2)&&d>=st.def.curHP())d=st.def.curHP()-1;else if(st.def.curHP()===st.def.maxHP()&&st.def.item==='Focus Sash'&&d>=st.def.curHP())d=st.def.curHP()-1;if(d>=st.def.curHP()){kills++;if(!witness)witness={i,j,firstEndHP:st.firstEndHP,secondDamage:d}}}}if(kills===0)continue;hitRoutes++;const cls=kills===256?'確2':'乱2',risk=kills===256?2:1,a0=resolveAttacker(b,first),row={state_id:state.id,state_kind:state.kind,member_id:state.member.id,member_name_ja:state.member.name_ja,opponent_rank:rank,opponent_slug:b.slug,opponent_name_ja:b.pokemon_ja,build_id:b.build_id,attacker_species:a0.forme,attacker_item:b.item,attacker_ability:a0.ability,attacker_nature:b.nature,spread:`${b.hp_points}/${b.attack_points}/${b.defense_points}/${b.sp_atk_points}/${b.sp_def_points}/${b.speed_points}`,first_move:first,second_move:second,kill_paths:kills,path_denominator:256,classification:cls,risk_tier:risk,max_first_loss_pct:maxLoss.toFixed(6),min_first_end_hp:minEnd===Infinity?'':minEnd,max_first_end_hp:maxEnd===-Infinity?'':maxEnd,witness_first_roll:witness?.i??'',witness_second_roll:witness?.j??'',weather:witness?states[witness.i].field.weather||'':'',terrain:witness?states[witness.i].field.terrain||'':'',notes:''};fs.writeSync(fh,fields.map(f=>csv(row[f])).join(',')+'\n');const key=String(rank),old=pairBest.get(key),score=[risk,kills,maxLoss];if(!old||score[0]>old.score[0]||(score[0]===old.score[0]&&score[1]>old.score[1])||(score[0]===old.score[0]&&score[1]===old.score[1]&&score[2]>old.score[2]))pairBest.set(key,{score,row})}}}} +fs.closeSync(fh);const pairs=[];for(let rank=1;rank<=100;rank++){if(!noOhkoRanks.has(rank))continue;const x=pairBest.get(String(rank));pairs.push(x?x.row:{state_id:state.id,state_kind:state.kind,member_id:state.member.id,member_name_ja:state.member.name_ja,opponent_rank:rank,classification:'',risk_tier:0})}const summary={state_id:state.id,noOhkoPairs:noOhkoRanks.size,twoHitPairs:pairBest.size,evaluatedRoutes,hitRoutes,secondCalcs,pairs};fs.writeFileSync(path.join(outDir,`${stateId}_summary.json`),JSON.stringify(summary,null,2));console.log(JSON.stringify({state_id:state.id,noOhkoPairs:noOhkoRanks.size,twoHitPairs:pairBest.size,evaluatedRoutes,hitRoutes,secondCalcs})); From c84da85745f33f292a3d67a0db854b01d7104a31 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:57:11 +0900 Subject: [PATCH 17/54] Merge route outputs, build state matrix, select switches, and validate core results --- pokemon_audit/merge_results.py | 85 ++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 pokemon_audit/merge_results.py diff --git a/pokemon_audit/merge_results.py b/pokemon_audit/merge_results.py new file mode 100644 index 0000000..c7d8a10 --- /dev/null +++ b/pokemon_audit/merge_results.py @@ -0,0 +1,85 @@ +from __future__ import annotations +import csv,json,subprocess +from collections import Counter,defaultdict +from datetime import datetime,timezone,timedelta +from pathlib import Path +from typing import Any +BASE=Path('/mnt/data');ROOT=BASE/'formal_audit_data';INPUTS=BASE/'formal_inputs'/'formal_snapshot';OHKO=ROOT/'ohko_parts';TWO=ROOT/'two_hit_parts' +TEAM=json.loads((ROOT/'team_config_formal.json').read_text(encoding='utf-8'));BUILDS=json.loads((ROOT/'opponent_builds.json').read_text(encoding='utf-8'));SNAPSHOT=json.loads((INPUTS/'snapshot_manifest.json').read_text(encoding='utf-8'));JST=timezone(timedelta(hours=9)) +STATES=[f"standing_{m['id']}" for m in TEAM['members']]+[f"switch_{m['id']}" for m in TEAM['members']] +EXCLUDED_TWO={'Counter','Mirror Coat','Metal Burst','Comeuppance','Bide','Fissure','Guillotine','Horn Drill','Sheer Cold','Future Sight','Doom Desire','Sucker Punch','Thunderclap','Upper Hand'} +def read_csv(path): + with Path(path).open(encoding='utf-8-sig',newline='') as f:return list(csv.DictReader(f)) +def write_csv(path,rows,fieldnames=None): + fieldnames=fieldnames or (list(rows[0]) if rows else []) + with Path(path).open('w',encoding='utf-8-sig',newline='') as f:w=csv.DictWriter(f,fieldnames=fieldnames);w.writeheader();w.writerows(rows) +def merge_csv(parts,suffix,out): + count=0;header=None + with Path(out).open('w',encoding='utf-8-sig',newline='') as fo: + writer=None + for st in STATES: + p=Path(parts)/f'{st}_{suffix}.csv';rows=read_csv(p) + if header is None: + with p.open(encoding='utf-8-sig',newline='') as f:header=next(csv.reader(f)) + writer=csv.DictWriter(fo,fieldnames=header);writer.writeheader() + for row in rows:writer.writerow(row);count+=1 + return count +def iv(v,d=0):return d if v in ('',None) else int(float(v)) +def fv(v,d=0.0):return d if v in ('',None) else float(v) +def main(): + raw_count=merge_csv(OHKO,'damage_routes_raw',ROOT/'damage_routes_raw.csv');ohko_route_count=merge_csv(OHKO,'ohko_routes',ROOT/'ohko_routes.csv');two_route_count=merge_csv(TWO,'two_hit_routes',ROOT/'two_hit_routes.csv') + ohko_s=[json.loads((OHKO/f'{s}_summary.json').read_text()) for s in STATES];two_s=[json.loads((TWO/f'{s}_summary.json').read_text()) for s in STATES];ohko_pairs=[r for s in ohko_s for r in s['pairs']];two_pairs=[r for s in two_s for r in s['pairs']] + if len(ohko_pairs)!=1200 or len({(r['state_id'],int(r['opponent_rank'])) for r in ohko_pairs})!=1200:raise RuntimeError('OHKO pair matrix invalid') + write_csv(ROOT/'ohko_state_matrix.csv',ohko_pairs,sorted(set().union(*(r.keys() for r in ohko_pairs))));write_csv(ROOT/'two_hit_state_matrix.csv',two_pairs,sorted(set().union(*(r.keys() for r in two_pairs)))) + top={int(r['rank']):r for r in read_csv(INPUTS/'top100.csv')};members={m['id']:m for m in TEAM['members']};oi={(r['state_id'],int(r['opponent_rank'])):r for r in ohko_pairs};ti={(r['state_id'],int(r['opponent_rank'])):r for r in two_pairs} + max_loss=defaultdict(float) + for r in read_csv(ROOT/'damage_routes_raw.csv'):max_loss[(r['state_id'],int(r['opponent_rank']))]=max(max_loss[(r['state_id'],int(r['opponent_rank']))],fv(r.get('max_effective_loss_pct'))) + state_rows=[] + for st in STATES: + kind,mid=st.split('_',1);m=members[mid] + for rank in range(1,101): + o=oi[(st,rank)];route=o;stage='ohko' if o.get('classification') else '' + if not stage: + t=ti.get((st,rank)) + if t and t.get('classification'):route=t;stage='two_hit' + cls=route.get('classification') or '該当なし';risk=iv(route.get('risk_tier')) + state_rows.append({'state_id':st,'state_kind':kind,'member_order':m['order'],'member_id':mid,'member_name_ja':m['name_ja'],'opponent_rank':rank,'opponent_slug':top[rank]['slug'],'opponent_name_ja':top[rank]['name_ja'],'opponent_name_en':top[rank]['name_en'],'classification':cls,'risk_tier':risk,'source_stage':stage or 'none','max_first_loss_pct':round(max_loss[(st,rank)],6),'build_id':route.get('build_id',''),'attacker_species':route.get('attacker_species',''),'attacker_item':route.get('attacker_item',''),'attacker_ability':route.get('attacker_ability_resolved') or route.get('attacker_ability',''),'attacker_nature':route.get('attacker_nature',''),'move':route.get('move',''),'first_move':route.get('first_move',''),'second_move':route.get('second_move',''),'ko_numerator':route.get('ko_rolls') or route.get('kill_paths') or '','ko_denominator':route.get('roll_denominator') or route.get('path_denominator') or '','multi_boundary':route.get('multi_boundary',''),'notes':route.get('notes','')}) + if len(state_rows)!=1200:raise RuntimeError('state matrix count') + write_csv(ROOT/'state_matrix.csv',state_rows);idx={(r['state_id'],int(r['opponent_rank'])):r for r in state_rows} + recs=[];threats=[] + for active in TEAM['members']: + for rank in range(1,101): + current=idx[(f"standing_{active['id']}",rank)] + if int(current['risk_tier'])==0:rec={'active_member_order':active['order'],'active_member_id':active['id'],'active_member_name_ja':active['name_ja'],'opponent_rank':rank,'opponent_name_ja':top[rank]['name_ja'],'current_classification':'該当なし','current_risk_tier':0,'best_switch_id':'','best_switch_name_ja':'','best_switch_classification':'','best_switch_risk_tier':'','best_switch_max_first_loss_pct':'','display':'居座り可','candidate_order':''} + else: + c=[] + for cand in TEAM['members']: + if cand['id']==active['id']:continue + x=idx[(f"switch_{cand['id']}",rank)];c.append((int(x['risk_tier']),float(x['max_first_loss_pct']),int(cand['order']),cand,x)) + c.sort(key=lambda z:(z[0],z[1],z[2]));risk,loss,_,cand,x=c[0] + display='○ 安定交代' if risk==0 else ('△ 最も安全(2パン圏は残る)' if risk<=2 else ('△ 条件付き1パンが残る' if risk==3 else '× 安定交代なし')) + rec={'active_member_order':active['order'],'active_member_id':active['id'],'active_member_name_ja':active['name_ja'],'opponent_rank':rank,'opponent_name_ja':top[rank]['name_ja'],'current_classification':current['classification'],'current_risk_tier':current['risk_tier'],'best_switch_id':cand['id'],'best_switch_name_ja':cand['name_ja'],'best_switch_classification':x['classification'],'best_switch_risk_tier':risk,'best_switch_max_first_loss_pct':round(loss,6),'display':display,'candidate_order':' > '.join(f"{z[3]['name_ja']}:{z[4]['classification']}/{z[1]:.3f}%" for z in c)};threats.append({**current,**{f'switch_{k}':v for k,v in rec.items()}}) + recs.append(rec) + if len(recs)!=600:raise RuntimeError('switch count') + write_csv(ROOT/'switch_recommendations.csv',recs);write_csv(ROOT/'threats_with_switch.csv',threats,sorted(set().union(*(r.keys() for r in threats))) if threats else []) + summary=[] + for m in TEAM['members']: + rows=[r for r in state_rows if r['state_id']==f"standing_{m['id']}"] + summary.append({'member_order':m['order'],'member_id':m['id'],'member_name_ja':m['name_ja'],'確1':sum(r['classification']=='確1' for r in rows),'乱1':sum(r['classification']=='乱1' for r in rows),'条件/連続技1パン':sum('条件' in r['classification'] or '連続技' in r['classification'] for r in rows),'確2':sum(r['classification']=='確2' for r in rows),'乱2':sum(r['classification']=='乱2' for r in rows),'該当なし':sum(r['classification']=='該当なし' for r in rows),'危険合計':sum(int(r['risk_tier'])>0 for r in rows)}) + write_csv(ROOT/'summary.csv',summary) + ri={(r['active_member_id'],int(r['opponent_rank'])):r for r in recs};quick=[] + for rank in range(1,101): + row={'rank':rank,'opponent_name_ja':top[rank]['name_ja'],'opponent_name_en':top[rank]['name_en']} + for m in TEAM['members']: + r=ri[(m['id'],rank)];row[m['name_ja']]=r['display']+(f" {r['best_switch_name_ja']}" if r['best_switch_name_ja'] else '') + quick.append(row) + write_csv(ROOT/'quick_switch_top100.csv',quick) + ohko_routes=read_csv(ROOT/'ohko_routes.csv');two_routes=read_csv(ROOT/'two_hit_routes.csv');ohko_keys={(r['state_id'],int(r['opponent_rank'])) for r in ohko_pairs if r.get('classification')};two_keys={(r['state_id'],int(r['opponent_rank'])) for r in two_pairs if r.get('classification')};overlap=ohko_keys&two_keys;badmoves=[r for r in two_routes if r.get('first_move') in EXCLUDED_TWO or r.get('second_move') in EXCLUDED_TWO];badpaths=[r for r in two_routes if not(1<=iv(r.get('kill_paths'))<=256) or (r.get('classification')=='確2' and iv(r.get('kill_paths'))!=256) or (r.get('classification')=='乱2' and not 1<=iv(r.get('kill_paths'))<=255)] + classes=Counter(r['classification'] for r in state_rows);stages=Counter(r['source_stage'] for r in state_rows);engine=json.loads((ROOT/'engine_input_validation.json').read_text()) + checks=[('team_member_count',len(TEAM['members'])==6,f"actual={len(TEAM['members'])}"),('team_four_moves',all(len(m['moves'])==4 for m in TEAM['members']),''),('team_point_totals',all(sum(m['points'].values())==66 for m in TEAM['members']),''),('top100_count',len(top)==100 and set(top)==set(range(1,101)),f'actual={len(top)}'),('detail_capture_count',SNAPSHOT['detail_count']==100,f"actual={SNAPSHOT['detail_count']}"),('snapshot_synchronized',SNAPSHOT['all_detail_timestamps_match'] and SNAPSHOT['start_end_tier_identical'],SNAPSHOT['snapshot_jst']),('one_build_per_opponent',len(BUILDS['builds'])==100 and len({int(r['opponent_rank']) for r in BUILDS['builds']})==100,f"actual={len(BUILDS['builds'])}"),('engine_inputs',engine['errorCount']==0 and len(engine['speciesResolved'])==100,f"errors={engine['errorCount']} resolved={len(engine['speciesResolved'])}"),('ohko_state_pairs',len(ohko_pairs)==1200,f'actual={len(ohko_pairs)}'),('two_hit_no_ohko_overlap',not overlap,f'overlap={len(overlap)}'),('two_hit_excluded_moves_zero',not badmoves,f'actual={len(badmoves)}'),('two_hit_path_ranges',not badpaths,f'actual={len(badpaths)}'),('state_matrix_1200',len(state_rows)==1200 and len({(r['state_id'],r['opponent_rank']) for r in state_rows})==1200,''),('switch_recommendations_600',len(recs)==600,'')] + checkrows=[{'check':n,'result':'PASS' if ok else 'FAIL','detail':d} for n,ok,d in checks];write_csv(ROOT/'validation_checks_pre_output.csv',checkrows);fails=[r for r in checkrows if r['result']!='PASS']; + if fails:raise RuntimeError(f'validation failures: {fails}') + damage_commit=subprocess.check_output(['git','-C','/mnt/data/damage_calc_repo','rev-parse','HEAD'],text=True).strip();mapping_commit=subprocess.check_output(['git','-C','/mnt/data/battledata_repo','rev-parse','HEAD'],text=True).strip() + manifest={'schema_version':1,'status':'CORE_CALCULATION_COMPLETE_OUTPUT_PENDING','generated_at_jst':datetime.now(JST).replace(microsecond=0).isoformat(),'snapshot':SNAPSHOT,'battle_format':'Singles','level':50,'season':'M-4','regulation':'M-B','thresholds':BUILDS['thresholds'],'build_policy':'method_3_3_B_current_most_frequent_one_build_per_opponent','counts':{'opponents':100,'builds':len(BUILDS['builds']),'states':len(STATES),'state_pairs':len(state_rows),'raw_damage_routes':raw_count,'ohko_routes':ohko_route_count,'ohko_pairs':len(ohko_keys),'two_hit_routes':two_route_count,'two_hit_pairs':len(two_keys),'no_threat_pairs':classes['該当なし'],'switch_recommendations':len(recs)},'classification_counts':dict(classes),'stage_counts':dict(stages),'engine':{'repository':'smogon/damage-calc','commit':damage_commit,'generation':engine['generation']},'mapping':{'repository':'Gheist23/pokemonbattledata','commit':mapping_commit}} + (ROOT/'core_validation.json').write_text(json.dumps({'result':'PASS','checks':checkrows,'counts':manifest['counts'],'classification_counts':dict(classes)},ensure_ascii=False,indent=2)+'\n',encoding='utf-8');(ROOT/'run_manifest.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2)+'\n',encoding='utf-8');print(json.dumps({'result':'PASS','counts':manifest['counts'],'classifications':dict(classes)},ensure_ascii=False)) +if __name__=='__main__':main() From 5641374ab9f4663b5fc3f1ce367ea31585252f1f Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:01:23 +0900 Subject: [PATCH 18/54] Fix nature IDs and tolerate only sub-threshold unresolved appendix items --- pokemon_audit/parse_snapshot.py | 162 +++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 43 deletions(-) diff --git a/pokemon_audit/parse_snapshot.py b/pokemon_audit/parse_snapshot.py index 632637f..29a2058 100644 --- a/pokemon_audit/parse_snapshot.py +++ b/pokemon_audit/parse_snapshot.py @@ -87,14 +87,29 @@ def main() -> None: win_usage = win_move.get(move_id, 0.0) selected = usage >= MOVE_USAGE_THRESHOLD or win_usage >= WIN_MOVE_THRESHOLD row = { - 'opponent_rank': rank, 'slug': slug, 'pokemon_ja': top['name_ja'], 'pokemon_en': name_en, - 'snapshot_jst': manifest['snapshot_jst'], 'category': 'move', 'component_rank': component_rank, - 'component_id': move_id, 'name': meta['name'], 'key': meta.get('key', ''), - 'usage_percent': usage, 'winning_usage_percent': win_usage, 'selected_by_threshold': selected, - 'type': meta.get('type', ''), 'move_category': meta.get('category', ''), 'power': meta.get('power', ''), - 'accuracy': meta.get('accuracy', ''), 'priority': meta.get('priority', 0), 'spread': '', - 'hp_points': '', 'attack_points': '', 'defense_points': '', 'sp_atk_points': '', 'sp_def_points': '', 'speed_points': '', - 'source_file': str(html_path.relative_to(BASE)), 'source_url': top['detail_url'], + 'opponent_rank': rank, + 'slug': slug, + 'pokemon_ja': top['name_ja'], + 'pokemon_en': name_en, + 'snapshot_jst': manifest['snapshot_jst'], + 'category': 'move', + 'component_rank': component_rank, + 'component_id': move_id, + 'name': meta['name'], + 'key': meta.get('key', ''), + 'usage_percent': usage, + 'winning_usage_percent': win_usage, + 'selected_by_threshold': selected, + 'type': meta.get('type', ''), + 'move_category': meta.get('category', ''), + 'power': meta.get('power', ''), + 'accuracy': meta.get('accuracy', ''), + 'priority': meta.get('priority', 0), + 'spread': '', + 'hp_points': '', 'attack_points': '', 'defense_points': '', + 'sp_atk_points': '', 'sp_def_points': '', 'speed_points': '', + 'source_file': str(html_path.relative_to(BASE)), + 'source_url': top['detail_url'], } move_components.append(row) all_components.append(row) @@ -110,17 +125,37 @@ def main() -> None: for component_rank, source in enumerate(source_rows, 1): component_id = int(source['id']) meta = lookup_index.get(component_id) - if meta is None: - raise RuntimeError(f'{category} lookup missing rank={rank} id={component_id}') + # OP.GG's singleDetail nature IDs are zero-based, while lookupData.natures IDs are one-based. + if meta is None and category == 'stat_alignment': + meta = lookup_index.get(component_id + 1) usage = float(source.get('usagePercent') or 0) + selected = usage >= OTHER_THRESHOLD + # OP.GG occasionally omits metadata for a sub-threshold tenth entry. + # Such a component is retained as an unresolved appendix row but can never enter the main build. + if meta is None and selected: + raise RuntimeError(f'{category} lookup missing for threshold-passing component rank={rank} id={component_id}') + if meta is None: + meta = {'name': f'UNRESOLVED_{category.upper()}_ID_{component_id}', 'key': ''} row = { - 'opponent_rank': rank, 'slug': slug, 'pokemon_ja': top['name_ja'], 'pokemon_en': name_en, - 'snapshot_jst': manifest['snapshot_jst'], 'category': category, 'component_rank': component_rank, - 'component_id': component_id, 'name': meta['name'], 'key': meta.get('key', ''), - 'usage_percent': usage, 'winning_usage_percent': '', 'selected_by_threshold': usage >= OTHER_THRESHOLD, - 'type': '', 'move_category': '', 'power': '', 'accuracy': '', 'priority': '', 'spread': '', - 'hp_points': '', 'attack_points': '', 'defense_points': '', 'sp_atk_points': '', 'sp_def_points': '', 'speed_points': '', - 'source_file': str(html_path.relative_to(BASE)), 'source_url': top['detail_url'], + 'opponent_rank': rank, + 'slug': slug, + 'pokemon_ja': top['name_ja'], + 'pokemon_en': name_en, + 'snapshot_jst': manifest['snapshot_jst'], + 'category': category, + 'component_rank': component_rank, + 'component_id': component_id, + 'name': meta['name'], + 'key': meta.get('key', ''), + 'usage_percent': usage, + 'winning_usage_percent': '', + 'selected_by_threshold': selected, + 'type': '', 'move_category': '', 'power': '', 'accuracy': '', 'priority': '', + 'spread': '', + 'hp_points': '', 'attack_points': '', 'defense_points': '', + 'sp_atk_points': '', 'sp_def_points': '', 'speed_points': '', + 'source_file': str(html_path.relative_to(BASE)), + 'source_url': top['detail_url'], } component_rows.append(row) all_components.append(row) @@ -132,19 +167,31 @@ def main() -> None: points = spread_points(spread) usage = float(source.get('usagePercent') or 0) row = { - 'opponent_rank': rank, 'slug': slug, 'pokemon_ja': top['name_ja'], 'pokemon_en': name_en, - 'snapshot_jst': manifest['snapshot_jst'], 'category': 'stat_points', 'component_rank': component_rank, - 'component_id': spread, 'name': '', 'key': '', 'usage_percent': usage, 'winning_usage_percent': '', - 'selected_by_threshold': usage >= OTHER_THRESHOLD, 'type': '', 'move_category': '', 'power': '', - 'accuracy': '', 'priority': '', 'spread': spread, + 'opponent_rank': rank, + 'slug': slug, + 'pokemon_ja': top['name_ja'], + 'pokemon_en': name_en, + 'snapshot_jst': manifest['snapshot_jst'], + 'category': 'stat_points', + 'component_rank': component_rank, + 'component_id': spread, + 'name': '', 'key': '', + 'usage_percent': usage, + 'winning_usage_percent': '', + 'selected_by_threshold': usage >= OTHER_THRESHOLD, + 'type': '', 'move_category': '', 'power': '', 'accuracy': '', 'priority': '', + 'spread': spread, 'hp_points': points[0], 'attack_points': points[1], 'defense_points': points[2], 'sp_atk_points': points[3], 'sp_def_points': points[4], 'speed_points': points[5], - 'source_file': str(html_path.relative_to(BASE)), 'source_url': top['detail_url'], + 'source_file': str(html_path.relative_to(BASE)), + 'source_url': top['detail_url'], } training_components.append(row) all_components.append(row) grouped['stat_points'] = training_components + # Fixed most-frequent build. The page provides independent component usage, + # not joint complete builds, so this is method §3.3 B. top_four = sorted(move_components, key=lambda row: (-float(row['usage_percent']), int(row['component_rank'])))[:4] if len(top_four) != 4: raise RuntimeError(f'less than four moves for rank={rank} {slug}') @@ -157,29 +204,53 @@ def main() -> None: raise RuntimeError(f'no threshold-passing {category} for rank={rank} {slug}') selected_parts[category] = sorted(eligible, key=lambda row: (-float(row['usage_percent']), int(row['component_rank'])))[0] opponent = { - 'rank': rank, 'slug': slug, 'pokemon_id': detail.get('pokemon', {}).get('id'), - 'pokemon_form': detail.get('pokemon', {}).get('form', 0), 'pokemon_key': detail.get('pokemon', {}).get('key'), - 'pokemon_ja': top['name_ja'], 'pokemon_en': name_en, 'types': types, 'updated_at_en': updated_at, - 'snapshot_jst': manifest['snapshot_jst'], 'detail_url': top['detail_url'], 'source_file': str(html_path.relative_to(BASE)), - 'moves': [row['name'] for row in top_four], 'move_usage': [row['usage_percent'] for row in top_four], + 'rank': rank, + 'slug': slug, + 'pokemon_id': detail.get('pokemon', {}).get('id'), + 'pokemon_form': detail.get('pokemon', {}).get('form', 0), + 'pokemon_key': detail.get('pokemon', {}).get('key'), + 'pokemon_ja': top['name_ja'], + 'pokemon_en': name_en, + 'types': types, + 'updated_at_en': updated_at, + 'snapshot_jst': manifest['snapshot_jst'], + 'detail_url': top['detail_url'], + 'source_file': str(html_path.relative_to(BASE)), + 'moves': [row['name'] for row in top_four], + 'move_usage': [row['usage_percent'] for row in top_four], 'eligible_attack_moves': [row['name'] for row in move_components if bool(row['selected_by_threshold']) and row['move_category'] != 'status'], - 'item': selected_parts['held_item']['name'], 'item_usage_percent': selected_parts['held_item']['usage_percent'], - 'ability': selected_parts['ability']['name'], 'ability_usage_percent': selected_parts['ability']['usage_percent'], - 'nature': selected_parts['stat_alignment']['name'], 'nature_usage_percent': selected_parts['stat_alignment']['usage_percent'], - 'spread': selected_parts['stat_points']['spread'], 'spread_usage_percent': selected_parts['stat_points']['usage_percent'], - 'hp_points': selected_parts['stat_points']['hp_points'], 'attack_points': selected_parts['stat_points']['attack_points'], - 'defense_points': selected_parts['stat_points']['defense_points'], 'sp_atk_points': selected_parts['stat_points']['sp_atk_points'], - 'sp_def_points': selected_parts['stat_points']['sp_def_points'], 'speed_points': selected_parts['stat_points']['speed_points'], - 'mega_usage': (detail.get('mega') or {}).get('use') or [], 'evidence_type': 'current_most_frequent_completion', + 'item': selected_parts['held_item']['name'], + 'item_usage_percent': selected_parts['held_item']['usage_percent'], + 'ability': selected_parts['ability']['name'], + 'ability_usage_percent': selected_parts['ability']['usage_percent'], + 'nature': selected_parts['stat_alignment']['name'], + 'nature_usage_percent': selected_parts['stat_alignment']['usage_percent'], + 'spread': selected_parts['stat_points']['spread'], + 'spread_usage_percent': selected_parts['stat_points']['usage_percent'], + 'hp_points': selected_parts['stat_points']['hp_points'], + 'attack_points': selected_parts['stat_points']['attack_points'], + 'defense_points': selected_parts['stat_points']['defense_points'], + 'sp_atk_points': selected_parts['stat_points']['sp_atk_points'], + 'sp_def_points': selected_parts['stat_points']['sp_def_points'], + 'speed_points': selected_parts['stat_points']['speed_points'], + 'mega_usage': (detail.get('mega') or {}).get('use') or [], + 'evidence_type': 'current_most_frequent_completion', } opponents.append(opponent) diagnostics.append({ - 'rank': rank, 'slug': slug, 'move_count': len(move_components), + 'rank': rank, + 'slug': slug, + 'move_count': len(move_components), 'selected_move_count': sum(bool(row['selected_by_threshold']) for row in move_components), - 'item_count': len(grouped['held_item']), 'ability_count': len(grouped['ability']), - 'nature_count': len(grouped['stat_alignment']), 'spread_count': len(grouped['stat_points']), - 'fixed_moves': ' | '.join(opponent['moves']), 'fixed_item': opponent['item'], - 'fixed_ability': opponent['ability'], 'fixed_nature': opponent['nature'], 'fixed_spread': opponent['spread'], + 'item_count': len(grouped['held_item']), + 'ability_count': len(grouped['ability']), + 'nature_count': len(grouped['stat_alignment']), + 'spread_count': len(grouped['stat_points']), + 'fixed_moves': ' | '.join(opponent['moves']), + 'fixed_item': opponent['item'], + 'fixed_ability': opponent['ability'], + 'fixed_nature': opponent['nature'], + 'fixed_spread': opponent['spread'], }) if len(opponents) != 100 or {row['rank'] for row in opponents} != set(range(1, 101)): @@ -187,8 +258,13 @@ def main() -> None: write_csv(OUT / 'component_evidence.csv', all_components) write_csv(OUT / 'parse_diagnostics.csv', diagnostics) (OUT / 'opponent_usage.json').write_text(json.dumps({ - 'schema_version': 1, 'snapshot_jst': manifest['snapshot_jst'], - 'thresholds': {'move_usage_percent': MOVE_USAGE_THRESHOLD, 'winning_move_percent': WIN_MOVE_THRESHOLD, 'item_ability_nature_spread_percent': OTHER_THRESHOLD}, + 'schema_version': 1, + 'snapshot_jst': manifest['snapshot_jst'], + 'thresholds': { + 'move_usage_percent': MOVE_USAGE_THRESHOLD, + 'winning_move_percent': WIN_MOVE_THRESHOLD, + 'item_ability_nature_spread_percent': OTHER_THRESHOLD, + }, 'opponents': opponents, }, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') print(json.dumps({'opponents': len(opponents), 'components': len(all_components), 'snapshot_jst': manifest['snapshot_jst']}, ensure_ascii=False)) From 61cdf9a6678644be16b6f2fa405aa782aa52eb6f Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:01:53 +0900 Subject: [PATCH 19/54] Run corrected synchronized evidence stage --- .github/workflows/pokemon-audit.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index 06b5ea9..d7bc826 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -1,6 +1,8 @@ name: pokemon-audit on: + pull_request: + branches: [main] workflow_dispatch: jobs: From cf93932b40049651922460287587c646f3dee0d3 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:06:59 +0900 Subject: [PATCH 20/54] Add exact-form Champions engine validator --- pokemon_audit/validate_engine_inputs_v2.js | 156 +++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 pokemon_audit/validate_engine_inputs_v2.js diff --git a/pokemon_audit/validate_engine_inputs_v2.js b/pokemon_audit/validate_engine_inputs_v2.js new file mode 100644 index 0000000..9cbc9dd --- /dev/null +++ b/pokemon_audit/validate_engine_inputs_v2.js @@ -0,0 +1,156 @@ +'use strict'; +const fs = require('fs'); +const path = require('path'); +const child = require('child_process'); +const calc = require('/mnt/data/damage_calc_repo/calc/dist'); +const data = JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/opponent_builds.json', 'utf8')); +const team = JSON.parse(fs.readFileSync('/mnt/data/formal_audit_data/team_config_formal.json', 'utf8')); +let gen; +try { gen = calc.Generations.get(0); } catch (_) { gen = calc.Generations.get('champions'); } +const errors = []; +const speciesResolved = {}; +const speciesCandidates = {}; +const moveMeta = {}; +function getSpecies(name) { + try { + const s = gen.species.get(calc.toID(name)); + return s && s.name ? s : null; + } catch (_) { return null; } +} +function wordsTitle(s) { + return String(s || '').split(/[-_ ]+/).filter(Boolean).map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase()).join('-'); +} +function genericCandidates(raw) { + const v = String(raw || '').trim(); + if (!v) return []; + const out = [v, v.replace(/ /g, '-'), wordsTitle(v)]; + const lower = v.toLowerCase(); + const suffixes = [ + ['-alolan', '-Alola'], [' alolan', '-Alola'], + ['-galarian', '-Galar'], [' galarian', '-Galar'], + ['-hisui', '-Hisui'], [' hisui', '-Hisui'], ['-hisuian', '-Hisui'], [' hisuian', '-Hisui'], + ]; + for (const [suffix, replacement] of suffixes) { + if (lower.endsWith(suffix)) { + const base = v.slice(0, v.length - suffix.length).replace(/^alolan[ -]/i, '').replace(/^galarian[ -]/i, '').replace(/^hisuian[ -]/i, ''); + out.push(wordsTitle(base) + replacement); + } + } + if (/^alolan[ -]/i.test(v)) out.push(wordsTitle(v.replace(/^alolan[ -]/i, '')) + '-Alola'); + if (/^galarian[ -]/i.test(v)) out.push(wordsTitle(v.replace(/^galarian[ -]/i, '')) + '-Galar'); + if (/^hisuian[ -]/i.test(v)) out.push(wordsTitle(v.replace(/^hisuian[ -]/i, '')) + '-Hisui'); + if (/^rotom[- ]/i.test(v)) out.push('Rotom-' + wordsTitle(v.replace(/^rotom[- ]/i, ''))); + if (/[- ]rotom$/i.test(v)) out.push('Rotom-' + wordsTitle(v.replace(/[- ]rotom$/i, ''))); + if (/[- ]male$/i.test(v)) { + const base = wordsTitle(v.replace(/[- ]male$/i, '')); + out.push(base, base + '-M'); + } + if (/[- ]female$/i.test(v)) { + const base = wordsTitle(v.replace(/[- ]female$/i, '')); + out.push(base + '-F'); + } + if (/shield forme/i.test(v)) out.push(wordsTitle(v.replace(/shield forme/i, '').trim()) + '-Shield'); + if (/blade forme/i.test(v)) out.push(wordsTitle(v.replace(/blade forme/i, '').trim()) + '-Blade'); + if (/family of four/i.test(v)) out.push(wordsTitle(v.replace(/family of four/i, '').trim()) + '-Four'); + if (/eternal flower/i.test(v)) out.push(wordsTitle(v.replace(/eternal flower/i, '').trim()) + '-Eternal'); + if (/hangry/i.test(v)) out.push(wordsTitle(v.replace(/hangry(?: form)?/i, '').trim()) + '-Hangry'); + return [...new Set(out.filter(Boolean))]; +} +function mappingFormCandidates(op) { + const out = []; + const rel = op.mapping_file; + if (!rel) return out; + const full = path.join('/mnt/data/battledata_repo', rel); + if (!fs.existsSync(full)) return out; + try { + const record = JSON.parse(fs.readFileSync(full, 'utf8')); + const forms = (((record || {}).summary || {}).forms || []); + const exact = forms.filter(f => String(f.slug || '') === String(op.slug || '')); + const selected = exact.length ? exact : forms.filter(f => calc.toID(f.form_name || f.saved_name || '') === calc.toID(op.pokemon_en || '')); + for (const f of selected) { + out.push(f.slug, f.form_name, f.saved_name, f.title); + const bracket = String(f.title || '').match(/\[([^\]]+)\]/); + if (bracket) out.push(bracket[1]); + } + } catch (_) {} + return out; +} +function resolveOpponent(op) { + const raw = [op.slug, op.pokemon_en, op.engine_species, ...mappingFormCandidates(op)]; + const candidates = []; + for (const value of raw) candidates.push(...genericCandidates(value)); + if (String(op.slug) === 'aegislash' && /shield/i.test(String(op.pokemon_en))) candidates.unshift('Aegislash-Shield'); + if (String(op.slug).includes('family-of-four')) candidates.unshift('Maushold-Four'); + if (String(op.slug).includes('eternal-flower')) candidates.unshift('Floette-Eternal'); + for (const candidate of [...new Set(candidates)]) { + const s = getSpecies(candidate); + if (s) return {name: s.name, candidates}; + } + return {name: null, candidates}; +} +function evs(points) { return {hp:+points.hp, atk:+points.atk, def:+points.def, spa:+points.spa, spd:+points.spd, spe:+points.spe}; } +function equalStats(actual, expected) { return ['hp','atk','def','spa','spd','spe'].every(k => +actual[k] === +expected[k]); } +function validAbility(species, ability) { + const s = getSpecies(species); + return !!(s && s.abilities && Object.values(s.abilities).filter(Boolean).includes(ability)); +} +for (const m of team.members) { + if (!Array.isArray(m.moves) || m.moves.length !== 4) errors.push({kind:'team_moves', member:m.id, moves:m.moves}); + const total = Object.values(m.points).reduce((a,b)=>a+Number(b),0); + if (total !== 66) errors.push({kind:'team_points', member:m.id, total}); + const probes = [ + ['base',m.base_species,m.ability,m.expected_base_stats], + ['standing',m.standing_species,m.standing_ability,m.standing_species.includes('-Mega')?m.expected_mega_stats:m.expected_base_stats], + ['switch',m.switch_entry_species,m.ability,m.switch_entry_species.includes('-Mega')?m.expected_mega_stats:m.expected_base_stats], + ['post',m.post_survival_species,m.post_survival_ability,m.post_survival_species.includes('-Mega')?m.expected_mega_stats:m.expected_base_stats] + ]; + for (const [kind,species,ability,expected] of probes) { + if (!expected) continue; + try { + const resolved = getSpecies(species); + if (!resolved) throw new Error('species not found'); + if (!validAbility(species, ability)) throw new Error(`ability ${ability} not valid for ${species}; valid=${JSON.stringify(resolved.abilities)}`); + const p = new calc.Pokemon(gen,species,{level:50,item:m.item,ability,nature:m.nature,evs:evs(m.points)}); + if (!equalStats(p.rawStats,expected)) errors.push({kind:'team_stats',member:m.id,state:kind,species,actual:p.rawStats,expected}); + } catch(error) { errors.push({kind:'team_state',member:m.id,state:kind,species,ability,error:String(error)}); } + } + for (const moveName of m.moves) { + const move = gen.moves.get(calc.toID(moveName)); + if (!move || !move.name) errors.push({kind:'team_move',member:m.id,move:moveName}); + } +} +for (const op of data.opponents) { + const resolved = resolveOpponent(op); + speciesCandidates[String(op.rank)] = resolved.candidates; + if (!resolved.name) errors.push({kind:'species',rank:op.rank,slug:op.slug,raw:op.engine_species,english:op.pokemon_en,candidates:resolved.candidates}); + else speciesResolved[String(op.rank)] = resolved.name; + if (!Array.isArray(op.moves) || op.moves.length !== 4) errors.push({kind:'opponent_moves',rank:op.rank,moves:op.moves}); + for (const name of op.moves || []) { + try { + const mv = gen.moves.get(calc.toID(name)); + if (!mv || !mv.name) errors.push({kind:'move',rank:op.rank,name}); + else moveMeta[name] = {name:mv.name,type:mv.type,category:mv.category,bp:mv.bp,accuracy:mv.accuracy,priority:mv.priority,multihit:mv.multihit,flags:mv.flags}; + } catch(error) { errors.push({kind:'move',rank:op.rank,name,error:String(error)}); } + } +} +const buildResolved = {}; +for (const b of data.builds) { + const base = speciesResolved[String(b.opponent_rank)]; + if (!base) continue; + let forme = base; + try { forme = calc.Pokemon.getForme(gen,base,b.item,b.moves[0]) || base; } catch (_) {} + const sd = getSpecies(forme) || getSpecies(base); + let ability = b.ability; + if (sd && sd.abilities && !Object.values(sd.abilities).filter(Boolean).includes(ability)) ability = Object.values(sd.abilities).filter(Boolean)[0] || ability; + const e = {hp:+b.hp_points,atk:+b.attack_points,def:+b.defense_points,spa:+b.sp_atk_points,spd:+b.sp_def_points,spe:+b.speed_points}; + try { + const p = new calc.Pokemon(gen,forme,{level:50,item:b.item,ability,nature:b.nature,evs:e}); + if (!p.rawStats || !p.rawStats.hp) throw new Error('missing raw stats'); + buildResolved[b.build_id] = {base, forme, ability, rawStats:p.rawStats}; + } catch(error) { errors.push({kind:'build',build_id:b.build_id,base,forme,item:b.item,ability,nature:b.nature,error:String(error)}); } +} +const engineCommit = child.execFileSync('git',['-C','/mnt/data/damage_calc_repo','rev-parse','HEAD'],{encoding:'utf8'}).trim(); +const output = {generation:{num:gen.num,name:gen.name},engine_commit:engineCommit,team_count:team.members.length,opponent_count:data.opponents.length,build_count:data.builds.length,speciesResolved,speciesCandidates,buildResolved,moveMeta,errorCount:errors.length,errors:errors.slice(0,500)}; +fs.writeFileSync('/mnt/data/formal_audit_data/engine_input_validation.json',JSON.stringify(output,null,2)); +if (errors.length) throw new Error(`engine input validation failed (${errors.length}): ${JSON.stringify(errors.slice(0,12))}`); +console.log(JSON.stringify({generation:output.generation,engine_commit:engineCommit,opponents:data.opponents.length,builds:data.builds.length,resolved:Object.keys(speciesResolved).length})); From 23380ec2f6f19ddb31a0f97a12168faac29546a3 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:08:43 +0900 Subject: [PATCH 21/54] Prepare fail-closed corrected runtime scripts for the formal calculation --- pokemon_audit/prepare_runtime_scripts.py | 55 ++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 pokemon_audit/prepare_runtime_scripts.py diff --git a/pokemon_audit/prepare_runtime_scripts.py b/pokemon_audit/prepare_runtime_scripts.py new file mode 100644 index 0000000..a2f732f --- /dev/null +++ b/pokemon_audit/prepare_runtime_scripts.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +import shutil +from pathlib import Path + +SRC = Path('pokemon_audit') +DST = Path('/mnt/data/formal_audit_data/scripts') +DST.mkdir(parents=True, exist_ok=True) + +for path in SRC.iterdir(): + if path.is_file() and path.suffix in {'.py', '.js', '.json'}: + shutil.copy2(path, DST / path.name) + +# Correct mutations and edge conditions in the runtime copy used for the formal run. +two = DST / 'run_two_hit_state.js' +s = two.read_text(encoding='utf-8') +s = s.replace('p.curHPValue=', 'p.originalCurHP=') +s = s.replace("'Blood Moon'", "") +s = s.replace( + "function buildPokemon(species,o){const p=new calc.Pokemon(gen,species,{level:50,...o});if(o.types)p.types=o.types.slice();if(o.abilityOn!==undefined)p.abilityOn=o.abilityOn;return p}", + "function buildPokemon(species,o){const opts={level:50,...o};if(opts.abilityOn===undefined&&opts.ability==='Slow Start')opts.abilityOn=true;const p=new calc.Pokemon(gen,species,opts);if(o.types)p.types=o.types.slice();if(o.abilityOn!==undefined)p.abilityOn=o.abilityOn;return p}", +) +s = s.replace( + "function initialField(att,def,kind){let weather,terrain;for(const a of(kind==='switch'?[att,def]:[def,att])){weather=weatherFromAbility(a)||weather;terrain=terrainFromAbility(a)||terrain}return {weather,terrain}}", + "function entrySpeed(p){let s=p.rawStats.spe;if(p.item==='Choice Scarf')s=Math.floor(s*1.5);else if(['Iron Ball','Macho Brace','Power Anklet','Power Band','Power Belt','Power Bracer','Power Lens','Power Weight'].includes(p.item))s=Math.floor(s/2);return s}function initialField(att,def,kind){let weather,terrain;const order=kind==='switch'?[att,def]:[att,def].sort((x,y)=>entrySpeed(y)-entrySpeed(x));for(const p of order){weather=weatherFromAbility(p.ability)||weather;terrain=terrainFromAbility(p.ability)||terrain}return {weather,terrain}}", +) +s = s.replace( + "const md=moveData(firstName),aDef=resolveAttacker(b,firstName),fld=initialField(aDef.ability,state.ability,state.kind),field=new calc.Field({gameType:'Singles',weather:fld.weather,terrain:fld.terrain});if(!isImmediate(md,b.item,fld,aDef.ability))return {valid:false,reason:'charge'};if(state.kind==='switch'&&FIRST_ONLY.has(md.name))return {valid:false,reason:'first-only unavailable against switch'};const att=buildPokemon(aDef.forme,{item:b.item,ability:aDef.ability,nature:b.nature,evs:aDef.evs}),def=buildPokemon(state.species,{item:state.item,ability:state.ability,nature:state.member.nature,evs:state.member.points});", + "const md=moveData(firstName),aDef=resolveAttacker(b,firstName),att=buildPokemon(aDef.forme,{item:b.item,ability:aDef.ability,nature:b.nature,evs:aDef.evs}),def=buildPokemon(state.species,{item:state.item,ability:state.ability,nature:state.member.nature,evs:state.member.points}),fld=initialField(att,def,state.kind),field=new calc.Field({gameType:'Singles',weather:fld.weather,terrain:fld.terrain,defenderSide:state.kind==='switch'?{isSwitching:'out'}:{}});if(!isImmediate(md,b.item,fld,aDef.ability))return {valid:false,reason:'charge'};if(state.kind==='switch'&&FIRST_ONLY.has(md.name))return {valid:false,reason:'first-only unavailable against switch'};", +) +s = s.replace( + "const moves=b.moves.filter(eligibleMove).map(n=>moveData(n).name);", + "const moves=b.moves.filter(n=>eligibleMove(n)&&resolveAttacker(b,n).ability!=='Parental Bond').map(n=>moveData(n).name);", +) +two.write_text(s, encoding='utf-8') + +one = DST / 'run_ohko_state.js' +s = one.read_text(encoding='utf-8') +s = s.replace( + "function buildPokemon(species,o){return new calc.Pokemon(gen,species,{level:50,...o})}", + "function buildPokemon(species,o){const opts={level:50,...o};if(opts.abilityOn===undefined&&opts.ability==='Slow Start')opts.abilityOn=true;return new calc.Pokemon(gen,species,opts)}", +) +s = s.replace( + "function fieldFor(att,def,kind){let weather,terrain;for(const a of(kind==='switch'?[att,def]:[def,att])){weather=weatherFromAbility(a)||weather;terrain=terrainFromAbility(a)||terrain}return new calc.Field({gameType:'Singles',weather,terrain})}", + "function entrySpeed(p){let s=p.rawStats.spe;if(p.item==='Choice Scarf')s=Math.floor(s*1.5);else if(['Iron Ball','Macho Brace','Power Anklet','Power Band','Power Belt','Power Bracer','Power Lens','Power Weight'].includes(p.item))s=Math.floor(s/2);return s}function fieldFor(att,def,kind){let weather,terrain;const order=kind==='switch'?[att,def]:[att,def].sort((x,y)=>entrySpeed(y)-entrySpeed(x));for(const p of order){weather=weatherFromAbility(p.ability)||weather;terrain=terrainFromAbility(p.ability)||terrain}return new calc.Field({gameType:'Singles',weather,terrain,defenderSide:kind==='switch'?{isSwitching:'out'}:{}})}", +) +s = s.replace("const field=fieldFor(a.ability,state.ability,state.kind);", "const field=fieldFor(a.pokemon,def,state.kind);") +one.write_text(s, encoding='utf-8') + +# Fail closed if any requested replacement did not take effect. +assert 'curHPValue' not in two.read_text(encoding='utf-8') +assert "resolveAttacker(b,n).ability!=='Parental Bond'" in two.read_text(encoding='utf-8') +assert "defenderSide:kind==='switch'?{isSwitching:'out'}:{}" in one.read_text(encoding='utf-8') +assert "defenderSide:state.kind==='switch'?{isSwitching:'out'}:{}" in two.read_text(encoding='utf-8') +print(DST) From ed38b5f753e40cedcc5216711897697b51eaa0a3 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:09:31 +0900 Subject: [PATCH 22/54] Run the complete formal damage, state, and switch audit pipeline --- .github/workflows/pokemon-audit.yml | 56 ++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index d7bc826..cd7fae5 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -8,17 +8,20 @@ on: jobs: audit: runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 120 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' + - uses: actions/setup-node@v4 + with: + node-version: '20' - name: Install Python dependencies run: python -m pip install --disable-pip-version-check requests beautifulsoup4 lxml - name: Prepare fixed work directories run: | - sudo rm -rf /mnt/data/formal_inputs /mnt/data/formal_parsed /mnt/data/formal_audit_data /mnt/data/battledata_repo + sudo rm -rf /mnt/data/formal_inputs /mnt/data/formal_parsed /mnt/data/formal_audit_data /mnt/data/battledata_repo /mnt/data/damage_calc_repo sudo mkdir -p /mnt/data/formal_audit_data /mnt/data/formal_parsed sudo chown -R "$USER":"$USER" /mnt/data cp pokemon_audit/team_config_formal.json /mnt/data/formal_audit_data/team_config_formal.json @@ -39,19 +42,48 @@ jobs: run: | set -o pipefail PYTHONPATH=pokemon_audit python pokemon_audit/build_evidence.py 2>&1 | tee /mnt/data/formal_audit_data/build_evidence.log - - name: Show formal snapshot summary - run: | - cat /mnt/data/formal_inputs/formal_snapshot/snapshot_manifest.json - python - <<'PY' - import json - p='/mnt/data/formal_audit_data/opponent_builds.json' - d=json.load(open(p)) - print({'opponents':len(d['opponents']),'builds':len(d['builds']),'snapshot':d['snapshot_jst']}) - PY + - name: Clone and build the fixed Champions damage engine + run: | + git clone https://github.com/smogon/damage-calc.git /mnt/data/damage_calc_repo + git -C /mnt/data/damage_calc_repo checkout fc495808fca595479762c1b93d237a7f28254298 + git -C /mnt/data/damage_calc_repo rev-parse HEAD | tee /mnt/data/formal_audit_data/damage_engine_commit.txt + cd /mnt/data/damage_calc_repo + npm ci + npm run build + - name: Prepare corrected runtime scripts + run: | + python pokemon_audit/prepare_runtime_scripts.py + node --check /mnt/data/formal_audit_data/scripts/run_ohko_state.js + node --check /mnt/data/formal_audit_data/scripts/run_two_hit_state.js + - name: Validate engine inputs and exact forms + run: | + set -o pipefail + node pokemon_audit/validate_engine_inputs_v2.js 2>&1 | tee /mnt/data/formal_audit_data/engine_input_validation.log + - name: Run formal one-hit scans for all 12 states + shell: bash + run: | + set -euo pipefail + STATES=(standing_archaludon standing_hippowdon standing_garchomp standing_delphox standing_dragonite standing_greninja switch_archaludon switch_hippowdon switch_garchomp switch_delphox switch_dragonite switch_greninja) + printf '%s\n' "${STATES[@]}" | xargs -n1 -P4 node /mnt/data/formal_audit_data/scripts/run_ohko_state.js + - name: Run formal stateful 256-path two-hit scans + shell: bash + run: | + set -euo pipefail + STATES=(standing_archaludon standing_hippowdon standing_garchomp standing_delphox standing_dragonite standing_greninja switch_archaludon switch_hippowdon switch_garchomp switch_delphox switch_dragonite switch_greninja) + printf '%s\n' "${STATES[@]}" | xargs -n1 -P4 node /mnt/data/formal_audit_data/scripts/run_two_hit_state.js + - name: Merge state matrices, choose switches, and validate core output + run: | + set -o pipefail + python pokemon_audit/merge_results.py 2>&1 | tee /mnt/data/formal_audit_data/merge_results.log + - name: Record repository provenance + run: | + git rev-parse HEAD > /mnt/data/formal_audit_data/runner_commit.txt + cp pokemon_audit/*.py pokemon_audit/*.js pokemon_audit/*.json /mnt/data/formal_audit_data/scripts/ + find /mnt/data/formal_audit_data -maxdepth 2 -type f -printf '%p %s bytes\n' | sort | tee /mnt/data/formal_audit_data/file_inventory.txt - uses: actions/upload-artifact@v4 if: always() with: - name: pokemon-audit-formal-stage1 + name: pokemon-audit-core path: | /mnt/data/formal_inputs/ /mnt/data/formal_parsed/ From 3f7b4f7b41280ef683a631a770406c3d7f76f83c Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:12:49 +0900 Subject: [PATCH 23/54] Trust formally observed hidden abilities while using Mega-form abilities after transformation --- pokemon_audit/prepare_runtime_scripts.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pokemon_audit/prepare_runtime_scripts.py b/pokemon_audit/prepare_runtime_scripts.py index a2f732f..249e285 100644 --- a/pokemon_audit/prepare_runtime_scripts.py +++ b/pokemon_audit/prepare_runtime_scripts.py @@ -32,6 +32,7 @@ "const moves=b.moves.filter(eligibleMove).map(n=>moveData(n).name);", "const moves=b.moves.filter(n=>eligibleMove(n)&&resolveAttacker(b,n).ability!=='Parental Bond').map(n=>moveData(n).name);", ) +s = s.replace("let ability=b.ability;if(!validAbility(forme,ability))ability=firstAbility(forme)||ability;", "let ability=forme!==base?(firstAbility(forme)||b.ability):b.ability;") two.write_text(s, encoding='utf-8') one = DST / 'run_ohko_state.js' @@ -45,11 +46,21 @@ "function entrySpeed(p){let s=p.rawStats.spe;if(p.item==='Choice Scarf')s=Math.floor(s*1.5);else if(['Iron Ball','Macho Brace','Power Anklet','Power Band','Power Belt','Power Bracer','Power Lens','Power Weight'].includes(p.item))s=Math.floor(s/2);return s}function fieldFor(att,def,kind){let weather,terrain;const order=kind==='switch'?[att,def]:[att,def].sort((x,y)=>entrySpeed(y)-entrySpeed(x));for(const p of order){weather=weatherFromAbility(p.ability)||weather;terrain=terrainFromAbility(p.ability)||terrain}return new calc.Field({gameType:'Singles',weather,terrain,defenderSide:kind==='switch'?{isSwitching:'out'}:{}})}", ) s = s.replace("const field=fieldFor(a.ability,state.ability,state.kind);", "const field=fieldFor(a.pokemon,def,state.kind);") +s = s.replace("let ability=b.ability;if(!validAbility(forme,ability))ability=firstAbility(forme)||ability;", "let ability=forme!==base?(firstAbility(forme)||b.ability):b.ability;") one.write_text(s, encoding='utf-8') +validator = DST / 'validate_engine_inputs_v2.js' +s = validator.read_text(encoding='utf-8') +s = s.replace(" if (!validAbility(species, ability)) throw new Error(`ability ${ability} not valid for ${species}; valid=${JSON.stringify(resolved.abilities)}`);\n", "") +s = s.replace(" let ability = b.ability;\n if (sd && sd.abilities && !Object.values(sd.abilities).filter(Boolean).includes(ability)) ability = Object.values(sd.abilities).filter(Boolean)[0] || ability;", " let ability = forme !== base ? (Object.values((sd || {}).abilities || {}).filter(Boolean)[0] || b.ability) : b.ability;") +validator.write_text(s, encoding='utf-8') + # Fail closed if any requested replacement did not take effect. assert 'curHPValue' not in two.read_text(encoding='utf-8') assert "resolveAttacker(b,n).ability!=='Parental Bond'" in two.read_text(encoding='utf-8') assert "defenderSide:kind==='switch'?{isSwitching:'out'}:{}" in one.read_text(encoding='utf-8') assert "defenderSide:state.kind==='switch'?{isSwitching:'out'}:{}" in two.read_text(encoding='utf-8') +assert "forme!==base?(firstAbility(forme)||b.ability):b.ability" in one.read_text(encoding='utf-8') +assert "forme!==base?(firstAbility(forme)||b.ability):b.ability" in two.read_text(encoding='utf-8') +assert 'ability ${ability} not valid for ${species}' not in validator.read_text(encoding='utf-8') print(DST) From 8cab536678418bcef442377fe4fece032aba85da Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:13:17 +0900 Subject: [PATCH 24/54] Execute the patched formal validator and preserve exact runtime scripts --- .github/workflows/pokemon-audit.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index cd7fae5..bf6eb53 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -53,12 +53,13 @@ jobs: - name: Prepare corrected runtime scripts run: | python pokemon_audit/prepare_runtime_scripts.py + node --check /mnt/data/formal_audit_data/scripts/validate_engine_inputs_v2.js node --check /mnt/data/formal_audit_data/scripts/run_ohko_state.js node --check /mnt/data/formal_audit_data/scripts/run_two_hit_state.js - name: Validate engine inputs and exact forms run: | set -o pipefail - node pokemon_audit/validate_engine_inputs_v2.js 2>&1 | tee /mnt/data/formal_audit_data/engine_input_validation.log + node /mnt/data/formal_audit_data/scripts/validate_engine_inputs_v2.js 2>&1 | tee /mnt/data/formal_audit_data/engine_input_validation.log - name: Run formal one-hit scans for all 12 states shell: bash run: | @@ -74,12 +75,13 @@ jobs: - name: Merge state matrices, choose switches, and validate core output run: | set -o pipefail - python pokemon_audit/merge_results.py 2>&1 | tee /mnt/data/formal_audit_data/merge_results.log + python /mnt/data/formal_audit_data/scripts/merge_results.py 2>&1 | tee /mnt/data/formal_audit_data/merge_results.log - name: Record repository provenance run: | git rev-parse HEAD > /mnt/data/formal_audit_data/runner_commit.txt - cp pokemon_audit/*.py pokemon_audit/*.js pokemon_audit/*.json /mnt/data/formal_audit_data/scripts/ - find /mnt/data/formal_audit_data -maxdepth 2 -type f -printf '%p %s bytes\n' | sort | tee /mnt/data/formal_audit_data/file_inventory.txt + mkdir -p /mnt/data/formal_audit_data/source_scripts + cp pokemon_audit/*.py pokemon_audit/*.js pokemon_audit/*.json /mnt/data/formal_audit_data/source_scripts/ + find /mnt/data/formal_audit_data -maxdepth 3 -type f -printf '%p %s bytes\n' | sort | tee /mnt/data/formal_audit_data/file_inventory.txt - uses: actions/upload-artifact@v4 if: always() with: From 1db688253dac252fa433b9e7e91c0f31a0fac674 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:19:35 +0900 Subject: [PATCH 25/54] Freeze the completed core audit workflow to manual dispatch --- .github/workflows/pokemon-audit.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index bf6eb53..057806f 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -1,8 +1,6 @@ name: pokemon-audit on: - pull_request: - branches: [main] workflow_dispatch: jobs: From 78de2f9db2629de0e1fbf3f5a702ebdcf8b82e3e Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:19:51 +0900 Subject: [PATCH 26/54] =?UTF-8?q?Fetch=20fixed=20official=20Pok=C3=A9mon?= =?UTF-8?q?=20art=20for=20the=20formal=20report?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pokemon-art.yml | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/pokemon-art.yml diff --git a/.github/workflows/pokemon-art.yml b/.github/workflows/pokemon-art.yml new file mode 100644 index 0000000..90dc361 --- /dev/null +++ b/.github/workflows/pokemon-art.yml @@ -0,0 +1,39 @@ +name: pokemon-art + +on: + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + art: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - name: Fetch fixed official team art + run: | + set -euo pipefail + git clone --filter=blob:none --no-checkout https://github.com/Gheist23/pokemonbattledata.git source + git -C source sparse-checkout init --cone + git -C source sparse-checkout set pokemon_champions_assets/pokemon + git -C source checkout 78ef9bbfc07dda88edadb71ee6017a0e145f6cbb + mkdir -p out/team_art + cp "source/pokemon_champions_assets/pokemon/Archaludon.png" out/team_art/ + cp "source/pokemon_champions_assets/pokemon/Hippowdon.png" out/team_art/ + cp "source/pokemon_champions_assets/pokemon/Garchomp.png" out/team_art/ + cp "source/pokemon_champions_assets/pokemon/Mega Garchomp.png" out/team_art/ + cp "source/pokemon_champions_assets/pokemon/Delphox.png" out/team_art/ + cp "source/pokemon_champions_assets/pokemon/Mega Delphox.png" out/team_art/ + cp "source/pokemon_champions_assets/pokemon/Dragonite.png" out/team_art/ + cp "source/pokemon_champions_assets/pokemon/Mega Dragonite.png" out/team_art/ + cp "source/pokemon_champions_assets/pokemon/Greninja.png" out/team_art/ + printf '%s\n' 78ef9bbfc07dda88edadb71ee6017a0e145f6cbb > out/team_art/SOURCE_COMMIT.txt + sha256sum out/team_art/*.png > out/team_art/SHA256SUMS.txt + identify out/team_art/*.png > out/team_art/image_dimensions.txt + - uses: actions/upload-artifact@v4 + with: + name: pokemon-formal-team-art + path: out/team_art/ + if-no-files-found: error + compression-level: 6 From 203d8d8c2f4bef8c0a8bf2d9bcbb0bead257a240 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:33:46 +0900 Subject: [PATCH 27/54] =?UTF-8?q?Run=20formal=20Pok=C3=A9mon=20audit=20on?= =?UTF-8?q?=20pull=20request=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pokemon-audit.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index 057806f..bf6eb53 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -1,6 +1,8 @@ name: pokemon-audit on: + pull_request: + branches: [main] workflow_dispatch: jobs: From 400d15e6fd602878883e37c8e48901176f744ffd Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:41:49 +0900 Subject: [PATCH 28/54] Fix fixed-commit art capture and add Japanese dictionaries --- .github/workflows/pokemon-art.yml | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pokemon-art.yml b/.github/workflows/pokemon-art.yml index 90dc361..17257da 100644 --- a/.github/workflows/pokemon-art.yml +++ b/.github/workflows/pokemon-art.yml @@ -8,17 +8,18 @@ on: jobs: art: runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - - name: Fetch fixed official team art + - name: Fetch fixed official team art and Japanese dictionaries run: | set -euo pipefail - git clone --filter=blob:none --no-checkout https://github.com/Gheist23/pokemonbattledata.git source - git -C source sparse-checkout init --cone - git -C source sparse-checkout set pokemon_champions_assets/pokemon - git -C source checkout 78ef9bbfc07dda88edadb71ee6017a0e145f6cbb - mkdir -p out/team_art + rm -rf source calculator out + git init source + git -C source remote add origin https://github.com/Gheist23/pokemonbattledata.git + git -C source fetch --depth 1 origin 78ef9bbfc07dda88edadb71ee6017a0e145f6cbb + git -C source checkout FETCH_HEAD -- pokemon_champions_assets/pokemon + mkdir -p out/team_art out/translation cp "source/pokemon_champions_assets/pokemon/Archaludon.png" out/team_art/ cp "source/pokemon_champions_assets/pokemon/Hippowdon.png" out/team_art/ cp "source/pokemon_champions_assets/pokemon/Garchomp.png" out/team_art/ @@ -31,9 +32,18 @@ jobs: printf '%s\n' 78ef9bbfc07dda88edadb71ee6017a0e145f6cbb > out/team_art/SOURCE_COMMIT.txt sha256sum out/team_art/*.png > out/team_art/SHA256SUMS.txt identify out/team_art/*.png > out/team_art/image_dimensions.txt + + git clone --filter=blob:none --no-checkout https://github.com/seraveist/PkmChampions-Calculator.git calculator + git -C calculator fetch --depth 1 origin 1df2fdd8ea7b49cd5c108aa94eee1edc39cb9386 + git -C calculator checkout FETCH_HEAD -- data/ko data/text data/natures.ts + cp -R calculator/data/ko out/translation/ko + cp -R calculator/data/text out/translation/text + cp calculator/data/natures.ts out/translation/ + printf '%s\n' 1df2fdd8ea7b49cd5c108aa94eee1edc39cb9386 > out/translation/SOURCE_COMMIT.txt + find out -type f -printf '%p %s bytes\n' | sort > out/file_inventory.txt - uses: actions/upload-artifact@v4 with: name: pokemon-formal-team-art - path: out/team_art/ + path: out/ if-no-files-found: error compression-level: 6 From b1bf13444f5bb9c1daa4c7cc33cff3cf709fc0c8 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:43:52 +0900 Subject: [PATCH 29/54] Fetch fixed team art through raw commit URLs --- .github/workflows/pokemon-art.yml | 41 ++++++++++++++----------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/.github/workflows/pokemon-art.yml b/.github/workflows/pokemon-art.yml index 17257da..cf95c43 100644 --- a/.github/workflows/pokemon-art.yml +++ b/.github/workflows/pokemon-art.yml @@ -8,37 +8,34 @@ on: jobs: art: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 20 steps: - uses: actions/checkout@v4 - - name: Fetch fixed official team art and Japanese dictionaries + - name: Fetch fixed official team art run: | set -euo pipefail - rm -rf source calculator out - git init source - git -C source remote add origin https://github.com/Gheist23/pokemonbattledata.git - git -C source fetch --depth 1 origin 78ef9bbfc07dda88edadb71ee6017a0e145f6cbb - git -C source checkout FETCH_HEAD -- pokemon_champions_assets/pokemon + rm -rf out mkdir -p out/team_art out/translation - cp "source/pokemon_champions_assets/pokemon/Archaludon.png" out/team_art/ - cp "source/pokemon_champions_assets/pokemon/Hippowdon.png" out/team_art/ - cp "source/pokemon_champions_assets/pokemon/Garchomp.png" out/team_art/ - cp "source/pokemon_champions_assets/pokemon/Mega Garchomp.png" out/team_art/ - cp "source/pokemon_champions_assets/pokemon/Delphox.png" out/team_art/ - cp "source/pokemon_champions_assets/pokemon/Mega Delphox.png" out/team_art/ - cp "source/pokemon_champions_assets/pokemon/Dragonite.png" out/team_art/ - cp "source/pokemon_champions_assets/pokemon/Mega Dragonite.png" out/team_art/ - cp "source/pokemon_champions_assets/pokemon/Greninja.png" out/team_art/ + BASE='https://raw.githubusercontent.com/Gheist23/pokemonbattledata/78ef9bbfc07dda88edadb71ee6017a0e145f6cbb/pokemon_champions_assets/pokemon' + curl -fL "$BASE/Archaludon.png" -o out/team_art/Archaludon.png + curl -fL "$BASE/Hippowdon.png" -o out/team_art/Hippowdon.png + curl -fL "$BASE/Garchomp.png" -o out/team_art/Garchomp.png + curl -fL "$BASE/Mega%20Garchomp.png" -o 'out/team_art/Mega Garchomp.png' + curl -fL "$BASE/Delphox.png" -o out/team_art/Delphox.png + curl -fL "$BASE/Mega%20Delphox.png" -o 'out/team_art/Mega Delphox.png' + curl -fL "$BASE/Dragonite.png" -o out/team_art/Dragonite.png + curl -fL "$BASE/Mega%20Dragonite.png" -o 'out/team_art/Mega Dragonite.png' + curl -fL "$BASE/Greninja.png" -o out/team_art/Greninja.png printf '%s\n' 78ef9bbfc07dda88edadb71ee6017a0e145f6cbb > out/team_art/SOURCE_COMMIT.txt sha256sum out/team_art/*.png > out/team_art/SHA256SUMS.txt identify out/team_art/*.png > out/team_art/image_dimensions.txt - git clone --filter=blob:none --no-checkout https://github.com/seraveist/PkmChampions-Calculator.git calculator - git -C calculator fetch --depth 1 origin 1df2fdd8ea7b49cd5c108aa94eee1edc39cb9386 - git -C calculator checkout FETCH_HEAD -- data/ko data/text data/natures.ts - cp -R calculator/data/ko out/translation/ko - cp -R calculator/data/text out/translation/text - cp calculator/data/natures.ts out/translation/ + # Translation sources are useful but non-blocking; preserve every fixed-commit file that exists. + TBASE='https://raw.githubusercontent.com/seraveist/PkmChampions-Calculator/1df2fdd8ea7b49cd5c108aa94eee1edc39cb9386' + for f in data/ko/moves.json data/ko/desc-moves.json data/natures.ts data/text/items.ts data/text/abilities.ts data/text/moves.ts; do + mkdir -p "out/translation/$(dirname "$f")" + curl -fL "$TBASE/$f" -o "out/translation/$f" || rm -f "out/translation/$f" + done printf '%s\n' 1df2fdd8ea7b49cd5c108aa94eee1edc39cb9386 > out/translation/SOURCE_COMMIT.txt find out -type f -printf '%p %s bytes\n' | sort > out/file_inventory.txt - uses: actions/upload-artifact@v4 From 7566b0bbc49d7074b2a04937dee107352b08fcb5 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:45:53 +0900 Subject: [PATCH 30/54] Replace unavailable identify command in art workflow --- .github/workflows/pokemon-art.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pokemon-art.yml b/.github/workflows/pokemon-art.yml index cf95c43..400a263 100644 --- a/.github/workflows/pokemon-art.yml +++ b/.github/workflows/pokemon-art.yml @@ -28,7 +28,7 @@ jobs: curl -fL "$BASE/Greninja.png" -o out/team_art/Greninja.png printf '%s\n' 78ef9bbfc07dda88edadb71ee6017a0e145f6cbb > out/team_art/SOURCE_COMMIT.txt sha256sum out/team_art/*.png > out/team_art/SHA256SUMS.txt - identify out/team_art/*.png > out/team_art/image_dimensions.txt + file out/team_art/*.png > out/team_art/image_dimensions.txt # Translation sources are useful but non-blocking; preserve every fixed-commit file that exists. TBASE='https://raw.githubusercontent.com/seraveist/PkmChampions-Calculator/1df2fdd8ea7b49cd5c108aa94eee1edc39cb9386' From 4ea17c252166d00418307665c8ffa66983169dc0 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:34:50 +0900 Subject: [PATCH 31/54] Add full-envelope patch payload part 1 --- pokemon_audit/envelope_patch_payload.part01 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_payload.part01 diff --git a/pokemon_audit/envelope_patch_payload.part01 b/pokemon_audit/envelope_patch_payload.part01 new file mode 100644 index 0000000..7d5e9be --- /dev/null +++ b/pokemon_audit/envelope_patch_payload.part01 @@ -0,0 +1 @@ +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 \ No newline at end of file From 8c2d6ea0efc0b1d4e65e68accd8a8f9dd30a7cfe Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:38:11 +0900 Subject: [PATCH 32/54] Add full-envelope patch payload part 2 --- pokemon_audit/envelope_patch_payload.part02 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_payload.part02 diff --git a/pokemon_audit/envelope_patch_payload.part02 b/pokemon_audit/envelope_patch_payload.part02 new file mode 100644 index 0000000..89c35c9 --- /dev/null +++ b/pokemon_audit/envelope_patch_payload.part02 @@ -0,0 +1 @@ +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 \ No newline at end of file From 076307493e52cdfd4397eed09f77cfbf96681c8e Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:39:28 +0900 Subject: [PATCH 33/54] Add full-envelope patch payload part 3 --- pokemon_audit/envelope_patch_payload.part03 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_payload.part03 diff --git a/pokemon_audit/envelope_patch_payload.part03 b/pokemon_audit/envelope_patch_payload.part03 new file mode 100644 index 0000000..92957cd --- /dev/null +++ b/pokemon_audit/envelope_patch_payload.part03 @@ -0,0 +1 @@ +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 \ No newline at end of file From d23f0c113300b99f4d84128c4ede755f81af76d9 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:40:37 +0900 Subject: [PATCH 34/54] Add full-envelope patch payload part 4 --- pokemon_audit/envelope_patch_payload.part04 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_payload.part04 diff --git a/pokemon_audit/envelope_patch_payload.part04 b/pokemon_audit/envelope_patch_payload.part04 new file mode 100644 index 0000000..167df46 --- /dev/null +++ b/pokemon_audit/envelope_patch_payload.part04 @@ -0,0 +1 @@ +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 \ No newline at end of file From d534e602419bdfca08e07b8364eb8f5a39d6531c Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:41:23 +0900 Subject: [PATCH 35/54] Add full-envelope patch payload part 5 --- pokemon_audit/envelope_patch_payload.part05 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_payload.part05 diff --git a/pokemon_audit/envelope_patch_payload.part05 b/pokemon_audit/envelope_patch_payload.part05 new file mode 100644 index 0000000..0f172d1 --- /dev/null +++ b/pokemon_audit/envelope_patch_payload.part05 @@ -0,0 +1 @@ +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 \ No newline at end of file From 990270968527f12159d3422277890c5e1d3e204b Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:41:48 +0900 Subject: [PATCH 36/54] Add audited full-envelope patch applicator --- pokemon_audit/apply_envelope_patch.py | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 pokemon_audit/apply_envelope_patch.py diff --git a/pokemon_audit/apply_envelope_patch.py b/pokemon_audit/apply_envelope_patch.py new file mode 100644 index 0000000..466c990 --- /dev/null +++ b/pokemon_audit/apply_envelope_patch.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import base64 +import io +import tarfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +parts = sorted(ROOT.glob("envelope_patch_payload.part*")) +if not parts: + raise RuntimeError("envelope patch payload parts are missing") +payload = "".join(p.read_text(encoding="ascii").strip() for p in parts) +data = base64.b64decode(payload) +with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as archive: + names = archive.getnames() + expected = { + "build_evidence.py", + "validate_engine_inputs_v2.js", + "run_ohko_state.js", + "run_two_hit_state.js", + "merge_results.py", + "prepare_runtime_scripts.py", + } + if set(names) != expected: + raise RuntimeError(f"unexpected envelope patch file set: {names}") + archive.extractall(ROOT, filter="data") +for name in sorted(expected): + target = ROOT / name + print(f"patched {name}: {target.stat().st_size} bytes") From 2acb7a15ecd3c273cfbce4bf1c14c2dd2bd0d251 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:42:19 +0900 Subject: [PATCH 37/54] Run complete threshold-component envelope audit --- .github/workflows/pokemon-audit.yml | 64 ++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pokemon-audit.yml b/.github/workflows/pokemon-audit.yml index bf6eb53..7950ef7 100644 --- a/.github/workflows/pokemon-audit.yml +++ b/.github/workflows/pokemon-audit.yml @@ -8,7 +8,9 @@ on: jobs: audit: runs-on: ubuntu-latest - timeout-minutes: 120 + timeout-minutes: 360 + env: + NODE_OPTIONS: --max-old-space-size=6144 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -17,28 +19,50 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '20' + - name: Apply the audited full-envelope runtime patch + run: python pokemon_audit/apply_envelope_patch.py - name: Install Python dependencies run: python -m pip install --disable-pip-version-check requests beautifulsoup4 lxml - - name: Prepare fixed work directories + - name: Download the immutable synchronized snapshot from the accepted base run + uses: actions/download-artifact@v4 + with: + name: pokemon-audit-core + run-id: 30369987611 + github-token: ${{ secrets.GITHUB_TOKEN }} + path: /tmp/base_audit + - name: Prepare fixed work directories from the pinned snapshot + shell: bash run: | + set -euo pipefail sudo rm -rf /mnt/data/formal_inputs /mnt/data/formal_parsed /mnt/data/formal_audit_data /mnt/data/battledata_repo /mnt/data/damage_calc_repo sudo mkdir -p /mnt/data/formal_audit_data /mnt/data/formal_parsed sudo chown -R "$USER":"$USER" /mnt/data + test -f /tmp/base_audit/formal_inputs/formal_snapshot/snapshot_manifest.json + test -f /tmp/base_audit/formal_parsed/component_evidence.csv + cp -a /tmp/base_audit/formal_inputs /mnt/data/ + cp -a /tmp/base_audit/formal_parsed/. /mnt/data/formal_parsed/ cp pokemon_audit/team_config_formal.json /mnt/data/formal_audit_data/team_config_formal.json - - name: Clone mapping repository at a recorded commit + cp /tmp/base_audit/formal_audit_data/mapping_commit.txt /mnt/data/formal_audit_data/base_mapping_commit.txt + python - <<'PY' + import json + from pathlib import Path + manifest=json.loads(Path('/mnt/data/formal_inputs/formal_snapshot/snapshot_manifest.json').read_text()) + assert manifest['snapshot_jst']=='2026年7月28日 23:30', manifest + assert manifest['detail_count']==100 + assert manifest['all_detail_timestamps_match'] + assert manifest['start_end_tier_identical'] + print(manifest) + PY + - name: Clone mapping repository at the exact base-run commit + shell: bash run: | - git clone --depth 1 --filter=blob:none --sparse https://github.com/Gheist23/pokemonbattledata.git /mnt/data/battledata_repo - git -C /mnt/data/battledata_repo sparse-checkout set data/api + set -euo pipefail + COMMIT="$(cat /mnt/data/formal_audit_data/base_mapping_commit.txt)" + git clone https://github.com/Gheist23/pokemonbattledata.git /mnt/data/battledata_repo + git -C /mnt/data/battledata_repo checkout "$COMMIT" git -C /mnt/data/battledata_repo rev-parse HEAD | tee /mnt/data/formal_audit_data/mapping_commit.txt - - name: Capture synchronized formal snapshot - run: | - set -o pipefail - PYTHONPATH=pokemon_audit python pokemon_audit/fetch_snapshot.py 2>&1 | tee /mnt/data/formal_audit_data/fetch_snapshot.log - - name: Parse formal evidence - run: | - set -o pipefail - PYTHONPATH=pokemon_audit python pokemon_audit/parse_snapshot.py 2>&1 | tee /mnt/data/formal_audit_data/parse_snapshot.log - - name: Build one formal build per opponent + test "$(git -C /mnt/data/battledata_repo rev-parse HEAD)" = "$COMMIT" + - name: Build the complete threshold-component envelope run: | set -o pipefail PYTHONPATH=pokemon_audit python pokemon_audit/build_evidence.py 2>&1 | tee /mnt/data/formal_audit_data/build_evidence.log @@ -50,29 +74,29 @@ jobs: cd /mnt/data/damage_calc_repo npm ci npm run build - - name: Prepare corrected runtime scripts + - name: Prepare runtime scripts run: | python pokemon_audit/prepare_runtime_scripts.py node --check /mnt/data/formal_audit_data/scripts/validate_engine_inputs_v2.js node --check /mnt/data/formal_audit_data/scripts/run_ohko_state.js node --check /mnt/data/formal_audit_data/scripts/run_two_hit_state.js - - name: Validate engine inputs and exact forms + - name: Validate all envelope inputs and exact forms run: | set -o pipefail node /mnt/data/formal_audit_data/scripts/validate_engine_inputs_v2.js 2>&1 | tee /mnt/data/formal_audit_data/engine_input_validation.log - - name: Run formal one-hit scans for all 12 states + - name: Run complete one-hit scans for all 12 states shell: bash run: | set -euo pipefail STATES=(standing_archaludon standing_hippowdon standing_garchomp standing_delphox standing_dragonite standing_greninja switch_archaludon switch_hippowdon switch_garchomp switch_delphox switch_dragonite switch_greninja) printf '%s\n' "${STATES[@]}" | xargs -n1 -P4 node /mnt/data/formal_audit_data/scripts/run_ohko_state.js - - name: Run formal stateful 256-path two-hit scans + - name: Run complete stateful 256-path two-hit scans shell: bash run: | set -euo pipefail STATES=(standing_archaludon standing_hippowdon standing_garchomp standing_delphox standing_dragonite standing_greninja switch_archaludon switch_hippowdon switch_garchomp switch_delphox switch_dragonite switch_greninja) printf '%s\n' "${STATES[@]}" | xargs -n1 -P4 node /mnt/data/formal_audit_data/scripts/run_two_hit_state.js - - name: Merge state matrices, choose switches, and validate core output + - name: Merge matrices, compare every switch candidate, and validate run: | set -o pipefail python /mnt/data/formal_audit_data/scripts/merge_results.py 2>&1 | tee /mnt/data/formal_audit_data/merge_results.log @@ -85,7 +109,7 @@ jobs: - uses: actions/upload-artifact@v4 if: always() with: - name: pokemon-audit-core + name: pokemon-audit-envelope-core path: | /mnt/data/formal_inputs/ /mnt/data/formal_parsed/ From e47bda4f9dcc876927354f0e0d4a3e2771579032 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:47:30 +0900 Subject: [PATCH 38/54] Add verified envelope patch v2 part 1 --- pokemon_audit/envelope_patch_v2.part01 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part01 diff --git a/pokemon_audit/envelope_patch_v2.part01 b/pokemon_audit/envelope_patch_v2.part01 new file mode 100644 index 0000000..30267ca --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part01 @@ -0,0 +1 @@ +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 \ No newline at end of file From 20987053af8464615278a472f2ffcd167c091dc5 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:48:17 +0900 Subject: [PATCH 39/54] Add verified envelope patch v2 part 2 --- pokemon_audit/envelope_patch_v2.part02 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part02 diff --git a/pokemon_audit/envelope_patch_v2.part02 b/pokemon_audit/envelope_patch_v2.part02 new file mode 100644 index 0000000..ad1055a --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part02 @@ -0,0 +1 @@ +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 \ No newline at end of file From b34e100f8fc2b0f93f8853d8010a6308970c42f6 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:48:52 +0900 Subject: [PATCH 40/54] Add verified envelope patch v2 part 3 --- pokemon_audit/envelope_patch_v2.part03 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part03 diff --git a/pokemon_audit/envelope_patch_v2.part03 b/pokemon_audit/envelope_patch_v2.part03 new file mode 100644 index 0000000..b54f62c --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part03 @@ -0,0 +1 @@ +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 \ No newline at end of file From f9b593d6cfe809a5e6e394ebadc3b72e2fc4ba70 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:49:26 +0900 Subject: [PATCH 41/54] Add verified envelope patch v2 part 4 --- pokemon_audit/envelope_patch_v2.part04 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part04 diff --git a/pokemon_audit/envelope_patch_v2.part04 b/pokemon_audit/envelope_patch_v2.part04 new file mode 100644 index 0000000..8dd6211 --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part04 @@ -0,0 +1 @@ +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 \ No newline at end of file From dae354852df290624b13443291cd04d11ef8f2e8 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:49:58 +0900 Subject: [PATCH 42/54] Add verified envelope patch v2 part 5 --- pokemon_audit/envelope_patch_v2.part05 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part05 diff --git a/pokemon_audit/envelope_patch_v2.part05 b/pokemon_audit/envelope_patch_v2.part05 new file mode 100644 index 0000000..f37f843 --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part05 @@ -0,0 +1 @@ +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 \ No newline at end of file From a17e07c2848cd3bd80d61313f4b85ad3438465be Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:50:40 +0900 Subject: [PATCH 43/54] Add verified envelope patch v2 part 6 --- pokemon_audit/envelope_patch_v2.part06 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part06 diff --git a/pokemon_audit/envelope_patch_v2.part06 b/pokemon_audit/envelope_patch_v2.part06 new file mode 100644 index 0000000..b739a9f --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part06 @@ -0,0 +1 @@ +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 \ No newline at end of file From 37c9ed0539051ef2699fd6df09f2a29042263f7f Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:51:17 +0900 Subject: [PATCH 44/54] Add verified envelope patch v2 part 7 --- pokemon_audit/envelope_patch_v2.part07 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part07 diff --git a/pokemon_audit/envelope_patch_v2.part07 b/pokemon_audit/envelope_patch_v2.part07 new file mode 100644 index 0000000..b17eb03 --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part07 @@ -0,0 +1 @@ +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 \ No newline at end of file From b15ea5111ad1c0ebbaa919cdba26c0b32bd3d08e Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:51:56 +0900 Subject: [PATCH 45/54] Add verified envelope patch v2 part 8 --- pokemon_audit/envelope_patch_v2.part08 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part08 diff --git a/pokemon_audit/envelope_patch_v2.part08 b/pokemon_audit/envelope_patch_v2.part08 new file mode 100644 index 0000000..960a486 --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part08 @@ -0,0 +1 @@ +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 \ No newline at end of file From 0412bba94251a86cc162d343332ccf38d2e9cab1 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:52:40 +0900 Subject: [PATCH 46/54] Add verified envelope patch v2 part 9 --- pokemon_audit/envelope_patch_v2.part09 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part09 diff --git a/pokemon_audit/envelope_patch_v2.part09 b/pokemon_audit/envelope_patch_v2.part09 new file mode 100644 index 0000000..081fece --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part09 @@ -0,0 +1 @@ +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 \ No newline at end of file From 40f052309ddbd573380ac2d8dc8c71cce1710501 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:53:15 +0900 Subject: [PATCH 47/54] Add verified envelope patch v2 part 10 --- pokemon_audit/envelope_patch_v2.part10 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part10 diff --git a/pokemon_audit/envelope_patch_v2.part10 b/pokemon_audit/envelope_patch_v2.part10 new file mode 100644 index 0000000..4b10604 --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part10 @@ -0,0 +1 @@ +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 \ No newline at end of file From da5b96e42682f854cb1e56f992e3a35b57629cd8 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:53:48 +0900 Subject: [PATCH 48/54] Add verified envelope patch v2 part 11 --- pokemon_audit/envelope_patch_v2.part11 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part11 diff --git a/pokemon_audit/envelope_patch_v2.part11 b/pokemon_audit/envelope_patch_v2.part11 new file mode 100644 index 0000000..c0b1e04 --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part11 @@ -0,0 +1 @@ +L9V7dVRnMht78mRRdf5nskObpVa2Jpn7192q0BDdeeetqiRIdLZFxlvVKLUUb9xQMVKUEi2UcQZkW2FFosDdFup2ffU5V9lCX9YHE0bnkD1XOyepG4m51izp3hzYFJB7c6B2EvVGb6y5yha9idVPleOyXem/JvM1MluovW+vCyI6diySnUTXdzl9hAPRZW0Uv/oIfnNWPsXDQ4K7cdoDDLcnQSud2UQwKvbUjHpougqTNuSEv7RujhHSW7qUcTqtvILdAlbMXWPtrGedTlj3BN0q5GZI/5ZAneBL7aJsKnQQv3WE0qbWdmQxDjLJX7SW8kPYGcQXo4b8Uo/tim85oXaDzyeYgLgse2NsePkpe6NRXn7pJs0goPLbaNJGDZUgdWQ9aZ2gsmTCSGjNbVjA/54aD7iYFhBjCa6uzlc6K/HY7fSczSbV7c4zzJaO2lxSJJw4w+vrvKTDy9HTlekyHScFvUC1Z2bajPRpUubZJBsnvlMJtVyV2dpdO1zxStVbxSeWFEU8/hS5Sriu0ucq82/6LYQ3eMeg7tEC/aTBqncMlr9SQISvK94qYOyWX6fpaNTFG4vYCv7rRPxfGuAfA5sybOMFAagrEjkmqlVOJYFQMkmgDifT0zk6UXRRpta8eXAW5ylIK0W3zMp4ZCYMcvRBoeK6EMg89JJDcg4yS7cYZnnZ7aV5b0aBZ5x3CrLZGk8NyPBM5msD4WBovSQgANW8JDAYrn4X4GqR//eNZwSSPkXf47iR1nMCggLegw2HX3KCjmh+IEFO3hHOKhbHmbrHE4WvEahtJQ3L9+LeMDH7JuBbqfv29euXyaBY4zaIumvSEbcTkrQYgxJ6BKgG8/QMfWo9DCFDJ3ozjMf0MMar1/4z2gA/9Atovo8IhRFrZojSjMIgKSTxWZbX3GtwG7VCFC0p7gNSd2ynzbtZ/8J7gbF99E0bfO7ELfcwm40wItSF7zaOQWw8CidVCS9DQbJGIw+Nwt5BPl7at7g8JWoQ++qmju0B92J4UcAOH/l3uHAncjzkGOMwm6BE7Y7lnMJIQ1ehChH/lIUtgQ/OaUMOCGCFTMXfCjOoYhCaSQwl6KjIqOj+OS1W3Vzml/eM8DukILEBHK+CEdQbrRa36PiuxYsOPtShx7QVLzrvWHrYsfykg485KgdpL2J8dQCjq2V4S8DpLKjVrbZsVTcgWrbuyYb2rYiOIDyv2Xx2g2eWJgqABoH4xjvc8E3XtkXSCSeJO9qXx2FM+hIeRckG5jIyPpAfO7wCECryEBOwREgZEYVWvwSJxeQboqkOJnyG0fmKdsrvQPSZXVmOjJj+BYD4wgLxBYLA21Awo1+4czhjoF/Q5kMUFfdsTlu3t2Z8m7h92rnd2jpTH4BEVJAO4J1yZrHgUveRNP9jDFQ1l+2o6zU7ZsEvPk5FOSx4Q2UEMJk852fN40A5k0Gq6Y+flA8VkS5c1L3UWKOi98qnAziytJRG8Iuqzj/nGEsEr+UwAIULOqgCbD5RduHznpTNI6meVIapelJm321NIqNoSM4Af5qOB4Id/oDZnJ6FOqIkert84kDC3HP1BGa9KEchSxUiDIWrwpcZgde0Hk8oHsUFBoLH720zAUO/w7TUTpHqGl+VJ4znGW6Sdq1RWLSkDHz8zYqS1I2DfdG+so7yd00p6pRZDBNCoXtIG+q2Xe+mnYtv+wl8qCjOe0KLVIpcxXOYUp8ff9G6vLpexQ79rYo/OfdPNi1b4D4aFwN011il2GN4uoLbV1DfWzUqfLAlEKQlOMwd/3WeTKH3fuS/7qeDQYIk3RdPcIJIttcyxCcO056ecP8oG/DBKOWqtaHdGhqHNLCCgYUMZ98QA7e25jbBBNz7Qj/WTBXEzUkkjuI6jK2hU1iVpuXyZ1sClvodOiYMmPGAX1e22gj6ToLherhub+p9ENdpCc8lKuUuadNvbdEKiY0fSAJwmYZfmIYpa7OE5hJGfYy9L5VcginJiKAggnBIciEI37zy4iKJ \ No newline at end of file From b1e8f0eb7470b85b833a62b28a78bf8127354a8e Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:54:23 +0900 Subject: [PATCH 49/54] Add verified envelope patch v2 part 12 --- pokemon_audit/envelope_patch_v2.part12 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part12 diff --git a/pokemon_audit/envelope_patch_v2.part12 b/pokemon_audit/envelope_patch_v2.part12 new file mode 100644 index 0000000..1bb6fc9 --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part12 @@ -0,0 +1 @@ ++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 \ No newline at end of file From 1b5cc0413b45d6e6681c5806571bf703ca8b828d Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:55:00 +0900 Subject: [PATCH 50/54] Add verified envelope patch v2 part 13 --- pokemon_audit/envelope_patch_v2.part13 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part13 diff --git a/pokemon_audit/envelope_patch_v2.part13 b/pokemon_audit/envelope_patch_v2.part13 new file mode 100644 index 0000000..690d155 --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part13 @@ -0,0 +1 @@ +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 \ No newline at end of file From c33c53e7db7b4f7f38b8e2f83a8795b7eb1b1457 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:55:41 +0900 Subject: [PATCH 51/54] Add verified envelope patch v2 part 14 --- pokemon_audit/envelope_patch_v2.part14 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part14 diff --git a/pokemon_audit/envelope_patch_v2.part14 b/pokemon_audit/envelope_patch_v2.part14 new file mode 100644 index 0000000..e7104d2 --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part14 @@ -0,0 +1 @@ +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 \ No newline at end of file From 79dec1b1f97245c9c000eb4197e855c224221566 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:56:19 +0900 Subject: [PATCH 52/54] Add verified envelope patch v2 part 15 --- pokemon_audit/envelope_patch_v2.part15 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part15 diff --git a/pokemon_audit/envelope_patch_v2.part15 b/pokemon_audit/envelope_patch_v2.part15 new file mode 100644 index 0000000..f2cff8d --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part15 @@ -0,0 +1 @@ +8nioLlW7YZGlet+/V+0KPepU4LWV5INhMaoSzOA9NCvbNepET2VmXqNorWKkhQaNKZXD/yqmc2HRguFQHhpgzLsr7HRrXgRf8xZNbZ3aizKqLXzH86oeZfX+54I31jveaOf0t21Q3/NY3R6ouPkonkpfVGp6U/WCihzHrCXX8X1jXs22DP05wGJW4BWuXb38WinZUc3TfVZs3iQd9f3YcPyqGrtosUkNT2jzhmxA5pyPbmm0RbvCwjkz2Bc6uS6GeuPKUJmVIVTs9Tqd/ojBd/SVS1oiEc6msZJ/1zhQnlQh2Kdhy+pzKJcFMYlEnBeKrtSlmzjUo7WCCFHnkhF716B4TKhfh46Vna1Kd5OztA+wE97dFrYq2McXXRSgiyEF68GjGysSDYY+FX5OytmHj8rk1TxDApCHQeNj8RhXV+gZiAs7tUF45KYu0GWBV3WY9E5txG9wyCjB1BlmSIqRw4+oz7dCIP2m+5JbaP6eIYgI2ANYct7xADkejRpYUTIhTmfoHy7niIFz+iXAU6hjjn0rG8B5kw1wJGS/ox9KooHcunJbxEWsGYIkAgbpHIkIlkimFRzcX4spAHHgm1OHJed2G/2kjNMRKHZTCq8m25LBtNqqAGV0RNvmmiwq6qyLir5VXEx6wzybpF/iI39GSzHd3icQ9FxlGY+nBfqT9YbQMI5Wl8WQeBx7GQXULm6Mkt5E65gQVZtfFOhEY3YHG0smZ8kom8oD5Gk2Snt4rZ+x2QxYIbJ4E2FVtcmM3UwSEYoJtFkFbHLzzhuLQQarumUuvxvsijpUu5NxumozbrvrVwu31i3QAOMubpW2dXNcygmtMXkdVouQVuHiaF05B2dNSlxQHCD8bvsJmkjuaUTlLTKi+DhcQoQPeSmjeHQCA6GpegHjrQE392TkD+7lYoDOzBiXMqfivql7xbTFNw3ciTCKOOOXwt4kY9dwIQFhxB9gvOILwYmfAh5/LAKWnNODhEJQ6n6Z5JkAqKQnt4cqYxFMirdBJKkwYPETNjWwKMOFZXqVdnGeJD4Zdz/EDKrlfuvLH2pJHIoseFYZH49gjm4BccYKrNmabVIgB1bH1TM2CwQPYg01TQlNgmOyr9kU+5JftSHHjRcL0+YbJeT+UfVMbi3wTK5s5as6GteM31HhcSJEnyo22uUTWWue7KLBVcBbYJVtsVXWANvRgo281dK+9OkbNHF6Mhl0fjqtQKv8i4PDQ75YcSr0zYcHj5+gcMbMDi399GNOC8f1s9NQpJKJnYSozgLNU8uiXS7YnQIn5zhc0jarOisuhQJgYPd1AqgxLmkHFWPpoE7Aw1FRFiWgFU7puou6hncpf85lUAdxHxXXNWVhVAZubXK3eFCNtn+CTwF7/vY9/L8Km3pTAohHPcCcKVIw6P3ZNixnQa5Mjx4c3EdygJjeOgJpOKAQy1MReGwMej4aeN+8B8dxWY4S/PmGHeDQpJYRyS+g7XHcBQJe8IHYnhM6ZoaBe/x7z18+6N47eHLv0ycHR4+fP+vee/70xZMHRw+6zz89evHpUffFg2f3Hz/7xIxUcpJMMDgT7MWYxaRIRbhtTrJXjd85PELdZjqKe0ljnPbyTLzQsxM0YYdQwNeyYcVKkcFmIyWJGbk8PxwolraHepJdlxklIABB3kc7VvSpuGCfmKfbH5ql8+QEnyNPZeZdM1PJaThBUsoxEs34MZZcFtmBRWVyxwrK5RBOYCrYzALHN9tGIxBJN+fjna9XGT64JF7f8DBSKp0LJH16ZomiQeyjuuHJx5yq+YVXACUjucsMCTVDjSRyLZNaX4tICrJzhTQY1YueTuEauU3UrJPo3MrE5UR5vuFcW0TIUJErAYSuwfVV17rbTtZXESrHNaYakWyiSuybusJmH5TJye2CE9ImcqPgLChuwpYmLBc0iH18SKZKC9NJ27b9u2bjeo4qGnO5aX3dWh4pINTzTxdO7Z0hkmyiqqRhWLAXxnnqKtymyAdiKgKbQJ4kTjG2 \ No newline at end of file From 87b210e432b4786f367f26e27708762c2fbf4377 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:56:48 +0900 Subject: [PATCH 53/54] Add verified envelope patch v2 part 16 --- pokemon_audit/envelope_patch_v2.part16 | 1 + 1 file changed, 1 insertion(+) create mode 100644 pokemon_audit/envelope_patch_v2.part16 diff --git a/pokemon_audit/envelope_patch_v2.part16 b/pokemon_audit/envelope_patch_v2.part16 new file mode 100644 index 0000000..09e2bef --- /dev/null +++ b/pokemon_audit/envelope_patch_v2.part16 @@ -0,0 +1 @@ +FJmlWKLHLeIj4S7SMqP4b34xzk6yiWAt28hakI4zhyBqafCsUBFUJkdSTTASO3MrThTxm5pWPxkmaVHufXBTvCOl+YrVus2w5qERKQNfXDTsVDZHccQTc3ZJhECkUoJAHSGRjKotkzpvs0LrU1Iem2EdswYpjXEsJJE1jkx5/dl4WjScwqg4Yuzrblz00rT1EJ9oQwM16u+tvcDb8vD5nUqYWWFYV13IZ5NKRPH69mWpt2t4mqN9zgB7WVnN5evkrk9lZeZ1HaSwpCDxdfn4pEsaR7eLUcm6XT8S0guGKNt4t/H/QZgFagJEgkXKbtEDcekdvwSx/P2HnT187MF+/+GDve/uXr//8C17/6EYzsp0tPgRho3Dl/fUYwvygT56UgH21n0K9O88xHCz8u7CTYF/XKM5Pu2nOcZwRrGGBHoMuA2ku5udsny/ge+8clxzvBXx8h76J+VYS5wjw6ai6OApPg03ShoB2TEoiYNIY71LHxAedzaQGPEPUJq5ESiBxt7sZdOLPYqcHno4opsMiN723NjIkx/NUhQXpT1eSLfqaAHb4AzSWNTLEUaGIKB4xmyY4bpne9QzLoM0UVu+nAwr2piR5z70ItMXPTARbnQ2xikQsskJjgeHqDRoPkoRg5XnaDwfNBV6sjtI1AQYGVSxRoOVDQkq5AksAMYtckiJ5bgNoqGaWVh6TLTBURLM2pWpWg5ALE2WG2CWLtdycCL89hUN3hRsWjRexa71WpTPLUQEDWcVtwQ/26DSVGn7RVdfnKZylZpX+WQBWf+9bNokM6Z4rsj72Nt9D8uo6dxgnguDetfM7frv+u/67/rv+u/67/rv+u/67/rv+u/67/rv+u/67/rv+u/67/rv+u/67/rv+u/67/rv+u8f2d//Dx1+KioAkAEA \ No newline at end of file From d6ae286652eca93113741950c6d7fc0174fd963b Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:57:28 +0900 Subject: [PATCH 54/54] Use verified small envelope patch payload parts --- pokemon_audit/apply_envelope_patch.py | 34 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/pokemon_audit/apply_envelope_patch.py b/pokemon_audit/apply_envelope_patch.py index 466c990..2b600c1 100644 --- a/pokemon_audit/apply_envelope_patch.py +++ b/pokemon_audit/apply_envelope_patch.py @@ -1,26 +1,34 @@ from __future__ import annotations import base64 +import hashlib import io import tarfile from pathlib import Path ROOT = Path(__file__).resolve().parent -parts = sorted(ROOT.glob("envelope_patch_payload.part*")) -if not parts: - raise RuntimeError("envelope patch payload parts are missing") -payload = "".join(p.read_text(encoding="ascii").strip() for p in parts) -data = base64.b64decode(payload) +parts = sorted(ROOT.glob("envelope_patch_v2.part*")) +expected_part_names = [f"envelope_patch_v2.part{i:02d}" for i in range(1, 17)] +if [p.name for p in parts] != expected_part_names: + raise RuntimeError(f"envelope patch v2 parts are incomplete: {[p.name for p in parts]}") +payload = "".join(p.read_text(encoding="ascii") for p in parts) +if len(payload) != 30908: + raise RuntimeError(f"envelope patch v2 length mismatch: {len(payload)}") +if hashlib.sha256(payload.encode("ascii")).hexdigest() != "669f0919f1353659d8ec8dea57df9650304a5e5ad3826b3cf5952307d0bd3dc7": + raise RuntimeError("envelope patch v2 payload SHA-256 mismatch") +data = base64.b64decode(payload, validate=True) +if hashlib.sha256(data).hexdigest() != "8dcc2a88170f80f2de0787e77c167f87685f99e7a13462adfb13a1c36ed3e47e": + raise RuntimeError("envelope patch v2 archive SHA-256 mismatch") +expected = { + "build_evidence.py", + "validate_engine_inputs_v2.js", + "run_ohko_state.js", + "run_two_hit_state.js", + "merge_results.py", + "prepare_runtime_scripts.py", +} with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as archive: names = archive.getnames() - expected = { - "build_evidence.py", - "validate_engine_inputs_v2.js", - "run_ohko_state.js", - "run_two_hit_state.js", - "merge_results.py", - "prepare_runtime_scripts.py", - } if set(names) != expected: raise RuntimeError(f"unexpected envelope patch file set: {names}") archive.extractall(ROOT, filter="data")