{"id":51511733,"url":"https://github.com/doodlewind/vibe-gba","last_synced_at":"2026-07-08T07:01:53.433Z","repository":{"id":363077562,"uuid":"1261655142","full_name":"doodlewind/vibe-gba","owner":"doodlewind","description":"Scratch-built Rust GBA emulator prototype for a Pokemon Emerald milestone","archived":false,"fork":false,"pushed_at":"2026-06-07T09:30:42.000Z","size":75,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T11:17:28.875Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/doodlewind.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-07T01:29:47.000Z","updated_at":"2026-06-07T09:30:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/doodlewind/vibe-gba","commit_stats":null,"previous_names":["doodlewind/vibe-gba"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/doodlewind/vibe-gba","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doodlewind%2Fvibe-gba","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doodlewind%2Fvibe-gba/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doodlewind%2Fvibe-gba/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doodlewind%2Fvibe-gba/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doodlewind","download_url":"https://codeload.github.com/doodlewind/vibe-gba/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doodlewind%2Fvibe-gba/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35255355,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-08T07:01:51.439Z","updated_at":"2026-07-08T07:01:53.426Z","avatar_url":"https://github.com/doodlewind.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# vibe-gba\n\n`vibe-gba` is a scratch-built Rust Game Boy Advance emulator prototype. It was first developed with one concrete milestone in mind: boot a legally supplied `Pokemon Emerald.gba` ROM far enough to render the game, pass the new-game onboarding route, walk through Littleroot Town into Route 101, reach Professor Birch's starter bag, and receive the first Pokemon in Birch's lab.\n\nThis repository does not include any ROM, save file, emulator state snapshot, or existing GBA emulator engine. The implementation is intentionally small and direct: ARM7TDMI execution, memory bus, cartridge loading, save memory, DMA/timers/interrupts, PPU background and OBJ rendering, keyboard input, screenshots, save states, and debug support for narrowing the next hardware gaps.\n\n![Littleroot entry](docs/screenshots/littleroot-entry.png)\n![Walking in Littleroot](docs/screenshots/littleroot-walk.png)\n\n## Status\n\nCurrent milestone:\n\n- Boots Pokemon Emerald from a user-provided `.gba` or zipped `.gba` file.\n- Renders title/menu/intro/truck/Littleroot/Route 101/Birch Lab scenes on the fresh-ROM milestone route.\n- Supports deterministic headless runs with scripted input, screenshots, state save/load, and debug dumps.\n- Supports interactive keyboard play once the window is opened.\n- Executes Emerald's native overworld callback/input/player-step path in the Littleroot development state with gameplay HLE disabled. A fixed BIOS `ObjAffineSet` implementation now prevents sprite affine updates from corrupting callee-saved registers and starving `CB1_Overworld`.\n- The previous direct movement / Route 101 / starter high-level gameplay assists are disabled by default. The older flow shortcuts that wrote Emerald callbacks/tasks to skip title, naming, and sprite-animation waits are also disabled by default.\n- With those shortcuts disabled, a fresh-ROM scripted run now reaches the real moving truck, follows Mom into the player's house, sets the wall clock, exits to Littleroot Town, meets May, enters Route 101, triggers the Birch rescue, chooses Torchic from Birch's bag, clears the first battle, arrives in Birch's lab, and has Torchic in the party from KEYINPUT alone.\n\nThis is not a general-purpose, compatibility-focused GBA emulator yet. The fresh-ROM Emerald M1 route is now proven through starter acquisition, but many games and many Emerald paths are expected to need more CPU, PPU, audio, BIOS, timing, and hardware coverage.\n\n## Build\n\nInstall Rust, then:\n\n```bash\ncargo build --release\n```\n\nThe binary is:\n\n```bash\ntarget/release/vibe-gba\n```\n\n## Run From A Fresh ROM\n\nProvide your own legally dumped ROM:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --save /tmp/emerald-fresh.sav\n```\n\nKeyboard mapping:\n\n| GBA | Keyboard |\n| --- | --- |\n| A | `Z` or `A` |\n| B | `X` or `S` |\n| Start | `Enter` |\n| Select | `Backspace` |\n| D-pad | Arrow keys |\n| L / R | `Q` / `E` |\n| Quit | `Esc` |\n\nFrom the title screen:\n\n1. Press `Enter` or `Z` on the title screen.\n2. If the dry battery prompt appears, press `Z` through it until the `NEW GAME` / `OPTION` menu is visible.\n3. Choose `NEW GAME` with `Z`.\n4. Advance Birch's intro text with `Z`.\n5. Pick gender, enter a name, then continue toward the moving truck scene.\n\nManual M1 smoke path:\n\n1. In the moving truck, walk right to trigger the native Littleroot moving-in sequence.\n2. Follow Mom into the house, go upstairs, set the wall clock, then return downstairs and advance the TV/Mom dialogue.\n3. Exit the house into Littleroot, walk east to May's house, go upstairs, and inspect the Poke Ball to meet May.\n4. Leave May's house and walk to Littleroot's north exit. The native script should lead into the Route 101 Birch rescue scene.\n5. Walk to Birch's bag, press `Z`, choose a starter, then use the battle menu normally to defeat Zigzagoon.\n6. Advance Birch's lab dialogue until the field is unlocked again. A successful run ends with Torchic in the party and native movement available again in Littleroot/Route 101.\n\nThe old direct gameplay HLE path could force parts of the route. That path is disabled now; the current milestone proof is for Emerald's own native field engine to drive truck movement, Littleroot movement, Route 101 transitions, starter selection, the first battle, and starter acquisition from KEYINPUT alone.\n\n## Native-Loop Development Checks\n\n### Full Fresh-ROM M1 Check\n\nThis is the current headless acceptance check for the Emerald native-loop M1 route. It does not load a save state, and the input script only presses Start, A, and D-pad buttons:\n\n```bash\nrm -f /tmp/vibe_goal_full_fresh_starter.sav\nSCRIPT=\"$(cat scripts/emerald-m1-onboarding.input)\"\n\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --save /tmp/vibe_goal_full_fresh_starter.sav \\\n  --frames 65200 \\\n  --input-script \"$SCRIPT\" \\\n  --screenshot /tmp/vibe_goal_full_fresh_starter.png \\\n  --save-state /tmp/vibe_goal_full_fresh_starter.bin \\\n  --dump-state\n```\n\nExpected shape from the validated run:\n\n- `CPU ... gameplay_hle=false legacy_flow_hle=false`\n- `FIELD ... layoutId=003a`\n- `SAVE save1=... route101=3 birch_lab=3 starter=1`\n- `PARTY count=1 species=280 level=5 hp=19/19`\n- `SAVE_FLAGS pokemon_get=true rescued_birch=true hide_bag=true hide_zigzagoon=true hide_lab_birch=false`\n- non-zero native-loop counts for `FieldGetPlayerInput`, `ProcessPlayerFieldInput`, `PlayerStep`, `ScrCmd_applymovement`, and `ScrCmd_waitmovement`\n- screenshot shows Professor Birch's lab after the starter sequence.\n\n`SPECIES_TORCHIC` is `280` in Emerald's internal species constants.\n\nAfter that full check, this shorter state-based probe exits Birch's lab, walks through Littleroot, and crosses into Route 101 with the starter still in the party:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --load-state /tmp/vibe_goal_full_fresh_starter.bin \\\n  --frames 10400 \\\n  --input-script '20:1:down,180:1:down,340:1:down,500:1:down,660:1:down,820:1:down,980:1:down,1200:1:down,1600:1:left,1800:1:right,2000:1:up,2220:1:right,2380:1:right,2540:1:right,2700:1:right,2920:1:up,3080:1:up,3240:1:up,3400:1:up,3560:1:up,3720:1:up,3880:1:up,4040:1:up,4200:1:up,4360:1:up,4520:1:up,4680:1:up,4840:1:up,5000:1:up,5160:1:up,5320:1:up,5680:1:left,5840:1:right,6000:1:up,6160:1:down,7420:1:up,7640:1:up,7920:1:left,8160:1:right,8400:1:down,8820:1:up,9060:1:left,9300:1:right,9540:1:down,9780:1:up' \\\n  --screenshot /tmp/vibe_goal_after_starter_route101_probe.png \\\n  --dump-state\n```\n\nExpected shape:\n\n- `FIELD ... layoutId=0011`\n- `PARTY count=1 species=280 level=5`\n- `SAVE save1=... route101=3 birch_lab=3 starter=1`\n- non-zero `FieldGetPlayerInput`, `ProcessPlayerFieldInput`, and `PlayerStep`\n\nThe current fresh-ROM no-gameplay-HLE check reaches the player's house 1F after the moving-truck and Mom escort sequence:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --save /tmp/vibe_goal_fresh_no_helper.sav \\\n  --frames 13200 \\\n  --input-script '3200:20:start,3850:20:a,4400:20:a,4800:20:a,5600:20:a,6700:20:a,7120:8:start,7180:8:a,8000:8:a,8900:140:right,10000:8:a,10300:8:a,10600:8:a,10900:8:a,11200:8:a,11500:8:a,11800:8:a,12100:8:a,12400:8:a,12700:8:a,13000:8:a' \\\n  --screenshot /tmp/vibe_goal_fresh_no_helper.png \\\n  --save-state /tmp/vibe_goal_fresh_no_helper.bin \\\n  --dump-state\n```\n\nExpected shape:\n\n- `CPU ... gameplay_hle=false legacy_flow_hle=false`\n- `FIELD ... layoutId=0036`\n- `SCRIPT ... lockFieldControls=0`\n- non-zero `PC_HITS` for `CB1_Overworld`, `DoCB1_Overworld`, `FieldGetPlayerInput`, `ProcessPlayerFieldInput`, and `PlayerStep`\n- screenshot shows the player inside the first floor of the house.\n\nThis is an intermediate debug checkpoint for shortening route work; the full fresh-ROM M1 check above is the current acceptance test.\n\nNative movement can be probed from that generated house state:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --load-state /tmp/vibe_goal_fresh_no_helper.bin \\\n  --frames 360 \\\n  --input-script '20:80:left,140:80:right,260:80:up' \\\n  --screenshot /tmp/vibe_goal_house_native_walk.png \\\n  --dump-state\n```\n\nExpected shape:\n\n- `CPU ... gameplay_hle=false legacy_flow_hle=false`\n- `PC_HITS` shows `FieldGetPlayerInput`, `ProcessPlayerFieldInput`, and `PlayerStep`\n- `PLAYER_OBJECT` coordinates and facing/movement direction change through the native object-event path.\n\nThe current full fresh-ROM no-gameplay-HLE Littleroot check does not load a save state. It starts from the title/new-game route, goes through the moving truck, Mom escort, wall clock, 1F TV/Mom sequence, exits the house, and performs a short native movement probe outside:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --save /tmp/vibe_goal_full_fresh_littleroot.sav \\\n  --frames 22300 \\\n  --input-script '3200:20:start,3850:20:a,4400:20:a,4800:20:a,5600:20:a,6700:20:a,7120:8:start,7180:8:a,8000:8:a,8900:140:right,10000:8:a,10300:8:a,10600:8:a,10900:8:a,11200:8:a,11500:8:a,11800:8:a,12100:8:a,12400:8:a,12700:8:a,13000:8:a,13220:340:up,13640:8:a,13780:8:a,13940:8:a,14120:8:a,14320:8:a,14540:2:left,14630:1:up,14700:8:a,14880:8:a,15060:8:a,15240:8:a,15519:8:a,15640:8:up,15740:8:a,16060:8:a,16360:8:a,16660:8:a,16960:8:a,17210:8:a,17400:1:right,17480:1:up,17700:8:a,17920:8:a,18140:8:a,18360:8:a,18580:8:a,18800:8:a,19020:8:a,19240:8:a,19460:8:a,19680:8:a,19900:8:a,20120:8:a,20260:1:right,20400:1:right,20540:1:right,20680:1:right,20840:1:down,21000:1:down,21140:1:down,21400:1:left,21600:1:right,21800:1:up,22000:1:down' \\\n  --screenshot /tmp/vibe_goal_full_fresh_littleroot.png \\\n  --save-state /tmp/vibe_goal_full_fresh_littleroot.bin \\\n  --dump-state\n```\n\nExpected shape:\n\n- `CPU ... gameplay_hle=false legacy_flow_hle=false`\n- `FIELD ... layoutId=000a`\n- `PLAYER_AVATAR flags=21 [foot|controllable]`\n- non-zero native-loop counts for `FieldGetPlayerInput`, `ProcessPlayerFieldInput`, `PlayerStep`, `ScrCmd_applymovement`, and `ScrCmd_waitmovement`\n- `SCRIPT ... lockFieldControls=0`\n- screenshot shows the player outside in Littleroot Town.\n\nFor one-frame headless D-pad pulses, Emerald may temporarily clear `PLAYER_AVATAR_FLAG_CONTROLLABLE` while a step or forced-movement check is in progress. Use `PC_HITS`, `PLAYER_OBJECT cur=(x, y)`, facing/move direction, and the final settled `lockFieldControls=0` state to distinguish native movement from direct movement HLE.\n\nThe local native naming-screen segment can be checked from a generated state at Birch's \"What's your name?\" prompt:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --load-state /tmp/vibe_after_boy_1200.bin \\\n  --frames 400 \\\n  --input-script '0:20:a' \\\n  --screenshot /tmp/vibe_native_name.png \\\n  --save-state /tmp/vibe_native_name.bin \\\n  --dump-state\n```\n\nExpected shape:\n\n- `CPU ... gameplay_hle=false legacy_flow_hle=false`\n- `GMAIN cb2=080e4f59`\n- active naming-screen tasks around `080e...`\n- screenshot shows the native `YOUR NAME?` keyboard.\n\nFrom that naming-screen state, pressing `Start` then `A` accepts the default name and returns to Birch's name-confirmation menu:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --load-state /tmp/vibe_native_name.bin \\\n  --frames 300 \\\n  --input-script '20:8:start,80:8:a' \\\n  --screenshot /tmp/vibe_after_name_ok.png \\\n  --save-state /tmp/vibe_after_name_ok.bin \\\n  --dump-state\n```\n\nFrom the name-confirmation menu, pressing `A` advances through the native Birch/title-to-field path into the moving truck:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --load-state /tmp/vibe_after_name_ok.bin \\\n  --frames 800 \\\n  --input-script '20:8:a' \\\n  --screenshot /tmp/vibe_native_truck.png \\\n  --save-state /tmp/vibe_native_truck.bin \\\n  --dump-state\n```\n\nExpected shape:\n\n- `CPU ... gameplay_hle=false legacy_flow_hle=false`\n- `GMAIN cb1=08085e05 cb2=08085e5d`\n- `FIELD ... layoutId=00ed`\n- non-zero `PC_HITS` for `CB1_Overworld`, `DoCB1_Overworld`, `FieldGetPlayerInput`, `ProcessPlayerFieldInput`, and `PlayerStep`.\n\nFrom the truck state, holding Right currently triggers Emerald's native truck exit / Littleroot transition:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --load-state /tmp/vibe_native_truck.bin \\\n  --frames 160 \\\n  --input-script '0:140:right' \\\n  --screenshot /tmp/vibe_native_littleroot_from_truck.png \\\n  --dump-state\n```\n\nExpected shape:\n\n- `GMAIN cb1=08085e05 cb2=08085e5d`\n- `FIELD ... layoutId=000a`\n- `PLAYER_OBJECT` reports native object-event coordinates near `cur=(10, 17)`\n- `WATCHLOG` contains object/player writes generated by the native PC path, while both HLE guard flags remain false.\n\nWhen `--dump-state` is enabled, the CPU line should include both `gameplay_hle=false` and `legacy_flow_hle=false`. That is the important guardrail for the current native-loop work: scripted inputs may press buttons, but movement and gameplay progression must not come from direct player-coordinate, sprite/OAM, map-var, flag, party-data, callback, or task-function writes.\n\nThe old local development run used save states plus direct gameplay HLE to verify the starter milestone. Those state files are not committed because they may contain ROM-derived data, and that HLE route is now legacy-only. If you have generated compatible local states, the old command shape looked like this, but it is no longer a passing criterion for the native-loop goal:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --load-state states/littleroot_entry_fix15.bin \\\n  --frames 820 \\\n  --input-script '0:54:right,90:125:up,300:32:left,350:8:up,420:40:a,580:40:a' \\\n  --save-state /tmp/vibe_littleroot_to_starter_lab.bin \\\n  --screenshot /tmp/vibe_littleroot_to_starter_lab.png \\\n  --dump-state\n```\n\nFormer legacy-HLE debug markers:\n\n- `layoutId=003a`\n- `callback2=08085e5d`\n- `PLAYER_OBJECT` reports `map=1.4`\n- `SAVE route101=3 birch_lab=3 starter=0` plus party debug showing a starter in the party\n- `SAVE_FLAGS pokemon_get=true rescued_birch=true hide_bag=true hide_zigzagoon=true hide_lab_birch=false`\n- The screenshot is Professor Birch's lab with the player and Birch visible.\n\nThe current no-gameplay-HLE Littleroot native-loop probe can be run from a locally generated Littleroot development state:\n\n```bash\ntarget/release/vibe-gba \"/path/to/Pokemon Emerald.gba\" \\\n  --load-state states/littleroot_entry_fix15.bin \\\n  --frames 200 \\\n  --input-script '0:200:right' \\\n  --screenshot /tmp/littleroot_native_probe.png \\\n  --dump-state\n```\n\nThe dump should include `gameplay_hle=false`. The important native-loop proof is the function chain, not just the screenshot:\n\nCurrent expected diagnostic shape for that probe after the `ObjAffineSet` fix:\n\n- `GMAIN held=0010` or another D-pad bit proves KEYINPUT reached Emerald's `gMain`.\n- `PC_HITS` should show non-zero counts for `CB1_Overworld`, `DoCB1_Overworld`, `FieldGetPlayerInput`, `ProcessPlayerFieldInput`, and `PlayerStep`.\n- `PLAYER_OBJECT` should change through Emerald's object-event movement path, for example `face=4 moveDir=4`, `heldMove`, `action=28`, and a changed `cur=(x, y)` when holding Right.\n- `WATCHLOG` records writes to player/object fields such as `obj00.action`, `obj00.flags`, `obj00.cur*`, and `player.tile`; these are expected only when they are produced by the native PC path above, not by gameplay HLE.\n- This state-based probe is not the final acceptance test. Use the full fresh-ROM M1 check above for the no-save-state route through truck/Littleroot/Route 101/starter acquisition.\n\n## CLI\n\n```text\nvibe-gba \u003crom.gba|zip\u003e\n  --save file.sav\n  --frames N\n  --screenshot out.png\n  --save-state state.bin\n  --load-state state.bin\n  --dump-state\n  --hold-a\n  --hold-start\n  --input-script frame:duration:buttons,...\n  --stop-pc HEX\n  --stop-hit N\n  --stop-invalid\n  --max-steps N\n  --turbo\n  --trace\n```\n\nInput script example:\n\n```text\n0:32:down,120:64:right,260:16:a\n```\n\n## Implementation Notes\n\n- `src/cpu.rs`: ARM/Thumb CPU core plus disabled legacy Emerald milestone HLE hooks for early movement, map transitions, and starter acquisition.\n- `src/bios.rs`: BIOS SWI helpers, including `ObjAffineSet`; the destination offset is byte-based and is covered by a regression test because Emerald uses `offset=2` for stack-local affine matrices.\n- `src/bus.rs`: memory map, IO, DMA, timers, save memory, state serialization, and debug watch logging. The bus no longer contains Emerald-specific VBlank helpers for DMA3 request queues or window tile mirroring; Emerald's own code must drive those through emulated hardware.\n- `src/ppu.rs`: framebuffer rendering for bitmap/text/affine backgrounds, OBJ sprites, and blending.\n- `src/cartridge.rs`: raw and zipped ROM loading plus flash save behavior.\n- `src/gba.rs`: emulator orchestration, frame stepping, save states, and debug summaries, including read-only Emerald progress/party diagnostics.\n- `src/main.rs`: CLI, keyboard input, window loop, screenshots, and scripted input.\n\nThe original development prompt is preserved in [PROMPT.md](PROMPT.md).\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoodlewind%2Fvibe-gba","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoodlewind%2Fvibe-gba","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoodlewind%2Fvibe-gba/lists"}