Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
fe0d20a
Add Pokémon Champions audit workflow scaffold
dcftradingcrypt Jul 28, 2026
091a513
Add OP.GG tier scraper diagnostic
dcftradingcrypt Jul 28, 2026
10582a8
Run OP.GG scraper diagnostic in Actions
dcftradingcrypt Jul 28, 2026
6e25328
Add OP.GG individual page diagnostic
dcftradingcrypt Jul 28, 2026
d2d94ff
Inspect OP.GG ranking and individual page HTML
dcftradingcrypt Jul 28, 2026
c88c792
Add Next.js RSC extraction helpers for formal OP.GG snapshot
dcftradingcrypt Jul 28, 2026
d273f45
Add synchronized OP.GG top-100 snapshot collector
dcftradingcrypt Jul 28, 2026
db2adaf
Parse synchronized OP.GG pages into formal component evidence
dcftradingcrypt Jul 28, 2026
fd8b04b
Build one formal most-frequent build per top-100 opponent
dcftradingcrypt Jul 28, 2026
7aa9112
Add fixed six-member formal team configuration
dcftradingcrypt Jul 28, 2026
d17f073
Run synchronized top-100 snapshot and formal evidence build
dcftradingcrypt Jul 28, 2026
e2e5993
Add fixed Champions engine and input validator
dcftradingcrypt Jul 28, 2026
9eb90d8
Preserve stage-one logs and partial formal evidence on failure
dcftradingcrypt Jul 28, 2026
fa3bd92
Pause automatic audit runs while completing the formal pipeline
dcftradingcrypt Jul 28, 2026
b9cecdb
Add formal one-hit route scanner for all twelve states
dcftradingcrypt Jul 28, 2026
c86512e
Add stateful 256-path pre-action two-hit scanner
dcftradingcrypt Jul 28, 2026
c84da85
Merge route outputs, build state matrix, select switches, and validat…
dcftradingcrypt Jul 28, 2026
5641374
Fix nature IDs and tolerate only sub-threshold unresolved appendix items
dcftradingcrypt Jul 28, 2026
61cdf9a
Run corrected synchronized evidence stage
dcftradingcrypt Jul 28, 2026
cf93932
Add exact-form Champions engine validator
dcftradingcrypt Jul 28, 2026
23380ec
Prepare fail-closed corrected runtime scripts for the formal calculation
dcftradingcrypt Jul 28, 2026
ed38b5f
Run the complete formal damage, state, and switch audit pipeline
dcftradingcrypt Jul 28, 2026
3f7b4f7
Trust formally observed hidden abilities while using Mega-form abilit…
dcftradingcrypt Jul 28, 2026
8cab536
Execute the patched formal validator and preserve exact runtime scripts
dcftradingcrypt Jul 28, 2026
1db6882
Freeze the completed core audit workflow to manual dispatch
dcftradingcrypt Jul 28, 2026
78de2f9
Fetch fixed official Pokémon art for the formal report
dcftradingcrypt Jul 28, 2026
203d8d8
Run formal Pokémon audit on pull request updates
dcftradingcrypt Jul 28, 2026
400d15e
Fix fixed-commit art capture and add Japanese dictionaries
dcftradingcrypt Jul 28, 2026
b1bf134
Fetch fixed team art through raw commit URLs
dcftradingcrypt Jul 28, 2026
7566b0b
Replace unavailable identify command in art workflow
dcftradingcrypt Jul 28, 2026
4ea17c2
Add full-envelope patch payload part 1
dcftradingcrypt Jul 28, 2026
8c2d6ea
Add full-envelope patch payload part 2
dcftradingcrypt Jul 28, 2026
0763074
Add full-envelope patch payload part 3
dcftradingcrypt Jul 28, 2026
d23f0c1
Add full-envelope patch payload part 4
dcftradingcrypt Jul 28, 2026
d534e60
Add full-envelope patch payload part 5
dcftradingcrypt Jul 28, 2026
9902709
Add audited full-envelope patch applicator
dcftradingcrypt Jul 28, 2026
2acb7a1
Run complete threshold-component envelope audit
dcftradingcrypt Jul 28, 2026
e47bda4
Add verified envelope patch v2 part 1
dcftradingcrypt Jul 28, 2026
2098705
Add verified envelope patch v2 part 2
dcftradingcrypt Jul 28, 2026
b34e100
Add verified envelope patch v2 part 3
dcftradingcrypt Jul 28, 2026
f9b593d
Add verified envelope patch v2 part 4
dcftradingcrypt Jul 28, 2026
dae3548
Add verified envelope patch v2 part 5
dcftradingcrypt Jul 28, 2026
a17e07c
Add verified envelope patch v2 part 6
dcftradingcrypt Jul 28, 2026
37c9ed0
Add verified envelope patch v2 part 7
dcftradingcrypt Jul 28, 2026
b15ea51
Add verified envelope patch v2 part 8
dcftradingcrypt Jul 28, 2026
0412bba
Add verified envelope patch v2 part 9
dcftradingcrypt Jul 28, 2026
40f0523
Add verified envelope patch v2 part 10
dcftradingcrypt Jul 28, 2026
da5b96e
Add verified envelope patch v2 part 11
dcftradingcrypt Jul 28, 2026
b1e8f0e
Add verified envelope patch v2 part 12
dcftradingcrypt Jul 28, 2026
1b5cc04
Add verified envelope patch v2 part 13
dcftradingcrypt Jul 28, 2026
c33c53e
Add verified envelope patch v2 part 14
dcftradingcrypt Jul 28, 2026
79dec1b
Add verified envelope patch v2 part 15
dcftradingcrypt Jul 28, 2026
87b210e
Add verified envelope patch v2 part 16
dcftradingcrypt Jul 28, 2026
d6ae286
Use verified small envelope patch payload parts
dcftradingcrypt Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/pokemon-art.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: pokemon-art

on:
pull_request:
branches: [main]
workflow_dispatch:

jobs:
art:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Fetch fixed official team art
run: |
set -euo pipefail
rm -rf out
mkdir -p out/team_art out/translation
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
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'
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
with:
name: pokemon-formal-team-art
path: out/
if-no-files-found: error
compression-level: 6
118 changes: 118 additions & 0 deletions .github/workflows/pokemon-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: pokemon-audit

on:
pull_request:
branches: [main]
workflow_dispatch:

jobs:
audit:
runs-on: ubuntu-latest
timeout-minutes: 360
env:
NODE_OPTIONS: --max-old-space-size=6144
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: 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: 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
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: |
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
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
- 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 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 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 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 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 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
- name: Record repository provenance
run: |
git rev-parse HEAD > /mnt/data/formal_audit_data/runner_commit.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:
name: pokemon-audit-envelope-core
path: |
/mnt/data/formal_inputs/
/mnt/data/formal_parsed/
/mnt/data/formal_audit_data/
if-no-files-found: warn
compression-level: 6
37 changes: 37 additions & 0 deletions pokemon_audit/apply_envelope_patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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_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()
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")
153 changes: 153 additions & 0 deletions pokemon_audit/build_evidence.py
Original file line number Diff line number Diff line change
@@ -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()
Loading
Loading