Gamebuino Classic games ported to vircon32 selectable in one convient menu
This project’s default policy is to preserve real upstream Gamebuino Classic bugs, even ones that look like mistakes, rather than silently “clean them up” - a faithful port should behave like the real cartridge it was built from. Bugs are only fixed when they’d crash, hang, or corrupt state specifically on this platform (Vircon32 traps on things real AVR hardware silently tolerates, like integer divide-by-zero), or - as of this sweep - when a real bug was judged a genuine, negative hit to player experience and fixed on direct request, overriding the default.
This file lists every currently-known deliberately-preserved gameplay
bug across all 99 games, and every bug that was found and fixed
instead, so both are documented in one place rather than scattered
across each game’s own header comment. See each file’s own header comment
in src/games/ for the full technical detail behind any single entry.
Bugs that are purely cosmetic with no real gameplay consequence, dead code with zero observable effect, or dropped real-hardware/multiplayer-only features are not listed here at all - only bugs that actually change what a player experiences.
gameStijnSnake.c): real upstream here isn’t a game
with bugs to preserve or fix in the first place - both real source files
(SnakeStart.ino/Snake2.ino) are genuinely incomplete tutorial stubs.
Nothing anywhere in either file ever reads a direction button, moves a
coordinate, spawns food, grows the snake, or ends a round - run as
upstream actually shipped it, “Snake” is a single static pixel frozen at
screen center, forever. This port completes a real, playable game
instead, guided directly by what upstream left as evidence of its own
intent (declared-but-never-used vx/vy/score globals, and a header
comment describing real head-growth list semantics) rather than invented
from nothing - documented in full in the file’s own header comment under
“INVENTED GAMEPLAY DETAILS.” Not listed as a fixed or preserved bug
above because there was no real upstream gameplay logic to diverge from
either way.These were found to negatively affect gameplay and fixed, overriding this project’s normal “preserve real bugs” default - see each entry for what changed. Every fix is documented in-place in its own file’s header comment too, marked “FIXED, NOT PRESERVED”.
gameAgaruino.c): The “Taille : “ (size) readout is a real
upstream float, and real hardware’s own default float printing shows 2
decimal places (e.g. “Taille : 3.75”) - this shim had no float-printing
primitive at all, so the port cast the value to int before printing,
silently truncating away the entire fractional part every tick. Found via
a direct side-by-side comparison against a real emulator screenshot.
Fixed by adding a new shared gbPrintFloat() primitive (a direct port of
real Arduino’s own Print::printFloat() algorithm) and calling it here;
the on-screen label text was also corrected from this port’s own
“TAILLE “ to upstream’s own real, literal “Taille : “.gameAsteroidRipper.c): Pausing used to freeze the
entire screen with no indication the game was paused rather than frozen/
crashed. A “PAUSED” label is now drawn while paused.gameBangBang.c): Pausing mid-game used to show a
leftover “TAQUIN” title screen (a copy-paste bug from the same author’s
other game) instead of “BANG! BANG!”. Fixed to show the real title.gameBlocksBuino.c): The playfield’s own real double-
line side border never drew at all - taken completely literally, real
upstream’s own drawRect(field_x, field_y, field_w, field_h) calls use
an off-screen field_y (49, one row below the real 48px-tall screen)
and a negative field_h (-49), which make every edge either off-screen
or a no-op against the real Display class’s own literal semantics. An
earlier pass through this file took that math at face value and shipped
it as a preserved no-op. That conclusion was wrong: a real screenshot
bundled in upstream’s own repo, and a live user report against a real
emulator, both clearly show a full-height double-line border down each
side of the field with no horizontal cap at top or bottom. Fixed by
reading field_y/field_h as “anchored at the bottom edge, height
extending upward” for the 4 real vertical edges only (matching the
observed shape exactly), while leaving the 2 horizontal edges at their
original, always-off-screen position.gameBlobAttack.c):
gameBlockdude.c):
> 0 - every level falsely showed as completed on a fresh
save. Fixed with a fresh-cell sentinel check.doLift/
liftBlock are now reset on the auto-advance path.gameCastleDefence.c):
<= 0 game-over check.<= 0 at death - a player who’d been climbing could
leave it stuck permanently positive, meaning the Game Over screen (and
its only exit) might never appear. Fixed to converge from either side.gbUpdate()’s own doc comment
in gamebuinoShim.c for the shared primitive that provides this.gameCatcher.c): A missed single-tick button-release pulse
on Button-C restart could leave the pad-input state stuck on “C”,
risking a repeating auto-restart loop. Fixed by explicitly clearing the
pad-hit state on every reset.gameCrazyTown.c):
abs() typo meant the “distance driven”
accumulator never actually added anything - the distance readout was
always “000000” and the drive-efficiency scoring bonus always resolved
to its floor value. Fixed to use fabs(), the clearly-intended
formula.gameDescent.c): A monster-spawn retry loop’s
condition was an assignment instead of a comparison, so it always ran
once - overlapping monster spawns were never actually retried. Fixed to
a genuine comparison.gameDigger.c): Game-over reset never reloaded the level
layout - a new life resumed on the previous run’s half-dug board. Fixed
to reload a fresh level.gameFiremen.c): Quitting with Button C double-read the
same press within one tick, skipping the Game Over screen (final score/
highscore) entirely. Fixed by making the transition a genuine deferred
state change instead of a same-tick synchronous call.gameGruniozerca.c): A fresh EEPROM cell composed to
255, showing an already-maxed-out top score on a save that had never
actually been played. Fixed with a fresh-cell sentinel check.gameJezzball.c): The EEPROM “reset magic bytes” routine
had a hardcoded address-0 typo instead of using its own loop variable,
so the high score could never actually persist across a save/reload.
Fixed to write the correct address.gameLightsOutAD.c): The “You won!” screen’s time
readout was recomputed every frame instead of once on entry, so it kept
counting up for as long as the player lingered on the screen instead of
showing the real, static finish time. Fixed to snapshot the time once.gameMaruino.c): The “enter a code” screen’s own on-screen
text said Button C returns to the menu, but only Button A was actually
checked - C did nothing there. Fixed so C genuinely returns to the menu.gameMotoCross.c): Real upstream’s own debug speed
readout (print(player1.vx), a real float) was ported with the same
“no float-print primitive exists” gap as Agaruino above - the value was
truncated to int before printing, missing the fractional digit real
hardware would show. Fixed once gbPrintFloat() was promoted to the
shared shim for Agaruino’s own identical need - this file’s own call
site now prints the genuine fractional value.gameShootBuino.c):
player_life was only ever initialized once and never reset on a new
game - after dying once, every subsequent restart started at 0 life
and died on the very first hit for the rest of the session. Fixed by
resetting it in sbuinoInitGame().fillRect(...,1,
-2)), which draws nothing at all - completely invisible bullets that
could still hit the player. Fixed to draw a real 2px vertical bullet.gameSkibuino.c): Choosing “Title Screen” from the pause
menu never saved the high score (only a crash/game-over did) - quitting
mid-run silently discarded a genuinely higher distance. Fixed to save
on that path too.gameWhg.c): A fresh EEPROM byte decoded as
255, and the “completed” check was > 0 - every level falsely showed
as completed on a fresh save. Fixed with a fresh-cell sentinel check.
(The separate “tries” counter accumulating across levels instead of
resetting per level is left preserved - see below.)Deliberately left as real, shipped upstream behavior. Grouped by game.
101 Starships, Simonbuino, Tetrino, B-Rally, Sokobuino, Breakout Ripper, Lights Out AD, Bub, Video Poker, ZombiEscape, Maze, 2048, Shufflepuck Cafe, Conduit, StickFighter, Tron, UFO Race, Asterocks, Paqman, DeathMaze, FlappyBirdo, Punkt, No Name Platform Game, Master Kebab - no gameplay-negative preserved bugs found in the original sweep. (Stijn’s Snake is its own special case - see above, not this list.)
gameA2K.c): Every D-pad press spawns a new tile even when
the move changed nothing on the board, speeding up the fill-the-board
loss condition.gameAerialAssault.c):
wait==0||10-style throttle checks are unconditionally true -
the pterodactyl and floater move/react far more aggressively than
intended.< instead of >=).random() exclusive-upper-bound bugs permanently disable the
floater’s leftward movement and one of three spawn points.case 2: fallthrough bug was already fixed in an
earlier session, on direct request - not preserved.)gameAgaruino.c): The viewport-culling test uses ||
where it needs &&, so the player’s own blob is never drawn -
permanently invisible.gameAimbuino.c): The aim-reset angle computes (1/4)*PI
as integer division (truncates to 0) - every fresh shot starts aimed
due-right instead of 45°-up-right.gameArmageddon.c): The enemy-missile launch-chance-
scales-with-difficulty formula is dead code - frequency never actually
increases as the game progresses.gameArtillery.c):
gameAsteroidRipper.c):
gameBangBang.c): The advertised second player-cannon
(cannon2) never aims or shoots - always a static sinking target, no
real duel. Confirmed directly from the real source, not just inferred:
PLAYER2AIMING/PLAYER2SHOOTING are defined constants that gamestate
is never once assigned anywhere in the file - every real transition
only ever goes between PLAYER1AIMING/PLAYER1SHOOTING/END, and the
one PLAYER2SHOOTING reference left in the source is inside a
commented-out line. Marked unfinished in the menu (red text, info
“Player 2 never shoots”) rather than fixed, since there’s no real
upstream player-2 AI logic to restore - it was never implemented at
all, on real hardware either. (The leftover “TAQUIN” pause title is now
fixed - see above.)gameBigBlackBox.c): A patrol enemy’s direction-
reversal is two independent ifs instead of if/else, so it can take
one extra step right after reversing - inconsistent patrol timing.gameBlocksBuino.c):
gameCopterStrike.c):
gameCopter.c): Mid-game restart never resets buildings’
drifted X/Y positions - a new attempt starts with stale scenery.gameCrazyCar.c): Restart after game-over resets the car
and lanes but not obstacle X positions or scroll offsets.gameDarkShmup.c):
break makes the boss run both its own AND another enemy
type’s update logic every tick.gameDarkTower.c): The well is one-way, and the rope
needed to retrieve the crystal only appears in the cellar’s object list
on the first visit - climbing out without grabbing it first permanently
locks the player out of finishing the game.gameDescent.c):
gameFifteen.c):
gameFirebuino.c): Pressing both equivalent buttons for
one direction on the same tick moves the stretcher two steps instead of
one.gameFootlol.c): Both collision-response blocks mix the
current object’s own vx with the other object’s vy - inconsistent,
asymmetric bounce physics.gameGemgem.c):
gameGlaciGlaca.c): Restart only resets ice-cream
flavor stock, never cone/cornet/luxe-cornet stock.gameGruniozerca.c): The acceleration counter keeps
incrementing while pinned against a screen edge - holding into a wall
then reversing snaps straight to top speed.gameKillrace.c): handledeath() is called twice per
tick during the crash animation, halving its intended duration.gameLander.c): Same double-call bug as KillRace - the
death animation and “SHIPS LEFT” countdown play back twice as fast.gameHexagon.c):
abs(rot_speed < SPEED_CAP) applies abs() to the comparison’s
boolean result, not the speed - the rotation-speed cap is a no-op.gameMinesweeper.c): Mine placement uses an exclusive-
upper-bound random range on both axes - the rightmost column and bottom
row can never contain a mine.gameMoleControl.c):
duration + current time,
narrowed to signed 16-bit - restarting after ~30 seconds wraps
negative, draining all lives in under a second.gameMotoCross.c): Holding LEFT in the wrong pose can
incorrectly fall through to the “fallen off” (KO) pose every 10th frame.gamePinball.c): Restart resets ball/lives/flippers but
never score or leftover velocity; losing all lives just wraps the life
counter back to 2 instead of ending the game.gamePong2017.c):
gameRobot.c):
gameSavePrincesse.c): Hit/attack collision always
uses the wider “attacking pose” hitbox regardless of the pose actually
drawn.gameSenet.c): The winner screen always prints “CPU WON”
whenever player 1 doesn’t win, with no mode check.gameShipwrek.c): Game-over is only checked once per full
turn - a fast player can sneak in an extra bonus turn.gameSmash.c):
random(0,1)’s exclusive upper bound means the ball never moves
right.gameSnake5110.c): The “remove last wall segment on an
axis” check has an inverted condition - one wall segment per axis can
never be cleared.gameSnakeAbc.c): Letter-pickup collision checks the
head’s pre-move position - the snake must sit visibly on the letter for
one extra move before it registers.gameSnakeClassic.c):
gameSolitaire.c): Because persistence is forced true
before any frame draws, the win-condition path meant to trigger the full
celebration and auto-return to title is unreachable.gameStarHonor.c): The repair-completion message table
has “Engines” and “Shields” swapped.gameStijnPong.c): resetBall() recenters using
LCDWIDTH instead of LCDHEIGHT - the ball re-spawns near the bottom
of the screen instead of vertical center.gameSuperCrateBuino.c):
switch fallthrough gives the Machine Gun the Shotgun’s velocity
spread.gameSuperSpaceShooter.c): There is no real
enemy spawner - a “temporary testing” button is the only way any enemy
ever appears.gameThunderShoot.c):
gameTrexQuest.c):
gameTaquin.c): A blocked D-pad press still increments the
move counter even though nothing moved.gameWhg.c): The “tries” counter only resets
when a level is freshly picked from the menu, not on auto-advance after
winning - tries accumulate across levels instead of resetting per level.
(The fresh-EEPROM false-“completed” bug is now fixed - see above.)gameXonix.c): An enemy’s bounce logic only tests orthogonal
neighbor cells before a diagonal move - it can briefly step onto claimed
territory that should be safe.