Name the Pokemon demo, and land a seed rename on its own row

`seed.py` matches a seed file to its scenario by title, so renaming one
inserted a second public scenario and stranded the first. The stranded row
stays public forever and has to be deleted by hand on every deployment, which
is what happened to "Road to the Champion". A seed file now lists its old
titles under `previous_titles`, and the rename updates the existing row.

The cover art is a PNG data URI. `app/images.py` accepts raster formats only,
because SVG can carry script and the bytes are served from the app's own
origin, so an SVG stores but yields an empty `image_url`.
`tools/make_pokeball.py` draws the ball with `zlib` alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PacdRuPXSkQQy4ZYdH32hF
This commit is contained in:
parththakkar106
2026-08-28 18:42:06 +05:30
co-authored by Claude Opus 5
parent ab546d6f3c
commit ae39514d1e
3 changed files with 130 additions and 13 deletions
@@ -1,12 +1,14 @@
{
"title": "[Demo] League Championship: Round One",
"title": "[Demo] Pokemon League Championship: Round One",
"previous_titles": ["[Demo] League Championship: Round One"],
"description": "A full Pokémon battle run on the built-in world-state system. Every member of your team is tracked as its own entity with HP and a status condition, a text field names who is currently out, and your potion supply runs down as you use it. The opponent's active Pokémon is tracked the same way. No scripting — the engine keeps the numbers honest. Open the World State panel on the left to watch the battle play out.",
"prompt": "The floodlights come up over the championship field and the referee raises both flags. Round One of eight. Beat the trainer in front of you or go home.\n\nAcross the packed rock terrain, Trainer Milo tosses his first Poké Ball. Graveler lands hard enough to crack the stone under it, four arms flexing, and settles into a low stance.\n\n\"Rock-types all the way down,\" Milo calls over the noise. \"Hope you brought something that hits harder than it looks.\"\n\nYour Pidgeotto is already in the air, wings locked into a slow circle above the field. It is fast, but Rock-type moves will tear it out of the sky, and Graveler is built to take a hit. Three Hyper Potions sit in your belt pouch.\n\nGraveler grinds one fist into its palm. The referee drops the flag.\n\nWhat's your move?",
"memory": "The player is a Trainer in Round One of an eight-round League Championship. The Round One opponent is Milo, a Rock-type specialist with three Pokémon: Graveler (Rock/Ground), Onix (Rock/Ground), and Kabutops (Rock/Water). The player's team is Pidgeotto (Normal/Flying), Ivysaur (Grass/Poison), Wartortle (Water), Machoke (Fighting), and Pikachu (Electric). Pidgeotto is out first. The player carries Hyper Potions, which restore HP and can be swapped for a status heal when needed. A battle is lost when every Pokémon on one side has fainted.",
"authors_note": "Play it like a real battle. Type matchups decide damage, status conditions stick around and cost the afflicted Pokémon turns, and switching costs the player a turn while the opponent attacks freely. Milo is competent: he switches to punish bad matchups and targets whatever is weakest.",
"ai_instructions": "Write in second person, present tense. Narrate the battle move by move, naming the active Pokémon on each side and the move each one uses. End every reply at a decision point where the player can choose a move, switch Pokémon, or use an item.\n\nReflect type advantage and disadvantage in the damage you narrate and in the HP numbers: a super-effective hit should take roughly double a neutral hit, a resisted hit roughly half. Ground and Rock moves hit Pikachu and Pidgeotto hard. Water, Grass, and Fighting moves are strong against Milo's Rock-types.\n\nEvery turn, update the state. Every number you send is a change to add to the current value, never the value you want it to end up at. The world state lines show you totals; the block you write carries the difference. A Pokémon at 96 HP that takes 30 damage is `-30`, not `66`.\n- Add 1 to `world.turn` in every reply, including this one. It counts the turns this battle has lasted, so it moves whether or not anything else does.\n- Send a negative number for HP lost and a positive number for HP restored on both sides (`npc.<name>.hp` for the player's own Pokémon, e.g. npc.pidgeotto.hp; `npc.milo.active_hp` for Milo's). Every Pokémon starts at its full HP, so a positive number on an undamaged one changes nothing.\n- Set a Pokémon's `status` stat (`npc.<name>.status`, or `npc.milo.active_status`) to the condition it just picked up: poisoned, burned, paralyzed, frozen, asleep, or none when it is cured or wakes up. Status matters. Poison and burn shave HP at the end of each turn, paralysis and freeze cost turns, sleep stops a Pokémon acting until it wakes.\n- When the player switches, set `player.active_pokemon` to the incoming Pokémon's name in full (not a delta).\n- When the player uses a potion, send `-1` for `player.potions` and a positive number for the healed Pokémon's HP. The count falls when a potion is spent. It never rises. Potions cannot be used once the count reaches 0.\n- When a Pokémon's HP hits 0 it faints, its status becomes `fainted`, and its trainer must send out a replacement (the player picks the next `player.active_pokemon`). A fainted Pokémon cannot be healed or switched back in.\n- Whenever Milo sends out a new Pokémon, set `npc.milo.active_pokemon` and `npc.milo.active_status` to their new values in full, and send `npc.milo.active_hp` as the positive change that carries it from where it stands to the newcomer's full HP. From a fainted Pokémon at 0 that is the newcomer's full HP: Graveler 98, Onix 90, Kabutops 88.\n\nWhen one of Milo's Pokémon faints, do both of these in the same reply: send `1` for `npc.milo.pokemon_fainted`, which adds one to the count rather than setting it, and set `npc.milo.active_pokemon` with a fresh `npc.milo.active_hp` and `npc.milo.active_status` for the one he sends out next. `npc.milo.pokemon_fainted` counts up from 0 and never goes down.\n\nMark each milestone as it happens, using the id shown beside the goal in the world state. Do not decide the whole battle in one reply; give the player a turn between every exchange.",
"tags": "demo, pokemon, battle, tournament, world-state, combat",
"icon": "⚡",
"icon": "◓",
"image": "data:image/png;base64,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",
"stat_schema": {
"world": {
"round": { "type": "counter", "min": 1, "max": 8, "initial": 1, "desc": "Which round of the eight-round championship this is; only ever counts up." },