Add after_route_name to boss battle export/seed pipeline

Exports now include after_route_name (resolved from the route FK),
and the seed loader resolves it back to an ID on import. Also adds
a draft bean for displaying encounter-less locations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 15:51:23 +01:00
parent 47c8fa8e88
commit 758750b7b8
4 changed files with 38 additions and 1 deletions

View File

@@ -0,0 +1,19 @@
---
# nuzlocke-tracker-xa5k
title: Display encounter-less locations for egg hatching
status: draft
type: feature
created_at: 2026-02-08T14:49:50Z
updated_at: 2026-02-08T14:49:50Z
---
Some routes/locations don't have wild encounters but are still relevant for gameplay — particularly for hatching eggs. Currently these locations are hidden or not useful in the run view since they have no encounters to log.
Add support for displaying locations that have no encounters in the run view, so players can track egg hatches or other location-based events there.
## Checklist
- [ ] Determine how encounter-less routes should appear in the run view (e.g. different visual treatment, no encounter status dot)
- [ ] Update route filtering logic to include routes without encounters when relevant
- [ ] Add ability to log an egg hatch at a location (new encounter type or dedicated UI)
- [ ] Consider whether these locations need an admin-side flag (e.g. `show_without_encounters`) or if all routes should always be visible

View File

@@ -127,6 +127,7 @@ async def export_game_bosses(
.where(BossBattle.version_group_id == game.version_group_id)
.options(
selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon),
selectinload(BossBattle.after_route),
)
.order_by(BossBattle.order)
)
@@ -143,6 +144,7 @@ async def export_game_bosses(
"badge_image_url": b.badge_image_url,
"level_cap": b.level_cap,
"order": b.order,
"after_route_name": b.after_route.name if b.after_route else None,
"location": b.location,
"section": b.section,
"sprite_url": b.sprite_url,

View File

@@ -211,10 +211,19 @@ async def upsert_bosses(
version_group_id: int,
bosses: list[dict],
dex_to_id: dict[int, int],
route_name_to_id: dict[str, int] | None = None,
) -> int:
"""Upsert boss battles for a version group, return count of bosses upserted."""
count = 0
for boss in bosses:
# Resolve after_route_name to an ID
after_route_id = None
after_route_name = boss.get("after_route_name")
if after_route_name and route_name_to_id:
after_route_id = route_name_to_id.get(after_route_name)
if after_route_id is None:
print(f" Warning: route '{after_route_name}' not found for boss '{boss['name']}'")
# Upsert the boss battle on (version_group_id, order) conflict
stmt = insert(BossBattle).values(
version_group_id=version_group_id,
@@ -225,6 +234,7 @@ async def upsert_bosses(
badge_image_url=boss.get("badge_image_url"),
level_cap=boss["level_cap"],
order=boss["order"],
after_route_id=after_route_id,
location=boss["location"],
section=boss.get("section"),
sprite_url=boss.get("sprite_url"),
@@ -237,6 +247,7 @@ async def upsert_bosses(
"badge_name": boss.get("badge_name"),
"badge_image_url": boss.get("badge_image_url"),
"level_cap": boss["level_cap"],
"after_route_id": after_route_id,
"location": boss["location"],
"section": boss.get("section"),
"sprite_url": boss.get("sprite_url"),

View File

@@ -68,6 +68,7 @@ async def seed():
# 4. Per version group: upsert routes once, then encounters per game
total_routes = 0
total_encounters = 0
route_maps_by_vg: dict[int, dict[str, int]] = {}
for vg_slug, vg_info in vg_data.items():
vg_id = vg_slug_to_id[vg_slug]
@@ -87,6 +88,7 @@ async def seed():
# Upsert routes once per version group
route_map = await upsert_routes(session, vg_id, routes_data)
route_maps_by_vg[vg_id] = route_map
total_routes += len(route_map)
print(f" {vg_slug}: {len(route_map)} routes")
@@ -147,7 +149,8 @@ async def seed():
if not bosses_data:
continue
boss_count = await upsert_bosses(session, vg_id, bosses_data, dex_to_id)
route_name_to_id = route_maps_by_vg.get(vg_id, {})
boss_count = await upsert_bosses(session, vg_id, bosses_data, dex_to_id, route_name_to_id)
total_bosses += boss_count
print(f" {vg_slug}: {boss_count} bosses")
@@ -416,6 +419,7 @@ async def _export_bosses(session: AsyncSession, vg_data: dict):
.where(BossBattle.version_group_id == vg.id)
.options(
selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon),
selectinload(BossBattle.after_route),
)
.order_by(BossBattle.order)
)
@@ -434,6 +438,7 @@ async def _export_bosses(session: AsyncSession, vg_data: dict):
"badge_image_url": b.badge_image_url,
"level_cap": b.level_cap,
"order": b.order,
"after_route_name": b.after_route.name if b.after_route else None,
"location": b.location,
"section": b.section,
"sprite_url": b.sprite_url,