The battle menu, as four buttons instead of a list.
A mod for Gen1Recomp.
FIGHT, PKMN, ITEM and RUN each get a bordered box of
their own, laid out two by two across the bottom of the screen. The four moves
get the same four boxes when you pick FIGHT.
Nothing new was invented to draw them. A button here is Font.drawBox — the
same call, the same border glyphs, the same white interior every text box in
the game is made of — so a skin or a font mod that redraws the border redraws
these buttons with it, and this mod never has to know it happened.
That decision is also what fixes the sizes. A Game Boy text box spends its first and last tile row on its border, so the smallest box that can hold a line of text is three tile rows. Two rows of buttons is six, which is exactly the six rows the classic layout's bottom strip has:
rows 12-14 FIGHT | PKMN four 10x3 boxes, tiling
rows 15-17 ITEM | RUN the twenty-by-six strip exactly
The engine has always read the command menu as a 2×2 — col = (i-1) % 2,
row = (i-1) // 2, which is why ← and → already crossed
between FIGHT and PKMN. It was only ever drawn as four words in one box.
So none of the menu's behaviour is replaced here: menuIndex is still the
engine's, still means what it meant, and is still moved by the engine's own
input handling. This is the drawing catching up with the arithmetic.
The move menu is the one that actually changes. It was a vertical list, so
← and → did nothing; the engine already has a hook for
making it a grid (battle.move_grid_navigation, which the widescreen layout
answers for itself), and this mod answers it too.
There is no handoff to get wrong. The mod asks the engine not to draw the
bottom strip for the three phases that are a menu — the command menu, the
move menu, and Mimic's copy menu — and asks for nothing else. messages is
left completely alone, so when the battle talks it is still the engine's own
text box, in the engine's own place, with the engine's own scroll and its own
blinking arrow. The buttons are simply not drawn underneath it, and they are
back on the frame the menu is.
The same rule is what keeps PKMN and ITEM working. A battle with a screen
open above it is still a battle whose strip is being drawn, so a mod claiming
it on the phase alone would take that screen's own prompts down with it. The
strip is claimed only while the battle is the top of the state stack.
The bag does not leave the menu, it opens on it. Its item list is the one
list in the game that is not a screen of its own — ListMenu's itemBox,
isOpaque = false, "a partial box the map stays visible around" — and it is
sixteen tiles at (4,2), so it stops at y=103 and the strip underneath is still
on screen.
So the buttons stay up, and the hand on ITEM goes hollow: the same
marker every list in this game leaves on the row it is acting on. No filled
hand is drawn anywhere, because the cursor is not in the grid any more.
That state is deliberately not claimed. Returning false for the battle
would take the bag's own boxes with it — How many?, the YES/NO, the use
message — because every box above a battle inherits the battle's answer. So
the engine keeps drawing its empty box and the buttons go over the top of it,
which lands in the same pixels: the four button boxes tile exactly the
twenty-by-six that box occupies.
PKMN reaches the same code and is never seen down it — PartyMenu is
opaque, so the stack stops drawing at it and the battle underneath, this
overlay included, never runs at all.
A classic cell is the box's eight interior tiles less the one kept for the cursor: seven glyphs, where the vanilla list had fourteen. So long move names are cut, with the engine's own trailing-dot idiom:
QUICK ATTACK |
QUICK. |
THUNDERSHOCK |
THUNDE. |
SAND-ATTACK |
SAND-A. |
That is the real price of the layout, and it is paid back rather than hidden. The panel above the grid carries the highlighted move's name, its type and its PP — which the vanilla list never showed all at once either.
The panel stands where the vanilla TYPE/PP box stood, (0,8), and is
fourteen tiles by five: twelve interior glyphs, which is exactly the length of
Gen 1's longest move name, so it never cuts. Name, type and PP each get a row
of their own. Fourteen is the narrowest that can promise that, and it stops 48
pixels short of the full width — which keeps it clear of the player's own HP
numbers on the right.
The move's type reads in that type's own colour in the panel, and each button's move name reads in its own — so the grid is four types at a glance and the panel says which one the cursor is on.
The letters themselves are coloured, not a field behind them. That takes a
shader: a tile glyph is black on transparent, so setColor cannot tint one,
and the way through is to throw the glyph's RGB away and keep only its alpha.
The glyph becomes a stencil and the stencil is filled with the type's colour —
same tile sheet, same font, same pixels, different ink.
The palette is darker than the familiar type colours, because these are
letters on a white box rather than a chip behind them; ICE and ELECTRIC at
their usual brightness are close to unreadable as text. A host with no
love.graphics.newShader, and a type this mod has no colour for, both draw
plain black. TYPE COLOUR turns it off.
The tile font is 8 pixels a glyph and cannot be anything else: it is a tile
sheet. A cell is seven of them. So in the game's own font a 2×2 grid cannot
print SELFDESTRUCT, whatever the boxes do.
A move menu whose names do not all fit is drawn in Plain Pixel instead — the TTF the engine already ships for its translation mode. Its advance is narrower, and the same cell holds twelve of it. The engine's own whole-game TTF mode is not switched on; the face is loaded and used for move names alone. The size is chosen rather than fixed: the largest one whose twelve glyphs still fit wins.
A grid takes it for all four names or none — GUST in one font beside
THUNDERSHOCK in another reads as a fault, not a choice. So a party whose
names all fit is vanilla to the pixel, the command menu never moves, and the
wide layout never reaches for it. FULL NAMES — off by default, because
the panel above already reads the whole name in the game's own font — turns it
on.
MOVE PANEL in the mod manager turns the panel off and gives the picture
behind it back.
OPTION → BATTLE LAYOUT → WIDE gets the same grid, built differently.
Five tile rows is what that layout's strip has, and six do not fit in five without eating a row of battlefield. So instead of four boxes it is one box ruled into four cells with its own border glyphs — five rows being exactly border, text, rule, text, border. Same grid, same cells, one shared frame instead of four.
The wide cells are twelve glyphs, which is what that layout's own move grid
already gave, so names arrive whole there and nothing is cut. Its
What will X do? prompt keeps the left half of the strip, and the move
panel keeps its ten tiles on the right.
Gen 2 draws an experience bar in its own player HUD; Gen 1 has none, and this is the Gen 1 substitute. It fills as the highlighted Pokémon earns experience, runs the Gen 2 fill-hold-burst-refill on a level up, and takes the palette's own shade unless a colourised mode wants Gen 2's blue.
It was Gen1WildQOL's until 1.3.0, and it is here because of what being here fixes.
Over there it was drawn by a wrapper around battle.draw — and battle.overlay,
this mod's only way onto the screen, is the last hook inside that function.
So the bar drew after every link on that hook, at any priority. It could not
be drawn over. What it did instead was clip itself to x=88, which is where
the vanilla move panel ends; this mod's panel ends at 112, and those
twenty-four pixels were a blue line lying across the PP row every time a
move menu was up. Raising this mod's hook priority — which 1.2.1 did — changed
nothing, because priority was never what decided the order.
In one file there is nothing to decide. The bar goes down first and the grid
second, so the panel covers it the way it covers anything else beneath it, and
a panel that changes width takes the covering with it. XP BAR turns it
off.
Level up in a battle and the engine prints IVYSAUR grew to level 28!, waits
for a press, clears it, and only then opens the ATTACK/DEFENSE/SPEED
/SPECIAL window — over an empty text box, with nothing left on screen saying
what those numbers belong to. Two screens and two presses.
The ROM prints one. GrewLevelText ends in text_end rather than prompt,
so PrintText returns without ever blinking the arrow, and PrintStatsBox
draws its window into the screen that line is still on; the button press
that follows dismisses the pair together.
So the line is re-marked as what the ROM makes it — the engine's own auto
row kind, the one the used-move line rides through its animation, which leaves
the typed page drawn under whatever runs next — and the level-up jingle moves
onto the stat box, which is the beat sound_level_up lands on anyway. Nothing
is queued and nothing is inserted: the two rows the engine already made come
back with two flags moved between them, so everything queued after them stays
exactly where it was. LEVEL-UP BOX turns it off and gives the engine's
two screens back.
Under COLORS = ADVANCED every sprite in a battle takes its colour from
the SGB zone underneath it, and a thrown ball is a sprite like any other — so
a GREAT BALL and an ULTRA BALL came out the same colour as each other and
as the grass behind them. Now the toss, the wobbles and the ball resting
through the caught text are each ball's own colours: red, blue, gold, purple,
olive.
There is one place that is decided — BattleState:animSpriteColors, the funnel
drawAnimLayer builds its colour function from for the playing animation and
the resting ball alike — so there is one wrap, and it hands the engine's own
answer straight back for every sprite that is not a ball.
The band along the seam is a third colour a two-tone sprite does not have.
The ball tiles do use all three opaque DMG indices — the bottom crescent, the
body mass, the outline ring — but vanilla's rOBP0 shade map collapses two of
them onto one shade, and the pixels a real Poké Ball's band runs through are
body pixels, indistinguishable from the rest of it. So the band comes with
re-indexed art: a copy of the sheet in which those pixels move onto index 3 and
the outline ring moves onto 2. Painted { accent, body, line } that is the
band; painted { accent, body, body } it is pixel-identical to vanilla, which
is what a ball with no band and a player with BALL BAND off both get.
That copy is rebuilt at runtime from your own extracted sheet. This art is ROM-derived, the engine is built so Nintendo's graphics come out of your cartridge and are never redistributed, and a mod has no business being the exception: what ships here is a table of which pixels play which part.
The MASTER and ULTRA tosses keep their palette strobe, in their own
colours now — and the band holds still through it, because OBJ_SHADES leaves
index 3 on the dark shade in both halves of the flash. Poof clouds and every
other battle animation are left exactly as vanilla, and so is every colour
mode that is not ADVANCED: those deliberately have no per-sprite colour to
give, and their nil is passed straight through.
The heal machine lights one ball per party member and painted all six the
same. Now each one is the ball that Pokémon was caught in — a party of
GREAT BALL catches heals blue.
Gen 1 records nothing about what caught a Pokémon — not in the engine, and not in the ROM it recompiles, whose party structure is species, HP, status, types, catch rate, moves, OT, exp, stat exp, DVs, PP and level with no ball anywhere in it. Nor does Gen 2: the caught data Crystal adds is time, level, location and OT gender, and this engine's own Gen 2 mon carries exactly those four. Gen 3 is the first generation to record the ball at all — four bits inside the origins halfword of the Misc substruct, holding the ball's item index. So this feature is not free in Gen 1, and here is exactly what it costs:
It writes one field into your save. mon.caughtBall goes onto the
Pokémon, and a Pokémon is save.party[i] — SaveSerializer is a generic
pairs() recursion that writes out every key it finds, so the field lands in
the save file beside species and dvs, and it stays there if you uninstall
the mod. One string per Pokémon you catch. Nothing else reads it.
The engine does offer a namespaced alternative — mod.save, backed by
save.modData["Gen1BattleUI"], which would vanish cleanly with the mod. It is
not used, on purpose. A side table needs a key, and a Gen 1 Pokémon has no
unique id: the best available key is a content fingerprint (OT id, nickname,
DVs) that can collide, or a party slot that cannot survive a deposit. A field
on the Pokémon needs no key at all — it goes where the Pokémon goes, through
the box, through a trade, forever. It is also the field Pokeball Colors
already owns, written by the same only-if-absent rule, which is what lets the
two of them share a save and never disagree.
Anything caught before you installed this reads as a POKE BALL, and corrects
itself as the party turns over. CENTER BALLS turns the whole thing off,
though the field is still recorded — it costs nothing to keep and means
switching it back on works on the party you already have.
fxHeal is a local closure inside OverworldState:drawWorld and cannot be
wrapped, and drawing after drawWorld returns lands in the wrong space — so
drawWorld is wrapped instead, and only while a heal is actually running is
love.graphics.draw shimmed. The shim recognises the ball draws exactly (that
image, that quad), counts them, and the i-th is party slot i, because the
machine lights them in party order. It paints through the same shader the
machine's own jingle flash uses, so a recoloured ball flashes with the rest of
the machine instead of sitting still through it.
The colours and the two seams are Pokeball Colors by Mister Miracle (MIT), ported down to what Red, Blue and Yellow actually ship: the five native balls and nothing else.
Left behind on purpose: its registerColors and registerColorResolver
registries, the colours it keeps for Custom Poké Balls, Too Many Balls
and Snag Quest, its Gold heal machine, and its dev toggle that stocks every
ball in every mart. A ball from a mod is that mod's business, and that mod is
where it is answered — install Pokeball Colors and this file stands down
whole, colours and Center and all, rather than the two of them wrapping one
funnel and arguing about it.
| Row | Default | What it does |
|---|---|---|
MOVE PANEL |
on | The name, type and PP of the highlighted move, above the grid, where the vanilla TYPE/PP box stood. Off gives the picture behind it back — and, on the wide layout, gives its tiles to the grid, because a strip that stops short of the screen edge is a hole in the frame rather than a saving. |
XP BAR |
on | A Gen 2 style experience bar under your Pokémon, filling towards the next level, with Gen 2's fill-hold-burst-refill on a level up. Drawn before the move panel, so the panel covers it rather than the other way round. |
TYPE COLOUR |
on | The type in the panel and each move name on its button, in that type's own colour, drawn through a shader that inks the game's own glyphs. Off is plain black text. |
LEVEL-UP BOX |
on | The level-up stat window over the grew to level line rather than after it, dismissed together, the way text_end + PrintStatsBox prints it in the ROM. Off is the engine's two screens, with the text box under the window blank. |
BALL COLOUR |
on | The ball you throw in its own colours — the toss, the wobbles and the ball resting through the caught text — for the five balls Red, Blue and Yellow ship with. COLORS = ADVANCED only; the mono modes have no per-sprite colour to give and are passed through whatever this says. |
BALL BAND |
on | The black band along a thrown ball's seam, which needs the re-indexed art to have a third region to paint. Off is the two-tone ball on the game's own tiles, which is what a thrown ball looked like before it. |
CENTER BALLS |
on | The Pokémon Center heal machine, lighting each ball in the colours of the ball that Pokémon was caught in. Off is the machine's own one palette for all six. |
FULL NAMES |
off | Move names in the engine's Plain Pixel when they will not fit the tile font, so they print whole in the buttons too. Off — the default — is the game's own font always, cut to the cell, and the panel above is what reads the whole name. |
- The HP panels, the pictures, the animations — with the one deliberate exception of the XP bar, which is drawn under the player's HP numbers because that is where Gen 2 puts its own.
- What the menus do. Only where they are drawn. Every index, every key, every callback is still the engine's, so anything wrong with what the battle menu does is not this mod.
- Dialogue, and everything above it. The text box, its scroll, its
wait arrow, and every screen pushed over the battle — with the one
deliberate exception of the level-up line, which is re-marked as the
ROM's own
text_endpage so its stat box can come up over it rather than after it. - The HP panels, the pictures, the animations. The whole top of the screen is untouched.
- Seven glyphs is seven glyphs in the tile font, and no arrangement of two
columns inside 160 pixels changes that.
FULL NAMESis the way out and it is a second face, not a cleverer layout; turning it off puts the cut back. - The small face is not the game's font, so a font mod or a skin that
redraws the tile sheet does not reach it — unlike the boxes, which are
Font.drawBoxand do. That is the tradeFULL NAMESmakes. THROW ROCKis cut in a Safari battle — ten glyphs against seven. It readsTHROW., with the trailing space taken off the cut rather than left asTHROW .. The wide layout has room and prints it whole.- The colour needs a shader, which is the one thing here that a host can
simply not have. Without
love.graphics.newShaderthe letters are black and everything else is unchanged — which is the picture this mod drew before it had any colour at all. - The overlay draws after the palette pass, so the buttons are black on
white and are not recoloured by a
COLORSzone the way the vanilla strip's border is. On the default look this is the same picture; under a mode that tints the text box it is not. - The level-up jingle loses
WaitForSoundToFinish, because the stat box it now rides in with parks the queue itself until you dismiss it, which is the clear window that wait was there to give it. Mash through the box fast enough and the next line can start over the tail of the jingle. - A mod that awards the EXP itself has to let this mod read the queue.
This mod's
battle.exp_awardlink runs outermost (priority 5000) because it callsnext()and then reads the rows the chain queued. Gen1WildQOL'sEXP SHAREwraps the same hook at priority 90 and, in every mode butOFF, awards the exp and returns without calling on — which used to leave this mod's link unreached and the stat box back over a blank text box. Fixed in 1.5.1 by being the outermost link; a mod that sits outside this one and never calls through would put it back, and the log now says so when it happens. - A
RARE CANDYoutside a battle is unchanged. That level-up is the party menu's, printed through the map's own text box, and this is a battle mod. - The first ball of a session has no band. The re-indexed sheet is only safe to serve once this mod's palette has been seen to reach a ball, because anything that blits it raw draws a grey ball with a black stripe — which is exactly what a sprite pack that ships its own pre-coloured ball art and suppresses the palette pass does. So the first throw is the two-tone ball and the band arrives with the second; and if that contradiction ever does turn up, the band switches off for the session and the other mod's own artwork shows through, which is the right outcome — their balls are already coloured.
- A ball from another mod keeps its vanilla colours, and says so once in the log naming the ball. This mod covers the five native balls; Pokeball Colors is the mod that covers the rest, and installing it stands this one down entirely.
- The
ULTRA BALLlooks like it turns over during the toss. That is the Master/Ultra palette flicker, which is on the hardware, in the ball's own colours now — and it stops at the wobbles becauseSHAKE_ANIMnever flickers. - Pokémon caught before this installed heal as
POKE BALL, because there was nothing recording what caught them. It corrects itself as the party turns over. - The caught ball does not survive an export to a real
.sav. It is not how the games flag anything:mon.caughtBallis a Lua key on a mon table, which is how this engine stores every other field too, but the real Gen 1 format has no byte for it.GenSave.encodeMonwrites fixed offsets from a known field list, so on export the field is simply not written — convert out and back and the machine lights every ball red again, until the party turns over. Nothing else about the Pokémon is affected. - The Center feature writes to your save, and it is the only thing here that
does. One field,
mon.caughtBall, on each Pokémon you catch, in the save file beside the vanilla party data — and still there if you uninstall the mod. Gen 1 records nothing about what caught a Pokémon, so there is no version of this feature that costs nothing. Everything else in this mod — every button, the panel, the XP bar, the level-up box, the battle ball colours — adds no byte to a save. - A throw while drawing costs a frame's buttons, not the battle. The draw
is wrapped and logged; a load-time failure is not swallowed, and marks the
row enabled-but-broken in
MODSwith the reason, because an enabled mod that silently changes nothing looks exactly like one that was never installed.
MODS → Import mod .zip, or add the index at MODS → FIND MODS:
wild1walker/Gen1Wild
- Gen1Recomp — the battle hooks this is built on:
battle.overlay,battle.bottom_ui_visible,battle.move_grid_navigationandbattle.exp_awardwere already there, and this mod is what happens when a mod uses all four. - pret/pokered —
engine/battle/core.asm, whoseDisplayBattleMenuandMoveSelectionMenuare the two screens this re-dresses, and whose 2×2 reading ofmenuIndexis why the command menu needed no new input handling. - Plain Pixel by Douglas Vautour (CC-BY 4.0), which Gen1Recomp bundles for its translation mode and which this mod borrows for move names.
- unxpected-uxp — the XP bar, from their Quality of Life mod by way of Gen1WildQOL, which maintained it and wrote the faint guard it still carries.
- Pokeball Colors by Mister Miracle (MIT) — the ball colours, the band's re-indexing table and both seams the ball colouring is drawn through. Ported here for the five native balls; its whole other-mods surface stays over there, and this mod stands down when it is installed.
- Nintendo / Creatures / GAME FREAK — Pokémon Red, Blue and Yellow. Unofficial fan mod, no affiliation, no endorsement.
MIT. See LICENSE and THIRD_PARTY_NOTICES.md.


