diff --git a/.beans/nuzlocke-tracker-rfg0--core-encounter-processing.md b/.beans/nuzlocke-tracker-rfg0--core-encounter-processing.md index 019b11b..eecf004 100644 --- a/.beans/nuzlocke-tracker-rfg0--core-encounter-processing.md +++ b/.beans/nuzlocke-tracker-rfg0--core-encounter-processing.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-rfg0 title: Core encounter processing -status: in-progress +status: completed type: task priority: normal created_at: 2026-02-11T08:43:12Z -updated_at: 2026-02-11T09:03:52Z +updated_at: 2026-02-11T09:12:59Z parent: nuzlocke-tracker-bs05 blocking: - nuzlocke-tracker-gkcy diff --git a/tools/import-pokedb/import_pokedb/__main__.py b/tools/import-pokedb/import_pokedb/__main__.py index b976551..d1c0064 100644 --- a/tools/import-pokedb/import_pokedb/__main__.py +++ b/tools/import-pokedb/import_pokedb/__main__.py @@ -19,6 +19,7 @@ from pathlib import Path from .loader import load_pokedb_data, load_seed_config from .mappings import PokemonMapper, LocationMapper, build_version_map, map_encounter_method from .processing import filter_encounters_for_game, process_encounters, build_routes +from .sprites import download_sprites SEEDS_DIR_CANDIDATES = [ Path("backend/src/app/seeds"), # from repo root @@ -131,8 +132,11 @@ def main(argv: list[str] | None = None) -> None: print(f" - {m}", file=sys.stderr) # Spot-check pokemon mapping on actual encounter data - form_ids_in_encounters = {e.get("pokemon_form_identifier", "") for e in pokedb.encounters} - form_ids_in_encounters.discard("") + form_ids_in_encounters: set[str] = set() + for e in pokedb.encounters: + fid = e.get("pokemon_form_identifier") + if fid: + form_ids_in_encounters.add(fid) mapped_forms = 0 for fid in form_ids_in_encounters: if pokemon_mapper.lookup(fid) is not None: @@ -182,6 +186,12 @@ def main(argv: list[str] | None = None) -> None: print(f" Routes: {total_routes}") print(f" Encounter entries: {total_enc}") + # Download sprites for all encountered pokemon + print("\nDownloading sprites...") + sprites_dir = output_dir / "sprites" + sprite_map = download_sprites(pokemon_mapper, form_ids_in_encounters, sprites_dir) + print(f" Sprite map covers {len(sprite_map)} forms") + print("\nProcessing complete. Output not yet written (subtask gkcy).") diff --git a/tools/import-pokedb/import_pokedb/mappings.py b/tools/import-pokedb/import_pokedb/mappings.py index 1b1f245..973a0a5 100644 --- a/tools/import-pokedb/import_pokedb/mappings.py +++ b/tools/import-pokedb/import_pokedb/mappings.py @@ -464,6 +464,19 @@ class PokemonMapper: return None + def get_sprite_url(self, pokemon_form_identifier: str | None) -> str | None: + """Get the PokeDB CDN sprite URL (100x100 medium) for a form identifier.""" + if not pokemon_form_identifier: + return None + form_record = self._pokedb_form_index.get(pokemon_form_identifier, {}) + return form_record.get("main_image_normal_path_medium") + + def get_form_data(self, pokemon_form_identifier: str | None) -> dict | None: + """Get the full PokeDB form record for a form identifier.""" + if not pokemon_form_identifier: + return None + return self._pokedb_form_index.get(pokemon_form_identifier) + def report_unmapped(self) -> None: """Print warnings for any unmapped identifiers.""" if self._unmapped: diff --git a/tools/import-pokedb/import_pokedb/sprites.py b/tools/import-pokedb/import_pokedb/sprites.py new file mode 100644 index 0000000..7fc451d --- /dev/null +++ b/tools/import-pokedb/import_pokedb/sprites.py @@ -0,0 +1,78 @@ +"""Download and manage PokeDB pokemon sprites.""" + +from __future__ import annotations + +import sys +import urllib.request +from pathlib import Path +from typing import Any + +from .mappings import PokemonMapper + + +def download_sprites( + pokemon_mapper: PokemonMapper, + encountered_form_ids: set[str], + sprites_dir: Path, +) -> dict[str, str]: + """Download sprites for all encountered pokemon forms. + + Returns a mapping of pokemon_form_identifier → local sprite filename. + Skips already-downloaded sprites. + """ + sprites_dir.mkdir(parents=True, exist_ok=True) + + to_download: list[tuple[str, str, Path]] = [] # (form_id, url, dest) + result: dict[str, str] = {} + + for form_id in sorted(encountered_form_ids): + info = pokemon_mapper.lookup(form_id) + if info is None: + continue + + pokeapi_id, _ = info + sprite_url = pokemon_mapper.get_sprite_url(form_id) + if not sprite_url: + continue + + filename = f"{pokeapi_id}.webp" + dest = sprites_dir / filename + result[form_id] = filename + + if not dest.exists(): + to_download.append((form_id, sprite_url, dest)) + + if not to_download: + print(f" Sprites: {len(result)} already cached") + return result + + print(f" Downloading {len(to_download)} sprites ({len(result) - len(to_download)} cached)...") + + failed = 0 + for i, (form_id, url, dest) in enumerate(to_download, 1): + try: + urllib.request.urlretrieve(url, dest) + except Exception as e: + print(f" Warning: Failed to download sprite for {form_id}: {e}", file=sys.stderr) + failed += 1 + # Remove the failed entry from results + result.pop(form_id, None) + + # Progress every 100 + if i % 100 == 0: + print(f" {i}/{len(to_download)}...") + + if failed: + print(f" Sprites: {len(result)} downloaded, {failed} failed") + else: + print(f" Sprites: {len(result)} total ({len(to_download)} new)") + + return result + + +def sprite_path_for_pokemon(pokeapi_id: int, sprites_dir_name: str = "sprites") -> str: + """Generate the relative sprite path for use in pokemon.json. + + Returns a path like "sprites/25.webp" suitable for the sprite_url field. + """ + return f"{sprites_dir_name}/{pokeapi_id}.webp"