Every update of the battle server, newest first.
3.7.0
- Monotype per side. The room rules have a second selector, Monotype · Player 2 / CPU (Team B in 2v2): Same (default, the old one-type behaviour), Off, a type, or Random type (the server picks one each game for a CPU or random team). Player 1's selector keeps its place. Example: you Monotype Flying against a Monotype Water CPU. Each seat's card shows its type, and the room list, lobby and replays show "Monotype (Flying vs Water)". Changing it un-readies, like other rules.
- Each team is checked against its own type on Ready (and marked in the builder before it), and Random / "Random from my Pokémon" / CPU teams are built for their own seat's type.
- R.O.W.E.'s in-game Monotype flag is no longer set in battle (it supports one type for both sides and caused "out of usable Pokémon" and pivot-switch bugs). Every team is already verified before the battle; the flag is only used for the species check.
- CPU teams (Practice and Battle Tower) now respect Little Cup and Limit Gen as well (Little Cup draws from unevolved species) and the team clauses (new draws while a clause is broken). If too few legal Pokémon exist the CPU gets a smaller team (at least 1), with a toast and a note on its card ("CPU team: 4 Pokémon, not enough legal ones for Monotype (Water), Gen 1-1"); only an empty pool gives an error.
- No signature moves, Old STAB and Inverse battle are game-wide flags and already apply to both sides, the CPU included.
-
Tests: new
test_monotype_sides.py(Flying vs Water: each side passes only its own type, CPU builds a Water team, random type, Little Cup + Water, tiny pool, a Flying vs Water battle runs to the end).
3.6.3
- Fix: Ready with a game mode did nothing. The server refused Ready (for example Monotype Water with a non-Water team) but the message was written under the page, out of sight, so the button seemed dead. Lobby errors now show in a red box right under the Ready button, and the message names the problem: "Can't ready up. Monotype (Water): Kilowattrel, Gliscor, Dragonite, Charizard and Corviknight aren't Water type". Each rule (Monotype, Little Cup, Limit Gen) is named on its own.
- Illegal Pokémon are marked before Ready. With Monotype, Little Cup or Limit Gen on, team members that break the rule get a red outline in the team builder and on your team card, with the reason on hover and a note listing them. The marks update when you change the team, items or rules.
- Random and CPU teams follow the game modes. The CPU's team, "Random" teams and "Random from my Pokémon" teams only contain Pokémon the modes allow (checked with the game's own filter). If no legal team can be made, you get a clear message and nobody stays "ready".
3.6.2
-
Online link: several providers at once. The admin "Online link" card is now a list: Cloudflare quick tunnel, Cloudflare named tunnel and Tailscale Funnel, each with its own status, link, Copy, Send to players, Start/Stop and a "Start with the server" checkbox. Starting or stopping one never touches the others, so you can try a new link type without losing the running link. One link is marked Main (the default for "Send to players" and the top-level status).
work/tunnel.jsonnow keeps one record per provider and each re-attaches after a server restart by itself; per-provider logs arework/tunnel-<provider>.log. An old single-recordtunnel.jsonis migrated and its running quick tunnel is adopted without restarting it, so the current link survives the update. -
Tests: new
test_tunnel_multi.py(A then B leaves A's record unchanged, stopping B leaves A running, both re-attach after a restart, legacy migration);test_tunnel.py/test_tunnel_restart.pyupdated.
3.6.1
-
Void match is now host-only (replaces v3.6.0's player Cancel match and Casual rooms, which are removed). The admin page's room list has a Void match button for each battle in progress, and a new Owner profile admin setting (
work/owner.json) picks one profile that gets a Void match (no record) button in battle from any device, with a confirm. The server checks the signed-in profile of the seat; for anyone else the message is rejected. A voided battle ends as "Cancelled by the host" with nothing recorded (records, leaderboard, usage stats, achievements, Tower streaks, tournament results, series score) and no replay saved. - Game errors void the match instead of a draw. When the watchdog stops a stuck battle, the emulator raises an exception mid-battle, or the battle has to be abandoned for a server-side failure, it ends as "Cancelled: the game hit an error" with a log line and no record for anyone. Forfeits, disconnect timeouts and real draws are unchanged.
-
Tests: new
test_void.py(owner voids, non-owner rejected, records unchanged; real server, port 8799);test_watchdog.pynow expects a void.
3.6.0
- (Superseded by 3.6.1.) Room rule Record: Ranked/Casual and a player Cancel match request/accept flow.
3.5.1
-
Clearer Tailscale Funnel setup.
docs/ONLINE.mdand the admin card's help now lead with the easy way (runtailscale funnel 8765once, open thelogin.tailscale.com/f/funnel?node=...link it prints, click Enable), and the manual way names the right place: Access controls -> JSON editor (not "General access rules"), adding thenodeAttrsline next togrants/acls.
3.5.0
-
The online link survives server restarts and updates. The Cloudflare tunnel is now an independent, detached process (its own process group on Windows, output in
work/tunnel.log) instead of a child of the server. Its pid, provider and address are saved inwork/tunnel.json. When the server starts it re-attaches to a tunnel that is still alive and answers, shows the same link, and starts a new one only when none is running. The admin restart / stop and updates no longer touch it; only the Stop button kills it. The card says the link changes only if the PC reboots or the tunnel stops. -
Tailscale Funnel as a third link type (Cloudflare quick tunnel / Cloudflare named tunnel / Tailscale Funnel): a free, permanent
https://<machine>.<tailnet>.ts.netlink with no domain. Detectstailscale(PATH orC:\Program Files\Tailscale), runstailscale funnel --bg <port>, reads the link from its output ortailscale status --json, stops withtailscale funnel --https=443 off(older syntax handled). If Funnel isn't enabled, the card lists the admin-console steps (HTTPS certificates + thefunnelnodeAttr). It lives in tailscaled, so it also survives reboots. -
Run as a Windows service (and Remove service) buttons for the named Cloudflare tunnel (
cloudflared service install). -
New
docs/ONLINE.md(Tailscale Funnel step by step, with the advice to set the server access code because the link is public), linked from the README and the guide. -
Tests:
test_tunnel.pyrewritten (detached process, re-attach, stop, dead record, Funnel with a fake tailscale); newtest_tunnel_restart.pyrestarts a real test server through the admin API and checks the same link comes back.
3.4.2
- Phone fix: the Pokémon editor is reachable again. At ≤ 600 px the Team tab of both team builders (room lobby and Teams page) hid the selection panel, so Summary / Edit / item buttons never showed after tapping a Pokémon. The panel is now shown on the Team tab. In the editor sheet: bigger touch targets, a taller move list, 16 px selects (no iOS zoom), a Save bar that stays at the bottom, and the keyboard no longer pops up on tapping a move slot. Desktop unchanged.
3.4.1
- Recent battles is paged. The card (Play and Replays) shows 5 battles per page with a compact pager (Prev · "1-5 of 23" · Next); the server now sends the last 50 battles instead of 10. The Leaderboard and Battle Tower cards no longer stretch to the height of Recent battles: each card is only as tall as its content. Phone layout unchanged (single column).
3.4.0
-
Server access code. Optional shared code (admin page -> "Server access code": set, change or turn off; stored hashed in
work/access.json; newaccesscode.py). With a code set, every page, websocket, sprite, clip, replay and pairing request needs it: pages show an "Enter the server code" screen (web/gate.html), everything else answers 401. A device remembers the code in a one-year HttpOnly cookie (Secure over HTTPS); changing or clearing the code signs everyone out; five wrong codes lock that address for 5 minutes. Requests from this PC itself always pass, and the admin page stays local-only. Off by default, so nothing changes until a code is set. -
Cloudflare Access section in
docs/CLOUDFLARE.md(named tunnel): Zero Trust -> Access -> Self-hosted application, an Allow policy for friends' emails with one-time PIN login, how players log in, the free 50-user limit, WebSockets behind Access, and how it combines with the access code. (These two items were requested for 3.3.0 after it was pushed, so they ship as 3.4.0.) -
Tests: new
test_access.pyin--quick.
3.3.0
-
Cloudflare Tunnel guide. New
docs/CLOUDFLARE.md, written for non-experts on Windows: Part A, a quick link with no account (winget install Cloudflare.cloudflared, the MSI alternative,run_server.cmd, thentunnel.cmd); Part B, a permanent link (free account, domain,tunnel login,tunnel create twigz,tunnel route dns, aconfig.ymlfor http://localhost:8765,tunnel run,cloudflared service install); troubleshooting; security notes with the--admin-remotewarning. Linked from the README and from a new "Share online" card on the admin page. -
Friendlier
tunnel.cmd. It now runstunnel.ps1: checks cloudflared (and points to the guide if it is missing), checks the server is running, prints your link in a box, copies it to the clipboard and says how to share and stop. -
Linux / Docker. The core is found by file type (
mgba_libretro.soon Linux, withTWIGZ_COREstill first) and every per-room core copy keeps the right extension (work/core_room<N>.so). NewDockerfile(python slim; downloads the libretro Linux core unlesscore/mgba_libretro.soexists),.dockerignore,docker-compose.yml(volume forwork/, ROM and host save as settings,restart: on-failure),deploy/twigz-battle-server.service(Restart=on-failure,RestartForceExitStatus=3) anddocs/LINUX.md. NewTWIGZ_HOST_SAVEsetting besideTWIGZ_GAME_ROM. -
Profile export / import. Admin page: Export per profile or Export all profiles (a zip with profile.json - teams, record, edits, PIN hash - and the stored saves; sign-in devices are left out) and Import profiles... (new
transfer.py). Existing names are never replaced unless you press "Replace the existing names too"; zips are validated (ids, file names, sizes). -
Online link from the admin page. New "Online link" card: Start / Stop online link runs cloudflared as a managed child of the server (new
tunnel.py; it is stopped on every server exit), shows the https link with Copy and Send to players (the admin message popup), restarts the tunnel when it drops (3-30 s back-off), and has Start the online link with the server (settings inwork/tunnel.json). A free quick-tunnel link is new on every tunnel start; the card says so. Under Link type a permanent link (named tunnel: tunnel name + hostname) runscloudflared tunnel run <name>and shows its fixed hostname. Links to the guide. -
Stats panel restyled (Play / Replays "Stats · server-wide" and the profile). A table: sprite + name, uses, win-rate bar with %, MVPs, with muted column labels and aligned numbers; moves show a type-colour chip and a right-aligned "11x". Sprites and move types come from the ROM by name (new
statlookup.py). Phone layout included. - Phone popups. At 600 px and narrower the profile, summary and editor popups are full-screen sheets: opaque background (nothing shows through), their own scroll, a sticky header with the Close button, safe-area padding, and no page scrolling behind them. Battle-record rows wrap cleanly on two lines: replay button, result, opponent, then rules and date.
-
Tests: new
test_transfer.pyandtest_tunnel.py(fake cloudflared) in--quick.
3.2.0
-
R.O.W.E. game modes as room rules. Rooms and tournaments can now switch on R.O.W.E.'s own challenge modes for their battles: Monotype (pick the type), Little Cup (also disables Mega Evolution, as in the game), Limit Gen (Gen 1 up to Gen 1–8), No signature moves, Old STAB and Inverse battle (R.O.W.E.'s Inverse Mode, flag 0x1DA: the type chart is inverted). The modes are set for that battle only (
rowe.apply_game_modes), in the main game and in the mirror; the host save's own modes are still ignored, now including Old STAB, No signature moves, Inverse and the extra Monotype types (vars 0x41E4/0x41E5). The "Game modes" controls are in the room rules panel and the tournament form, and active modes show on the room list, replays and tournament cards. -
Team check on Ready. Monotype, Little Cup and Limit Gen teams are checked with the game's own species filter (0x80A3BDC, through the new game call
OP_FILTER= 8), and Ready is refused with a clear message, e.g. "Monotype (Fire): Pikachu, Gyarados aren't allowed". Random teams and CPU/Battle Tower teams are not checked (logged). - Fair modes in battle (server ROM). In battle R.O.W.E. applied the species filter to player 1's side only (the old "out of usable Pokémon" and U-turn switch bugs; it also made player 1's legendaries count as unusable). The server ROM now counts every Pokémon with HP as usable on both sides (0x080A3D84), since teams are checked before the battle, and the filter no longer always rejects legendaries and cap Pikachu (0x080A3C28, 0x080A3C44), so it checks only the mode rules.
-
Tests:
test_battle_end.pyadds a Monotype-on check for both sides (battle end and U-turn switch-ins); newtest_game_modes.py(filter per mode, neutral afterwards, rule validation).
3.1.0
-
Real battle backgrounds. The battle field now shows R.O.W.E.'s own battle backgrounds, decoded from the ROM by the new
backgrounds.py. It reads the battle terrain table at 0x08422508: 27 entries, each with an LZ77 tileset, a tilemap and a palette, of which only the first 16 colours are used. They are served as 240×112 PNGs at/background/<n>.png, cached, with the list at/backgrounds.json. The 27 are grass, tall grass, sand, underwater, water, pond, mountain, cave, building and plain, plus 17 R.O.W.E. variants (autumn, marsh, snow, forest, ice, flower fields, evergreens…). They work in singles and doubles, on desktop and on phones. Game clips still show the game's own frames. -
"Background" room rule. Pick one of the 27 backgrounds, or Random (the default), which picks a new one every battle, including each game of a series, CPU, Battle Tower and tournament battles. Replays remember the background used (
backgroundin the replay data). Old replays keep the plain field. - Trainer intro. Before the first turn, a short "VS" slide shows both players' names and lobby trainer sprites (names only for guests and the CPU). Click or press any key to skip it. Replays show it when they open, never when seeking.
-
Tests: new
test_backgrounds.pyin--quick. It decodes every background and checks Tall grass's size and colours.
3.0.0
-
More tournament formats. Organizers now pick the format when creating an event: double elimination (as before), Swiss (round 1 is random or by leaderboard seed; each later round pairs players with the same record, nobody meets twice, an odd field gives the bye to the lowest-ranked player who hasn't had one; default rounds = ceil(log2 players), or 2-8 chosen) or round robin (up to 16 players, everyone plays everyone, one round at a time). Registration, check-in, ready, rooms, disputes and organizer decisions work exactly as before. The event page shows the standings table (wins, losses, Buchholz, head-to-head, game difference) and the rounds instead of a bracket; ties are broken by Buchholz, then head-to-head among the players still level, then game difference, then seed (
tournaments.standings,swiss_pairings,round_robin_rounds,build_rounds,advance_rounds). - 2v2 team tournaments. Choose the "2v2 teams" battle format: a player starts a team (three starters from their save) and a partner joins it from the event page; both check in; the pair plays as one entrant ("Ali & Bob") in any format. Matches use the 2v2 room rule (four seats, each player brings their three; the room opens for all four players at once). Either partner can press Ready, and a win by either member counts for the team.
-
Weekly recurring events. Organizers add a weekly schedule on the Tournaments page (day, time, format, rules; check-in opens 30 minutes before the start, registration 48 hours before, server time). The server creates each week's event and opens registration by itself, lists the upcoming ones with a link, and the organizer starts the event as usual. Schedules are stored in
work/tournaments/schedules.json(wstournament.schedule.add/.remove). -
Prize titles. When an event finishes, the top three get a title on their trainer card ("ROWE Cup Champion", "... Runner-up", "... Third place"; both players of a 2v2 pair), shown on the card, seats and the leaderboard. The best finish wins the title;
prizesare kept on the profile (cards.add_prize). The champion also still earns the Tournament champion achievement, now for events of every format and when an organizer decision settles the final. -
Tests: new
FormatTestsintest_tournaments.py(Swiss pairings and byes, round-robin schedule and standings, Buchholz and head-to-head tiebreaks, a full store run of both formats, 2v2 pairs, weekly schedule, prize titles, podium placements).
2.14.0
-
Internet-ready hosting.
tunnel.cmdstarts a free Cloudflare quick tunnel (cloudflared tunnel --url http://localhost:8765) and prints the public https link; ifcloudflaredis missing it says how to install it (winget install Cloudflare.cloudflared). README: steps, what is protected, and the trade-offs against Tailscale/Radmin. -
Hardening. Per-address rate limits (websocket messages; joins, creates and uploads; open connections), a 256 KB cap on uploaded saves,
/adminanswers only to this PC (not through the tunnel) unless started with--admin-remote, admin cookies are Secure over HTTPS, and forwarded headers are believed only from this PC (newnetsafe.py; PIN lockouts now count the real client address behind the tunnel). -
Backups. Nightly zip of profiles, replays and tournaments in
work/backups/<date>.zip(14 kept), a "Back up now" button on the admin page and restore notes in the README (newbackup.py). Newtest_hosting.pyin the quick tests.
2.13.0
-
Battle the CPU. The host of a singles or doubles room can add a CPU player in seat 2 (Easy / Normal / Hard / Expert; "Add CPU" / "Remove CPU" in the lobby; ws
cpu.add/cpu.remove). The CPU uses R.O.W.E.'s own trainer AI via a server ROM hook (switching and Mega Evolution included), is always ready, brings a new random team every battle fromcpu.py(base-stat-total band per difficulty;randbats.make_team/bst_min/bst_max) and follows the room rules. The mirrored copy is not used in CPU battles, and no timer ever waits on the CPU. Rematch deals a new CPU team; spectators can watch. -
Battle Tower. New "Battle Tower" button (ws
tower): a single-player streak room. The opponent's AI and team strength rise every 7 wins (min(streak // 7, 3): AI flags 0x1 / 0x3 / 0x7 / 0x107, base-stat bands 0-480 / 400-530 / 470-580 / 520+). A win offers "Next opponent"; a loss, forfeit or leaving ends the run. The room shows "Streak N · Tier T". Profiles keeptowerBestandtowerRuns; the rooms page lists a Battle Tower leaderboard and trainer cards show a "Tower N" badge (bronze 7, silver 21, gold 49). Tower rooms (state and CPU seat) survive restarts. -
Practice only. CPU and Tower battles don't change the W/L record, rank, usage stats or achievements; their replays are saved with
cpu: trueand listed as "vs CPU". -
New
cpu.pyandtest_cpu_modes.py(in--quick).
2.12.0
- Play on phones. At 600 px and narrower (desktop is untouched): the battle field sits on top with the log folded under a "Battle log" button, the moves are a 2×2 grid of touch-sized buttons, the switch and Team views open as bottom sheets (tap outside to close), the team builders (lobby room and Teams page) become Team / Boxes / Items tabs, the lobby world gets an on-screen d-pad, the settings panel fits the screen, and the tournament bracket scrolls sideways. No screen scrolls the page sideways at 375 px.
-
Installable app (PWA).
/manifest.json, a Poké Ball icon drawn bysprites.app_icon(/icon/app-192.png,/icon/app-512.png) and a service worker (/sw.js) that caches only the page shell (html, css, scripts, icons) and always fetches fresh when online. Websocket, battle, clip, audio, replay and sprite data are never cached. -
Touch details. Press and hold a move to see its damage preview, description and signature text (the same data as the desktop tooltip; the context menu is suppressed). The phone vibrates when it is your turn, and the screen stays awake during a battle (Wake Lock, released when the battle ends or you leave it). New
web/js/mobile.js.
2.11.5
-
Doubles damage preview shows foes only. The preview no longer runs or lists ally targets (single-target moves are probed against foe targets only; spread moves report only the foes), so the doubles probe fits the 1.3 s budget comfortably: measured about 0.9 s for full coverage (was about 1.27 s).
test_damage_preview.pyasserts no entry targets our own side.
2.11.4
-
Doubles damage preview covers every move and target.
PREVIEW_BUDGETraised from 1.0 s to 1.3 s so the doubles probe finishes all moves and targets, ally targets included (full coverage measures about 1.27 s).test_damage_preview.pynow checks doubles coverage with the real budget instead of a 10 s workaround.
2.11.3
-
Boosts in the in-battle summary. Opening the summary of your Pokémon on the field now shows an "In battle" line in the header and an "In battle" block on the Stats tab: every stat stage with its multiplier (Atk +1 ×1.5, Spe +2 ×2, Eva −1 ×0.75…) and modifiers that aren't stages: Swift Swim / Chlorophyll / Sand Rush / Slush Rush / Surge Surfer in matching weather or terrain, Tailwind, Choice Scarf / Band / Specs, Assault Vest, Iron Ball and power items, paralysis (Spe ×0.5) and burn (Atk ×0.5), Guts, Quick Feet, Marvel Scale, Unburden (item lost), Huge/Pure Power, Hustle, Solar Power, Fur Coat, Grass Pelt, Rock-type Sp. Def in Sandstorm, Ice-type Def in Snow, and a Trick Room note. Each stat shows "raw → effective" when it differs; accuracy/evasion stages are listed too. The live ability, item, stats and stages come from the battle (
_live_summaryaddslive). -
Foe card boosts. The foe info card (click the foe's healthbox / K) lists the modifiers the battle has shown: its revealed ability and item, its status, weather, terrain and Tailwind on its side. Opponents' stat stages stay hidden for players, as before; spectators and replays see them there too. No effective stats for foes. New
web/js/boosts.js.
2.11.2
- Faster damage preview. The throw-away turns now run with the game's no-animations flag (gHitMarker bit 0x80, set on the copy only) and only the maximum roll is played; the minimum is derived (max(1, D*85//100), as the server-ROM roll hook computes it). The minimum roll is only run when the max is capped by the target's HP / Sash / Sturdy (then it is flagged KO). Doubles probes the moves of both battlers interleaved so the 1.0 s budget is shared fairly, and the probe time and entry count are logged at debug level. Measured: singles 0.82 s -> 0.2-0.3 s (4 entries); doubles 1.04 s with 2 entries -> 1.0 s with 9 of 12 entries (full coverage needs about 1.27 s; the rest is cut by the budget).
2.11.1
- Mega Evolution uses the new Speed and ability immediately. Move the server ROM's single turn-order calculation until after all Mega Evolutions and their ability effects. Mega Swampert can use Swift Swim on its evolution turn; Mega Banette gets Prankster immediately. The game's existing priority, Trick Room, weather, item and tie handling still decide order, and switching retains its place before moves.
-
Fix false stuck-battle draws after an Encored Pokémon faints. A forced move can skip the move-selection prompt, leaving an old move choice behind. Clear that choice when the game asks for a replacement or pivot, while preserving an actual switch choice. This fixes the reproduced failure from replay
a7b0c1868b33. - Turn-order regression coverage. Controlled ROM battles cover weather abilities/suppression, speed boosts and drops, Tailwind, Trick Room priority/recasting/expiry, speed items, paralysis, ability priority, Mega ordering on both sides and in doubles, and Encore-to-replacement with damage preview enabled. Added focused choice-lifecycle tests and documented the R.O.W.E.-specific rules.
2.11.0
- Damage preview. Hover (or long-press on touch) a move to see "deals 38-45% to <foe>" per target, with "(can KO)" when the top roll takes the target out. It is computed on a throw-away copy of the battle (the same save-state approach as the move-lock probe) with the damage roll forced to the minimum (85%) and maximum (100%), then the real battle state is restored. No crits are counted, and the foe's item and ability are blanked unless the battle has already shown them, so nothing hidden leaks.
-
Server ROM hooks (area 0x1F86800): the damage-roll site 0x08058034 (
Random()&15) and the crit check 0x0805DD18 read a control byte at 0x0203F58C: 0xA1 = max roll + no crit, 0xA2 = min roll + no crit, any other value = the game's own code. The byte is only written inside throw-away states.Randomis 0x080A4600. -
Room rule "Damage preview" (On/Off). On by default; tournament match rooms have it off. The probe is limited to 1 s per decision point (partial results are kept), so very large move sets may show previews for only some moves. The mirror copy never runs it. New
test_damage_preview.py.
2.10.0
-
Fair IVs in Lv 50 / Lv 100 rooms. R.O.W.E. keeps no IVs per Pokémon: the IV getter (0x080A257C) returns the save's IV option × 3 + 1, so both teams got the host's option. The server ROM now hooks it (routine at 0x1F86700): while the level-rule byte (0x0203F58A) is set it returns 31 for everyone; with the rule off it is the game's own value. Rebuild with
python patchuild.py(done at startup when the game ROM changes). -
Pokémon usage stats. New
cards.py;work/replays/stats.jsonis an incremental index updated at each replay save (once per replay id): per profile and server-wide, most-used Pokémon (uses, win rate, MVPs) and most-used moves (from the log's "used X" lines). Shown as a Stats card on the Play/Replays page and in the profile panel. Pokémon are counted by the name shown in battle (nickname). -
Trainer cards + achievements. A card shows the trainer sprite (from the lobby avatar), a title, favourite Pokémon, record and leaderboard rank; it appears in the profile, on room seats and when hovering the leaderboard. Achievements are stored per profile with a toast when earned: First win, 5- and 10-win streaks, Flawless (won with all 6 standing), Comeback (won after being behind in Pokémon left), Tournament champion, Mega MVP, 100 battles. New
test_cards.py(incl. the IV hook with two different IV options). - Fix: commentary no longer shows "undefined is down to undefined% HP" for older replays/events without details.
2.9.0
-
Code health:
server.pyis split up. No behaviour change.-
rooms.pyholdsPlayer,RoomandRooms;ws.pythe websocket dispatcher;routes.pythe plain HTTP handlers.server.pykeeps the arguments, ROM check and startup.from server import Room(andGame,Player, ...) still works.
-
-
Faster startup, with timings. The replay index is read on first use (the random-team pools already were), and the log shows
startup:lines for the ROM check, engines, workshop and total time. - Smaller replays. New replay files are zlib-compressed; older plain files still play.
-
One-click tests.
run_tests.cmd/python run_tests.pyruns everytest_*.py(180 s cap each) against temp data folders and prints a PASS/FAIL table with the total time.--quickruns the unit-only subset (about 15 s). Newtest_store_music.py. -
Battle music: only real battle themes. The random pool held every song the BGM picker (0x080E9510) can swap in, town and route themes included. It now holds only what the battle BGM selector (0x08043710, by trainer class) plays: songs 383, 395, 326 and 325, plus the picker's music-style alternates of them (558, 739, 573, 765). The new songs are recorded in the background on the next start; until then matches use the old fallback track.
-
Admin page: a Battle music list with play and an on/off switch per song (
work/music/settings.json), in case a song slips through.
-
Admin page: a Battle music list with play and an on/off switch per song (
2.8.6
-
2v2: a pivot with nothing of your own left now really doesn't switch. When your last Pokémon uses U-turn / Volt Switch / Flip Turn and only your ally's Pokémon could come in, nothing switches, as in the real games: no prompt, your Pokémon keeps its stat changes and entry hazards don't hit it again (2.8.5 sent it back in).
-
The server ROM hooks the game's
CanBattlerSwitch(0x08064E6C): with the 2v2 rule on, it only counts the party slots of the player controlling each battler (per-battler masks at 0x0203F2D8, rule byte 0x0203F58B, set by the server and in player 2's mirror). Singles and doubles are unchanged. -
The 2.8.5 answer (send the same Pokémon back in) stays only as a safety net. New
test_switch_rule.py.
-
The server ROM hooks the game's
2.8.5
-
2v2: a pivot with nothing of your own left keeps your Pokémon in. When your last Pokémon uses U-turn / Volt Switch / Flip Turn and only your ally's Pokémon could come in, your Pokémon stays on the field instead of the ally's coming in (you'd have had nothing left to control).
- The game sends it straight back in, so, like any switch, its stat changes reset and entry hazards hit it again.
- The same happens in any format when nothing at all can come in.
2.7.0
Released after 2.8.4: the user swapped the order, so this number comes after the 2.8.x releases (2.8.0 had skipped 2.7).
-
The lobby uses R.O.W.E.'s own graphics: the map is a real Pokémon Center from the ROM (layout 269, 19×12 metatiles), rendered server-side at
/lobby/map.pngwith its own collision. Stations sit where they belong: the nurse counter (tournament registration), the PC (brackets), the right counter (spectators) and the Poké Ball emblem on the floor (champions). -
Trainer avatars: pick one of 10 overworld trainers (Brendan, May, Youngster, Lass, Camper, Picnicker, Black Belt, Hex Maniac, Scientist, Sailor) when you enter the lobby (remembered in this browser). Everyone walks with the real 16×32 sprite frames, facing the way they move. Sheets are served at
/lobby/trainer/<name>.png. - The server still checks every step (speed, blocked tiles, other players). Challenges, station actions and presence are unchanged.
-
New
maps.py: decodes map layouts (gMapLayouts at 0x08832D54; R.O.W.E. uses 12-bit metatile ids with 2048 primary metatiles, collision bit 12) and object-event graphics (0x08DF6C7C, palettes 0x08DF9498).
2.8.4
- Better commentary, with names and numbers: comments now say who and how much: "Low Kick tears off 75% of Slowking's HP!", "Lucario is down! Koach leads 5–4". New moments: first blood, knockouts with the Pokémon left on each side, momentum (two KOs in a row), comebacks, last Pokémon, big hits (50%+ in one hit, with crit / super-effective flavour), set-up (Calm Mind, Swords Dance, Nasty Plot, Dragon Dance, double stat rises), hazards (set and damage on entry), Knock Off, Rocky Helmet / Life Orb chip, Intimidate, Taunt and low HP.
- Less noise: no comments on leads or plain switches, none that just restate the line (plain "super effective" / critical hit reactions are gone unless they come with a big hit or a KO), several phrasings per moment, longer cooldowns, at most one comment per line.
- R.O.W.E.'s wording: stat lines with "Sp. Atk" / "Sp. Def" are recognised.
- Comments shown above the field: each comment also appears as a large animated caption in a strip directly above the battle field (it never covers the healthboxes, sprites or party balls; its height is always reserved, so the layout doesn't jump). Captions are queued, last about 2.5 s, follow the log, and work in replays (the replay speed scales them). Settings → Commentary Off leaves the strip empty.
-
Events carry a
dataobject (name, side, pct, move, remaining counts, ...).
2.8.3
- Recent battles details: click a row in Recent battles (Play page) to open its details: players and result, date, turns, the full rules (format, Best of N, level, teams, team preview, every active clause by name), the battle stats and a Watch replay button. Close with ✕, Esc or a click outside.
-
Rules locked during a series: once a Best-of-3/5 series has started, the rules can't be changed until it ends (a decided result, or a player leaving / joining). The lobby shows the rules read-only with "🔒 Locked for this series (game N · a–b)". Reset series clears the score only when every player in the room presses it (
{"t":"reset_series"}toggles your vote; lobby fieldsseriesLocked,resetVotes). -
New teams rule: Random from my Pokémon (
teams: "mine"): each game every player gets a random team of up to 6 (3 in 2v2) from their own save (party + boxes, no eggs), with each Pokémon's own moves, items and edits. Active team clauses are respected (up to 10 redraws). Players without a save can't Ready. -
Fix: 2v2, ally not shown in the log snapshots: a loop variable in
battle_viewoverwrote the viewer's own battler set, so per-line snapshots shaped your own Pokémon wrongly. -
Fix: pivot moves with nothing to switch in no longer soft-lock the battle: when U-turn / Volt Switch / Flip Turn asks for a switch and you have no selectable Pokémon, the server answers the prompt itself (in 2v2 the ally's Pokémon is used, or the ally takes over, like after a faint). The prompt now says "Choose a Pokémon to switch in for X" for a pivot and "X fainted – choose a Pokémon to send out" only after a faint (
needs()switch entries carryreason:pivot/faint). - Fix: party balls hidden by tall healthboxes: the foe's party balls now sit just below the lowest foe healthbox, even when stat-change chips add a row.
-
Fix: items when loading a saved team: a Pokémon's item could be dropped if it came from another team member listed later; all items are now applied first. New
test_item_pool.py.
2.8.2
- Recent battles: the Watch replay button sits at the right edge of each row, and each battle shows its type (Singles / Doubles / 2v2, Best of N, level rule, random teams, clauses).
2.8.1
-
Clauses checked against the game's own data:
- OHKO and Evasion moves: the lists were only the examples given. They have now been compared with every move in R.O.W.E., and they are complete: only Fissure, Guillotine, Horn Drill and Sheer Cold use the one-hit-KO effect, and only Double Team and Minimize use the evasion-raising effects.
- Evasion abilities: the Evasion Clause now also bans Sand Veil, Snow Cloak and Tangled Feet.
- Species Clause: it compares the base species number instead of the name, so R.O.W.E.'s differently named forms (e.g. "Mega Typhlosion" for a Typhlosion form) count as the same species. Stored profile saves are prepared once more for this.
2.8.0
- Optional battle clauses: eight extra rules, all off by default, set per room (a Clauses row of toggle chips in the lobby rules, each with a tooltip; the room list shows the active ones) and per tournament (checkboxes in the create form; the match rooms get them and they show on the tournament cards and page).
- Team clauses (checked on Ready): Species (forms count as one species), OHKO (Fissure, Guillotine, Horn Drill, Sheer Cold), Evasion (Double Team, Minimize, Bright Powder, Lax Incense), Item (no duplicate items), Legendary ban (R.O.W.E.'s own blacklist), Mega limit (one Mega Stone or Delta Orb) and Trapper ban (Moody, Shadow Tag, Arena Trap). They check the team that would actually battle (edited moves, lobby item changes) and Ready is refused with a message listing the violations. Tournament registration checks the six starters the same way. Random teams are not checked.
- Sleep Clause (checked per choice): a sleep move (Spore, Sleep Powder, Hypnosis, Sing, Lovely Kiss, Grass Whistle, Dark Void, Yawn) is refused while any Pokémon on the other team is asleep.
-
Rules:
clausesis a comma-separated string ("species,ohko"); unknown ids are dropped. Tournament settings:clauseslist. Newclauses.py. (2.7 is skipped.)
2.6.0
- 2v2 multiplayer doubles: a new match rule Battle: 2v2 · 4 players next to Singles and Doubles. Four players, two per team, each controlling one of their team's two Pokémon. It runs as a normal double battle in the existing doubles engine; R.O.W.E.'s link multi-battle mode isn't used.
- Seats: a 2v2 room has four seats: Team A = seats 1 and 3, Team B = seats 2 and 4. Each seat starts as the battler with the same number. Joining takes the first free seat, and the battle starts when all four are ready. The lobby shows the four cards team by team, with your ally marked. The room list shows four slots ("Ann & Cy vs Bo & Di"). A full 2v2 room can only go back to Singles/Doubles once seats 3 and 4 are empty.
-
Teams: each player brings 3 Pokémon: the first 3 of their team (from their save, a saved team, or 3 of the team's random team). With Pick 3 team preview each player picks 3 of their 6, and the first one leads. A team's party is
[left 1, right 1, left 2, left 3, right 2, right 3], so both players' leads come out together. The server keeps which seat owns each party slot. -
Choosing: you're only asked for your own Pokémon, and the server only accepts your choices for it (
Room.controllers). You can only switch to your own Pokémon; your ally's are greyed out in the switch list and the Team view, and their summaries stay private. A fainted Pokémon is replaced by its owner. If its owner has none left and the ally does, the ally sends one out there and plays it from then on (the game always fills the position from the whole team's party). - What you see: your own Pokémon in full; your ally's exact HP, but not their moves or stat stages; the foes as before. The battle log is worded for your team ("Foe ..." are the other team's). Team B's players both get the mirrored copy's game clips; Team A's use the real battle's.
-
Results: a team wins or loses together. Forfeiting or leaving forfeits for the whole team. Each signed-in player gets the result in their record, with the other team as the opponent ("Bob & Dave"). The series score is kept per team. Replays name both teams and list the four players. Rooms with four players survive restarts (
persist.pysaves all four seats and the slot owners). - Not in this version: tournaments and lobby challenges stay 1v1 (singles or doubles); 2v2 is only for regular rooms.
- Fix: rooms were not being saved for restarts since 2.0: saving any room with players failed on a field only tournament players have, and the error was only logged. Rooms now save and come back after a restart again.
- Fix: the spectator view (and so replays) no longer crashes in 2v2.
2.5.0
- Double battles in all formats: a new match rule Battle: Singles / Doubles (lobby rules), and a Battle setting (Singles / Doubles) when creating a tournament, passed to every match room. Two players, two Pokémon out each. Doubles needs at least 2 Pokémon per team (Ready says so otherwise); team preview Pick 3 becomes Pick 4 in doubles (VGC style: the first two lead).
- Choosing in doubles: "What will X do?" for your left Pokémon, then your right one, each sent as it's made. ← <name> goes back and changes the first choice (Esc too); Change choice still takes back the last one while the opponent decides. Forced switches handle one or two fainted Pokémon, and the same party slot can't be picked twice. Only one Pokémon per side can Mega Evolve.
-
Targets: moves that hit one Pokémon (Tackle, Heal Pulse...) ask for their target after the move: either foe or your ally, with names, sprites and effectiveness (Acupressure: itself or the ally). Spread, self, field and random moves (Earthquake, Rock Slide, Swords Dance, Spikes, Outrage...) need none. The server checks the target against the move's own target type from the ROM (
rowe.move_targets); fainted or empty positions can't be picked, and if the target faints first the game picks another one itself. Moves with no chosen target send what the game's own menu sends. - Doubles field: two sprites and two compact healthboxes per side (your Pokémon lower left, the foes upper right; the singles layout is unchanged). Damage numbers, hit animations and web move animations land on the Pokémon that was actually hit (both foes for spread moves). Move buttons show one effectiveness badge per foe. Each foe healthbox opens its own info card.
- Player 2's game clips in doubles: the mirrored copy of the game runs the double battle too (battler b ↔ b ^ 1: 0↔1, 2↔3), with targets mirrored and all four Pokémon synced; a rebuild starts it again with both active Pokémon of each side as leads.
- Battle stats, commentary, replays, party balls and the spectator view work with four battlers.
2.4.1
-
Tests:
test_team.pysets up its test room with the tournament and rules fields added in 2.0 (the test failed; the server was fine). All tests pass after the 2.1–2.4 changes.
2.4.0
-
Tournaments page: a tournament opens as its own full-width page instead of a popup, at
#tournament/<id>(links work, and a reload stays there). ← Tournaments goes back to the list at#tournaments. The page refreshes by itself whenever the event changes. The list's cards, the lobby's tournament stations (the bracket station opens the running event), the Champions Hall and the battle-over Bracket button all open it. From a match room or a battle it opens on top without leaving, and ← Back to match returns. -
Event page, top to bottom:
- Header: name, status, the format (team source, Best of N, seeding, reset, level, capacity), check-in and start times, and the organizer's Edit / Start / Resume.
- Your match: your opponent, the match number and label, its status, Ready for this match, and Open room once your room is assigned (rejoins it if you left). A line says where team changes happen: in the match room before game 1. Before the start it holds Register, Check in and the roster picker.
- Bracket: front and centre, full width, scrolling sideways. Before the start it shows a preview of the right size (4, 8, 16 or 32 slots for the players registered so far) with seeds and TBD names.
- Entrants: a compact grid with seed, name, W–L and check-in.
- Organizer audit, folded.
- Tournaments list: cards with status pills, player counts, format and the champion; click a card to open it. Organizers get a big + Create tournament, which opens the form on the page.
2.3.0
- Replay player: replays get a control bar instead of "…": ⏮ step back, ⏯ play/pause, ⏭ step forward, a timeline to jump to any line, the line counter ("12 / 140") and speed buttons 0.25× / 0.5× / 1× / 2× / 4× (remembered per browser). Keys: Space play/pause, ← / → step. Pause stops between lines; jumping shows the earlier lines at once with their HP tags and the field as it was. The speed scales text and web animations; game clips play at the game's own speed and are cut short or held at the end. The end-of-replay buttons (replay again, copy link, back) stay, with the bar above them. ← Back and Esc close a replay, and leaving stops it.
- Party balls follow the log: every log line carries both parties' ball state (fainted / status) at that moment, read cheaply from the game with each line's snapshot. Live battles, reconnects and replays draw the balls from the line on screen, so a faint no longer shows from the start of a replay. The opponent's side still shows only fainted / status.
- Stat changes for spectators: spectators and replays see both sides' stat stages. Players in a live battle still see only their own.
- Stat chips by size: ▲ +1 / +2 / +3 in deepening greens, ▼ −1 / −2 / −3 from orange to deep red.
- Log colours: each log line gets a colour and icon by kind: stat rises and drops (matching the chips), healing, status per condition (poison purple, burn orange, paralysis yellow, sleep grey-blue, freeze/frostbite cyan, confusion pink), weather and terrain, super effective / not very effective / no effect, critical hits, faints, switches, Mega Evolution and commentary. HP tags stay as they were. Legend in ⚙ Settings → Log colours.
2.2.0
-
Pokémon editor: select a team Pokémon in a room's team builder and press Edit to change its moves, ability, nature, style and held item. Only legal choices are offered, and the server checks them again.
- Moves: four slots, each with a searchable list showing type, category, power, accuracy and PP. Legal moves are level-up moves up to its level (also from its pre-evolutions), the TMs and tutor moves R.O.W.E. lets it learn, its signature move once unlocked, and the moves it already knows. New moves start with full PP.
- Ability: any ability of its species, the hidden one included, with the game's descriptions.
- Nature: any nature, like a Mint (R.O.W.E.'s nature override, which the stat calculation uses). Hardy only if it's the Pokémon's own nature; the other neutral natures are always there.
- Style: any of R.O.W.E.'s 8 styles. They change the base stats: Disruptor Spe +⅓, Atk/SpA −⅓; Tank Def/SpD +¼, Spe −⅓; Striker Atk/SpA +¼, Def/SpD −⅓; Speedster Spe +⅓, Def/SpD −⅓; Bruiser Def/SpD +¼, Atk/SpA −⅓; Juggernaut Atk/SpA +⅓, Spe −½; All-Rounder makes all five their average. Balanced changes nothing, and HP never changes.
- Held item: only from your bag, as before.
- The game applies each edit itself (SetMonData, CalculateMonStats, then a heal). Only the server's copy changes, never your save file. Remove edits goes back to the save.
- Signed-in players keep their edits in their profile and in saved teams, so they follow their Pokémon into every room. Old profiles keep working.
- Tournaments: edits are allowed before a match and between matches, and go into the locked roster. While a match is being played, also between its games, they're locked.
- Teams page: the Teams tab is open. Signed-in players see their stored save's party and boxes and their saved teams, and can build or change a team (pick six, order them, edit each one) and save it, with no room needed. Signed-out players get a sign-in prompt.
-
Summary page:
- Info shows the ability's and the held item's descriptions, and the style.
- The nature shown is the one the game uses, Mints included (it used to show the personality's nature).
- New Base stats view, like Showdown or Bulbapedia: a coloured bar per stat, the base value (and the style's value when it changes it), the total, and the lowest and highest the stat can be at the Pokémon's level (IV 0, EV 0, hindering nature up to IV 31, EV 252, helpful nature), with the game's own formulas.
- Tournament registration: the long list of dropdowns is now sprite tiles from your save. Click once for a starter, again for a substitute, again to leave it out, with counters ("Starters 4/6 · Subs 1/2"). A saved team can fill the starters.
- Recent battles and Leaderboard are back on the main Play tab (2.1.0 showed them only under Replays). Replays still shows them.
- Profile saves are prepared again once, to pick up styles, natures, descriptions and base stats.
2.1.0
-
New navigation: a top bar with Play · Tournaments · Replays · Lobby. Links like
#tournamentsor#replaysopen that section directly. - ← Back at the top of every battle and replay. In a live battle it asks first, because leaving counts as a forfeit. Esc does the same.
-
Behind the scenes: the page was split from one 2,100-line file into
web/app.cssand plain scripts inweb/js/(rooms, battle, replays, tournaments, lobby…), so the coming UI work is easier. They're served uncached, like the page itself.
2.0.0
-
Double-elimination tournaments: organizer profiles can publish events for 4–32 checked-in players, with random or leaderboard seeding, Best-of-1/3/5 singles matches, byes, and a configurable grand-finals reset.
- Tournament rooms report only the final server-side series result. Match rooms and tournament state recover together after a restart; duplicate and stale room results are rejected.
- Save-team entrants lock six starters and two or three substitutes from a stored save. Random-team events create an eight- or nine-Pokemon pool for each series. Players can change their six between games.
-
Organizers resolve no-shows, simultaneous disconnects, disputes, rematches and result corrections manually. Corrections are audited and pause downstream matches for review. Server administrators can grant organizer roles and oversee or resolve any event from
/admin. - The bracket looks like a printed bracket sheet: numbered matches in play order, connector lines, seeds, "Loser of 7" slots and the grand-final reset marked "if needed". Click a match for its details and actions.
- Wins, losses and titles are worked out from the matches, so corrections can't leave them wrong. Each tournament file stores its matches once.
-
Battle commentary: stable battle text and state snapshots produce conservative events for effectiveness, critical hits, knockouts, low HP, status, weather, stat changes, switches, last Pokemon, results, tournament progress and comebacks.
- Each event is attached to its log line, so the reaction appears right under that line as it plays, in step with the battle, reconnects and replays. There's at most one reaction per line, with per-type cooldowns.
- Settings → Commentary picks the Original pack or Off.
-
Multiplayer lobby: signed-in profiles can move around a server-validated 2D map, use registration, bracket, spectator and Champions Hall stations, and challenge nearby players into random-team rooms. Presence reconnects for 90 seconds; movement speed, walls, player collisions and interaction distance are checked on the server.
- Holding a direction key walks smoothly. A wall or another player simply stops you, with no error messages. Challenges are answered under the map, without a blocking dialog.
- Positions are only sent to players in the lobby, and the rooms page shows how many are inside.
-
Tests start their servers with temporary rooms, profiles, replays and tournaments folders (
TWIGZ_TOURNAMENTS_DIRis new), so they never touch real data. - Doubles, lobby chat and emotes remain future work.
1.19.0
-
Random battles: a new Teams rule in the lobby: From saves (as before) or Random.
- Every game deals two new random teams of six Lv 100 Pokémon, balanced against each other by base stat total.
- Species come from R.O.W.E.'s own Wonder Trade pool, evolved to their final form, without legendaries.
- The game itself builds each Pokémon: stats, a random nature and ability, a held item that fits, EVs, and moves.
- Each gets up to three strong attacks of different types, preferring its own types, plus a useful move like Swords Dance, Recover or Will-O-Wisp. No one-hit KOs, recharge or two-turn moves.
- No save needed: in a random-teams room, guests can join with just a name.
- Works with Best of 3/5 and team preview (Pick 3 picks from your random team). The Lv 50 / Lv 100 rule still applies.
-
New
randbats.pyandtest_random.py.
1.18.0
-
Best of 3 / Best of 5: a new Match rule in the lobby.
- The room keeps the score across games. The battle header shows e.g. "Best of 3 · game 2 · 1–0".
- After each game, Next game starts the next one. Once someone has won the series, the page says so and the next game starts a new series.
- Every game still counts in the profiles' records and gets its own replay.
- Changing the rule or the players starts the score again from 0–0.
-
Team preview: a new Team preview rule.
- Show teams: before each game, both players see both teams (species and level; items stay hidden), then press Start battle.
- Pick 3: each player picks 3 of their 6 in order, and the first one leads. Whoever hasn't picked after 90 s gets their first 3.
- Back to lobby leaves the preview, as does a player leaving or going offline. After a restart a preview starts again from the lobby.
- In battle, the Team view and summaries follow the picked Pokémon.
-
New
test_series.py.
1.17.0
-
Replays: every finished battle is saved and can be watched again, with the game animations, sounds and music, exactly as a spectator saw it.
- Recent battles on the rooms page lists the last 10, each with Watch replay.
-
Copy replay link on the battle-over screen gives a link anyone on the server can open (
#replay-…). - Your profile's battle list has a ▶ to watch each battle.
-
The newest 200 replays are kept in
work\replays\. The admin page lists them and can delete one.
- Battle stats: the battle-over screen and every replay show, for each Pokémon, the damage dealt and taken, HP healed and KOs, with a ★ MVP per side (most KOs, then most damage).
- Leaderboard: the rooms page ranks signed-in players by wins, then win rate. Guests aren't ranked.
-
New
replays.pyandtest_replays.py.
1.16.0
-
Mega and Delta icons: after Mega Evolution, the healthbox shows R.O.W.E.'s own icon next to the name, like in the game: the Mega icon, or the Delta icon for a Delta Orb. The icons are drawn from the game itself.
- Mega Evolved Pokémon now also show their Mega sprite. The page used to keep the base sprite, because the game only changes the form inside the battle.
- Signature moves are looked up for the Mega form, as the game does.
- Shiny Pokémon: they're drawn in their shiny colours everywhere (battle, party, team builder, summary), with a ✦ next to the name. R.O.W.E. stores shininess in the Pokémon, so it matches the game.
- Form names: "Hisuian Typhlosion", "Alolan Raichu", "Galarian Meowth", "Mega Charizard Y", "Heat Rotom"… in summaries, party lists and the team builder. The battle log keeps the game's own text. R.O.W.E.'s custom alternate forms keep their plain name.
- Foe weakness card: tap the foe's healthbox (or K) to see what it's weak to, resists and is immune to, from R.O.W.E.'s own type chart and its current types. It sits next to its revealed moves, item and ability, and works from the moment it's sent out.
-
Your-turn alerts:
- While the tab is in the background, its title flashes ("▶ Your move") when it's your turn or someone joins your room.
- Turn on Ping + notify when it's my turn in ⚙ Settings for a short sound and a browser notification as well.
- Profile saves are prepared again once, to pick up shininess and form names.
1.15.0
-
Locked moves are greyed out: when the game wouldn't let you pick a move (Choice Scarf/Band/Specs, Taunt, Encore, Disable, Torment, Assault Vest…), its button is greyed out with the game's own reason, e.g. "Choice Scarf allows the use of only Body Press!".
- The server finds these by trying each move on a saved copy of the battle and throwing the copy away, so every R.O.W.E. rule, signature and ability counts.
- If no move is usable, all buttons stay open and the game uses Struggle, as before.
- Change choice: while your opponent is still choosing, Change choice (or Esc) takes your move or switch back so you can pick again.
- Fairness check: players 1 and 2 swapped with the same seed now get identical damage. There's no hidden player-1 advantage.
-
Tests can now run a server without touching the real rooms and profiles (
TWIGZ_ROOMS_DIR,TWIGZ_PROFILES_DIR). -
New
test_choices.py.
1.14.0
- Your party in battle: a Team button (or T) next to Switch shows your whole party with HP and status, without switching. It also works while you wait for your opponent. Tap a Pokémon for its summary, with its current HP, status and PP. The switch screen's summary buttons now also work after a page reload.
-
Regional forms: Hisuian Typhlosion, Hisuian Samurott and other forms now show as themselves everywhere: sprite, types, ability, signature move and summary. The battle itself always used the right form; only the page showed the base Pokémon.
- Saves already stored in a profile are prepared again once, so they pick this up.
- Damage and heal numbers on the right line: some messages only appear after their HP bar has moved, e.g. "had its energy drained", "was hurt by … Rocky Helmet", "regained health" and recoil. Their numbers and HP bar changes used to show on the line before; now they show on the message itself.
-
New
test_forms.py.
1.13.1
-
Joining a room while signed in: the join screen lists the saves in your profile, so you pick one and press Join as <name>, with no upload.
- A save picked from your computer in a room is now kept in your profile too (Keep in my profile, on by default), so the next room offers it.
-
Saves in the profile panel: each one shows the trainer, the file name, when it was added and its party, so they're easy to tell apart. The button is now Add another save.
- Uploading the exact same save file again no longer looks like it replaced the others: it just moves to the top, and a message says so. Save in the game first to store a newer one.
1.13.0
-
Profiles: a name and a 4–6 digit PIN that keep your save, your teams and your battle record on the server.
- Upload your save once: after that, Join as <name> puts you in a room in one tap with your last team, with no upload and no waiting (the save's Pokémon are prepared once and cached).
- Save history: the latest save and the 2 before it are kept, and you can switch back to an older one.
- Saved teams: Save team / Load a saved team in the team builder. Teams remember their held items, and they still find their Pokémon after a newer save moves them to different boxes or slots. Your last team is always remembered.
- Battle record: wins, losses, draws and your last battles, in the profile panel.
- Other devices: sign in with name + PIN, or press Add a device to show a QR code / 6-character code that signs the other device in once (valid 5 minutes). Each device can be signed out from the panel.
- Security: PINs are stored hashed (scrypt), and 5 wrong PINs lock sign-in for 5 minutes. Devices keep a sign-in token, never the PIN.
-
Everything is stored on the host PC in
work/profiles/, never in git.
- Admin page: a profile list with records, plus Reset PIN (gives a temporary PIN) and Delete.
- Guests can still join without a profile by uploading a save.
-
New
profiles.pyandtest_profiles.py;qrcodeadded torequirements.txt.
1.12.0
-
What's new page: a What's new button in the header opens this changelog as a page (
/changelog), in a new tab so nobody leaves their battle.-
The page is generated from
CHANGELOG.mdon every visit, so it's always up to date. - After an update, the button shows a dot until you've opened it.
-
The page is generated from
1.11.0
-
server.gbarebuilt from the updated game ROM (SHA-14db09e01, which has the new signature moves). Battles on the server now use the same signature moves as the players' games. -
The server keeps
server.gbain step with the game ROM by itself.-
At startup it compares the game ROM's SHA-1 with the one recorded at the last build (
patch/server.base, new) and rebuildsserver.gbawhen they differ. -
Set the ROM with
--game-romorTWIGZ_GAME_ROM. -
Without the game ROM on the PC, the repository's
server.gbais used. -
A ROM the patch doesn't fit keeps the old
server.gba, with a warning in the log. -
Builds are written to a temporary file first, so a failed build never leaves a broken
server.gba.
-
At startup it compares the game ROM's SHA-1 with the one recorded at the last build (
-
patch/build.pycan now be called from code (build(),BuildError);python patch\build.pyworks as before. -
keystone-engineis now inrequirements.txt(install.cmdinstalls it), so the automatic rebuild also works in the installer's environment.
1.10.1
-
Fixed:
install.cmd/run_server.cmdcould misbehave when the project was downloaded as a ZIP from GitHub. The ZIP had Unix line endings, which can breakcmd'sgotolabels (the restart loop). A new.gitattributesmakes.cmdand.ps1files always use Windows line endings, in the ZIP too.
1.10.0
-
One-step Windows install: download the project from GitHub, double-click
install.cmd, thenrun_server.cmd.-
The installer finds Python 3.10+ (or offers to install Python 3.12 with winget) and installs the packages into a private
.venv(requirements.txt). - It checks the bundled files and plays one test battle turn.
- Optionally it opens port 8765 in Windows Firewall (with an admin prompt) and adds a desktop shortcut.
-
Safe to run again; it repairs a broken
.venv. Options:-Yes,-NoFirewall,-NoShortcut.
-
The installer finds Python 3.10+ (or offers to install Python 3.12 with winget) and installs the packages into a private
-
Everything the server needs is now in the repository:
-
the mGBA core:
core\mgba_libretro.dll, version 0.11-212-7a12d6d, the exact build all tests ran on; MPL-2.0, seecore\NOTICE.txt; -
a host save that only boots the game:
data\host.srm. -
RetroArch is no longer needed.
TWIGZ_COREcan point at a different core.
-
the mGBA core:
-
run_server.cmduses.venvwhen it exists, and tells you to runinstall.cmdif nothing is set up. It no longer tries to rebuildserver.gba(that needs your own ROM andkeystone-engine). - The startup log shows which core and host save are in use.
- The tests use the bundled host save.
1.9.0
-
Fixed: battles ending after a faint while the losing side still had Pokémon.
- The server's copy of the game inherited the host save's challenge modes, and the default host save has Monotype (Flying).
- In that mode the game ignores off-type Pokémon on player 1's side when it counts who can still fight. So "Major is out of usable Pokémon!" appeared with 2 still standing.
- The server now switches off every challenge mode in its own copy right after it boots and before every battle: Monotype, Limit Gen and its custom list, Little Cup, No Custom Forms, Grindless and the randomizers. Players' saves are never touched.
- Fixed: U-turn / Volt Switch / Flip Turn sometimes not switching. Same cause: Monotype made the game think no off-type teammate could switch in. In a stress test with these moves, all 499 switches brought in the chosen Pokémon.
- Fixed: a win for player 1 wasn't noticed, so the watchdog stopped those battles as a draw. The game clears the result in the same frame it leaves the battle (a loss keeps it); the server now remembers the result as soon as it appears.
-
A different battle song every match.
-
The server records all of R.O.W.E.'s battle themes once in the background on first start (
work/music/, about 6 minutes): the 36 songs its battle-music picker can choose from, keeping those with a clean loop. - Each match picks one at random, and both players hear the same song. The single track is used until the recordings are ready.
-
The server ROM's game-call routine gained a "play song" operation (
patch/build.py), soserver.gbawas rebuilt.
-
The server records all of R.O.W.E.'s battle themes once in the background on first start (
-
Longer player names: up to 16 characters, shown in full on the page, in the room list and in the battle log.
- The game itself only stores 7 characters, so it gets a short form (if both players' would match, the second ends in "2"), and the server puts the full names back into the battle text.
- The game's own text box inside the animation clips still shows the short form.
-
New
test_battle_end.py: an off-type team as player 1 fights to the end, U-turn with off-type teammates switches, and a player 1 win is noticed.
1.8.1
-
Fixed: missing pictures for Pawmot and about 55 other Pokémon, mostly Gen 9 (Sprigatito, Quaxly, Lechonk, Nymble, Pawmo, Tandemaus, Nacli…).
- Their palettes in the ROM store fewer than 16 colours, and the sprite decoder failed on them, so no picture was served.
- Short palettes are now padded; the pixels never use the missing colours.
-
New
test_sprites.py: every species' front and back sprite renders. The one exception is a Terapagos form, which has no graphics in R.O.W.E. itself.
1.8.0
-
Sound from the real game.
- Every battle-log line plays the game's own sound: attack sounds, hits, cries on send-out, stat changes, healing and faints. They're recorded on the server with the music muted, so they line up with the log and the game animations.
-
Battle music: recorded once from the game on first start (
work/music.ogg), looped seamlessly (82.7 s) during battles, and faded out afterwards. - ⚙ Settings: Sound effects & cries volume, Battle music volume and Mute all, remembered per browser. Browsers only allow sound after your first click or key press.
-
Encoded as Ogg Opus, a few KB per line; needs
pip install numpy soundfile(WAV fallback without soundfile).
- Saved battles keep their sounds across restarts.
1.7.0
-
Field status bar above the battle:
- turn number and weather, read from the game;
- field effects (Trick Room, terrains, Gravity…);
- each side's screens and hazards: Reflect / Light Screen / Aurora Veil / Tailwind with turns left, and Stealth Rock / Spikes ×n / Toxic Spikes / Sticky Web. Rapid Spin and Defog clear them.
- What you've learned about the opponent: click their HP box or a party ball (or press K). It shows each Pokémon you've seen with the moves it has used, plus its ability and held item once the battle reveals them (e.g. "Foe Corviknigh's Leftovers restored…"), and whether it Mega Evolved. Spectators can check both sides.
- Real max PP: buttons show "PP 20/21", with PP Ups counted.
-
Keyboard shortcuts (press ? for the list):
- 1–4 use a move, M Mega, S switch, 1–6 in the switch menu;
- Esc back, R Ready / Rematch, K opponent info.
- Saved battles from 1.6.0 still load. Also fixed: a restored battle missing newer data could crash the page and lose the room.
-
IDEAS.md: notes on a possible player-side damage bonus found while investigating the mirror drift.
1.6.0
-
Battles survive server restarts and crashes.
-
After every turn (and every lobby change) each room is saved to
work/rooms/<room>.bin: players and teams, rules, and both emulators' state with the battle log and animations. - On the next start the rooms come back with the same numbers. Players' pages reconnect into their seats by themselves and the battle continues from the last finished turn.
- This works after an admin Restart, Ctrl+C or a crash; tested by killing the server mid-battle.
- Saves are written to a temporary file first, so a crash mid-save keeps the previous turn.
-
After every turn (and every lobby change) each room is saved to
- After a restart, players get 5 minutes to come back before their seat is given up (or the battle forfeited). Rooms with seats waiting aren't closed as idle.
- Each tab also remembers its own seat, so two players in one browser (testing) no longer swap seats.
-
New
persist.pyandtest_resume.py.
1.5.1
-
Seats survive closing the tab.
- Your seat is remembered by your browser, not just the tab.
- Reopening the page, or the room's link, puts you straight back in your seat, and the room list shows a Rejoin button.
- In the lobby a disconnected player's seat (and their team) is now kept for 60 s and shown as "offline". Before, it was freed immediately. Someone else can still take an offline seat.
- Watchdog for stuck battles: if the game keeps running without new battle text or a decision (or one turn takes over 90 s), the battle stops as a draw with a message. The details go to the server log, and players can press Rematch.
- Double clicks: a choice can only be sent once per decision, and the server quietly ignores a repeated one. If the server refuses a choice, the buttons come back.
-
/dev/save(testing only,--dev-save) now answers only this PC, with a warning at startup. -
New
IDEAS.md: the ideas/to-do list for things not built yet. -
New
test_watchdog.py.
1.5.0
-
Team builder in the lobby, like a Pokémon Center PC. You build your battle party from your save's party and all 21 PC boxes: click a Pokémon, then a slot, to move it.
- Box Pokémon get their level, stats and full PP from the game itself (R.O.W.E.'s own stat calculation and HealPokemon, run on the server).
- Every Pokémon starts the battle fully healed.
- Held items: give and take items from your bag, mirroring the real game. Only items you own can be given, each only once, and key items can't be held. A taken item goes back to your bag.
-
Summary pages for your own Pokémon (Info / Stats / Moves), in the lobby and from the switch screen in battle:
- Info: ability (and whether it's hidden), nature with its stat arrows, item, OT, EXP and friendship.
- Stats: IVs and EVs.
- Moves: move details, descriptions and the ★ signature text.
- All values are read through the game's own GetMonData/GetNature.
- Hidden teams: your opponent (and spectators) only see how many Pokémon you bring until the battle starts.
- Server ROM: a small game-call routine lets the server run the game's own Pokémon functions (get/set data, calculate stats, heal, nature) on a separate "workshop" emulator.
-
Your
.srmis never changed.
1.4.0
-
Admin page (
/admin) for running the server without touching its window.-
Password-protected. Set it with
--admin-passwordorTWIGZ_ADMIN_PASSWORD. Otherwise one is generated on first start, printed, and saved inwork\admin_password.txt. - Shows uptime, rooms, running battles, connected browsers and the server log.
- Restart / Stop: Now, In N seconds or When idle (after the running battles finish; new battles can't start meanwhile), with Cancel.
- Message all players and Close room.
-
Password-protected. Set it with
- Players are warned: a countdown banner ("Server restarting in 0:27"), host messages as pop-ups, and "The server is back" once their page has reconnected by itself.
- Clean exits: Restart, Stop and Ctrl+C tell everyone, close all connections and exit in about a second. Before, Python could hang waiting for emulator threads.
-
run_server.cmdrestarts the server when asked (exit code 3), ends quietly on Stop, and only pauses after a crash. Started without it, a restart launches a fresh copy of the server itself. - Startup output goes through the server log (also shown on the admin page) and lists Radmin (26.x) addresses too.
- The code for this already went out with the 1.3.0 commit (39528c5); this entry documents it.
1.3.0
-
Multiple battles at once (rooms).
- The start page lists all rooms ("Room 1 · Ash vs Gary · in battle · 2 watching"), with Create room, plus Join, Watch and Copy link for each room.
-
A link like
…:8765/#room-3opens that room directly.
- Each room is independent: its own players, spectators, rules, battle and clips. Two emulators per room (battle + player 2's mirror), each on its own copy of the mGBA core, and battles in different rooms run in parallel.
- Fast room creation: the game boots once; new rooms start from that saved state in about 0.2 s.
- Idle rooms: a room nobody is in closes after 3 minutes, and its emulators are reused for the next room.
-
Clip addresses now include the room:
/clip/<room>/<battle>/<n>.webp. -
New
test_rooms.py: two rooms play identical battles to one at a time, run in parallel, and reuse closed rooms' emulators.
1.2.2
-
Repository: added
.gitignore; the Python cache andwork/(the server's own copy of the mGBA core) are no longer tracked.server.gbastays in the repo.
1.2.1
-
Fixed: wrong or out-of-sync game animations after a server restart.
- Clip addresses reused battle numbers that restarted at 1, so browsers replayed cached clips from earlier battles.
- Every battle now has a unique random id.
- Fixed: an old copy of the page staying in the browser after an update. The page is now sent with no-cache headers.
- Both players (and spectators) now receive each turn at the same moment, after player 2's clips are recorded.
1.2.0
-
Game animations for player 2 are now drawn from player 2's own side.
- The server runs a second, mirrored copy of the game in which player 2 is the player.
- It gets the same choices and is re-synced with the real battle every turn.
- If the copies ever fall out of step, the mirror rebuilds itself from the real battle. Until then, player 2 sees web animations for those moves.
- Player 1 and spectators get each turn immediately; player 2's view follows once the mirror has recorded it (about half a second).
1.1.1
- Game animations now show the full game screen (240×160, with the game's text box), letterboxed over the field instead of a cropped strip.
- Fixed garbled battle-log lines such as "Dragonite usgh's Defense sharply rose!". Text is only read once the game has finished writing it.
1.1.0
- Renamed to Twigz Battle Server; the version is shown in the page header and printed at startup.
-
Settings (⚙):
- text speed: Slow / Normal / Fast;
- animations: Game / Web / Off;
- animation speed: Slow / Normal / Fast.
- Real game animations: the server records each move animation from the real game. They're drawn from player 1's side.
- Slower, longer web animations.
- Damage and healing numbers are always flat numbers. They also appear as tags in the battle log.
-
Lobby match rules:
- Opponent HP shown as % or exact;
- Level rule: as in save / Lv50 / Lv100.
- Party balls turn muted red for Pokémon with a status condition.
1.0.1
- Fixed self-targeting moves (Roost etc.) affecting the opponent instead of the user.
1.0.0
- Spectators: a Watch button.
-
Leaving:
- Leave counts as a forfeit.
- Disconnects get a 60 s grace period.
- If both players disconnect, the battle is abandoned. This fixes "a battle is in progress" blocking new players.
-
Battle display:
- The battle log reveals one line at a time, with HP bar animation, damage numbers and web-drawn move animations.
- Stat-change chips on your own Pokémon; the opponent's stat stages and exact HP are hidden.
-
Moves:
- signature moves marked with ★ and their R.O.W.E. text;
- type effectiveness from the ROM's type chart;
- move descriptions;
- colour tags for category (Physical / Special / Status).
- Removed the boy/girl choice.
0.3.0
-
Web server and battle page:
.srmupload, lobby, ready/rematch, reconnect, forfeit, sprites from the ROM, and player 2's log rewritten from their point of view.
0.2.0
-
Full singles battles: switching, forced switches, mega evolution (validated by the game's
CanMegaEvolve), battle end, state reader and battle log.
0.1.0
- Headless R.O.W.E. battle engine: the libretro frontend, the server ROM patch, and both sides driven through the game's built-in remote-battle queue.