Skip to content

Expose a generic battle presentation compatibility hook - #2

Open
ArmstrongThomas wants to merge 121 commits into
absol89:masterfrom
ArmstrongThomas:feature/battle-art
Open

Expose a generic battle presentation compatibility hook#2
ArmstrongThomas wants to merge 121 commits into
absol89:masterfrom
ArmstrongThomas:feature/battle-art

Conversation

@ArmstrongThomas

@ArmstrongThomas ArmstrongThomas commented Aug 9, 2026

Copy link
Copy Markdown

Summary

  • Add the provider-neutral battle.presentation.suppress_native.v1 hook.
  • Expose mod.exports.battlePresentation for feature discovery.
  • Remove the hardcoded Gen1 Modern UI monkey patch and direct option reads.
  • Fail open so native battle surfaces remain visible when hooks are absent or error.

Verification

  • Focused contract test passed locally.
  • Relevant Lua syntax checks passed.
  • Staged whitespace checks passed.

The local test changes used for verification are intentionally not included in this pull request.

absol89 and others added 28 commits August 6, 2026 06:58
The BATTLE BG force edit dropped Voxel.update(dt, level) from update(),
which stopped the voxel pipeline from advancing and broke mode switching
(the 3 key). Restore it immediately after applyFull.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The ported XP bar (from unxpected-uxp's QoL mod) never tracked correctly:
it drew on the wrong HUD side, did not follow the moving textbox, and our
positioning hacks kept failing. It is better handled by dedicated mods, so
remove it completely -- the lib/ExpBar.lua file, the OverworldBattle require
and draw call, and the main.lua require and options row.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The EXP bar is owned by other mods, not this one. Those mods draw their
bar assuming a native 1X HUD, but this mod scales the player/opponent HUD
with the battle zoom (hs = zoom - 1). Add a HUD SCALE setting:

  SCALED (default) -- hs follows the battle zoom, the mod's look
  1X               -- pins the HUD to native size (hs = 1) so an external
                     XP-bar mod lines up

Implemented by overriding hs in OverworldBattle.snapRects; snapHUDs and
the textbox backplates consume that same hs, so the toggle covers the
whole HUD. Exposed as a FULL-preset-safe options row.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The dialogue box backplate is drawn into the GB canvas at the static
GB-frame rect {0,96,160,48}. The engine's DYNAMIC uiLayout docks the box
to the window's bottom edge (window space, full width), so under any
non-1:1 aspect the box and the in-canvas backplate diverge -- they only
coincide near 1:1. Force CENTERED uiLayout when a battle begins so the box
stays inside the 160x144 frame, where the backplate already is, at every
window aspect. This is also the classic battle composition.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The in-game mod manager's ModUpdate.parseRelease requires a strict
X.Y.Z tag (version:match("^(%d+%.%d+%.%d+)$")). Two-part tags like 1.71 /
v1.71 are silently dropped, so the manager only saw the old three-part
releases (1.3.1) and ignored everything recent. Bumping to 1.72.0 makes
the release parse, and the ps1/release.yml already name the zip
<id>-<version>.zip = BATTLE_ART_VOXEL_FORK-1.72.0.zip, which
pickZipAsset prefers. Semver.parse accepts two-part so installed 1.71
users still get the offer.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The previous 1X mode set hs = 1, which collapsed the HUD to one GB pixel
per screen pixel -- tiny on a large/high-DPI window. Rename it to WINDOW
and set hs = shot.scale (Renderer:fitScale, the window-fit scale), matching
upstream DynamicShapes' player HUD. That is the scale external XP-bar mods
adapted to, so they line up. The backplates are untouched -- only the HUD's
on-screen size tracks the window. SCALED (default) keeps hs = zoom - 1.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
…igin

- TEXTBOX FILL: drop our own GB-canvas slab. The engine draws the dialogue
  box (Font.drawBox) in SCREEN space, docked to the window edge, so it already
  tracks the box at every aspect ratio. Our static-rect slab in the GB canvas
  could never follow a window-docked box (they only coincided at ~1:1), which
  is why it drifted. The engine's white box is now the backplate.
- Player HUD: remove the +2 breathing-room margin on the player rect. It
  shifted the whole HUD left by 2*hs vs the engine's player status box, so an
  external EXP-bar mod (which anchors to that box) sat 2px to the right. Our
  player HUD now shares the engine's status-box origin.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
- Capture the free-roam attitude (yaw/pitch/zoom) into FirstPerson.last*
  / ThirdPerson.lastZoom WHILE the freecam is engaged, and hold it after the
  rung is dropped. The staged battle now seeds from these last-known values
  instead of the live FirstPerson/ThirdPerson numbers, which are stale during
  a battle (the freecam is not driving) and reset to the sprite facing on the
  frame the rung is re-entered. So battles open where the player last looked,
  not where the camera happened to be pointing at battle start.

- BattleCam.reset no longer zeroes the drift phase (BattleCam.t): the slow
  parallax breath now continues from the previous battle's phase instead of
  snapping back to 0 every encounter.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
…aspects)

Wrap the engine's Font.drawBox so the battle dialogue box's backplate is
controlled by TEXTBOX FILL, applied inside the 160x144 UI canvas the box
lives in -- so it tracks the box at every window aspect (a world-canvas
slab could never follow a box the engine letterboxes).

- OFF  -> the engine's white fill is skipped: the backplate is invisible, the
  box border and the ink still draw.
- HALF -> a translucent-black fill (the upstream default) over the diorama.

Gated on a live battle session and the unique dialogue-box tile rect
{0,12,20,6}, so menus/summary/overworld boxes are untouched.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
When ARENA FILL: WHITE paints a solid white field, a dark HALF backplate
would read as a grey slab on white. Override TEXTBOX FILL: HALF to an
opaque white fill in that view, so the box is a plain white Game Boy panel
with the inverted black ink on top -- no dark engine backplate over white.

Also reverts the earlier CENTERED uiLayout force (2b43007): the engine box
now tracks at every aspect via the Font.drawBox hook, so forcing uiLayout
was redundant and only overrode the player's layout preference.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
OG is the original gen1recomp scale (window-fit, what the engine's player
HUD uses and external XP-bar mods target); SCALED is this mod's new
zoom-dependent scale. Renamed the option value/label and updated the
options-row description and code comment accordingly.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The Font.drawBox hook bypassed the battle box fill, which the engine draws
through BattleState:drawTextArea as a g.rectangle("fill") (intercepted by the
withoutBoxFill/whiteBoxFill shims). So HALF did nothing.

Replace the dead hook with a halfBoxFill shim -- recolours the engine's opaque
white box fill to a translucent black slab -- routed through drawTextArea
alongside the existing WHITE path, so it tracks the box at every aspect. OFF
keeps the invisible backplate (withoutBoxFill drops the white fill); WHITE
arena still forces opaque white.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
…eecam

- HALF backplate: the translucent-black fill was being applied to every
  white fill during drawTextArea, washing the move-select / type / action
  boxes. Scope halfBoxFill to the dialogue box's own GB rect {0,96,160,48}
  so only the dialogue paper is recoloured; the other boxes keep their
  colours.

- Battle camera: reset() only seeded from the freecam when the battle camera
  was at its defaults, so a steered (or never-seeded) camera opened on the
  zoomed-in solved shot instead of the saved freecam position. Now reset()
  always seeds from the last-known freecam attitude when we have one, so
  battles open where the player last looked (the saved drift). A steered
  battle camera is only kept when there is no freecam history.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The HALF backplate applied flipGlyphs(white ink) to the WHOLE drawTextArea
call, which also draws the move-select / type / mimic menus. Flipping those
menus' black glyphs to white left black text on white paper -- white-on-white,
the regression reported.

Scope the dark slab + white-flip to the dialogue box (phase "messages") only;
the menus fall through to the normal withoutBoxFill (black glyphs on the
diorama), their original behaviour. The rule: white backplate -> black glyphs,
dark backplate -> white glyphs. Updated the stale UiBackplates comment too.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The HALF/OFF toggle produced inconsistent boxes (grey dialogue, transparent
menus) because the g.rectangle shim flipped glyph ink across the whole
drawTextArea call. Replace it with the one predictable backplate: every
battle box -- dialogue, action-select and type panels -- is drawn as an
opaque white panel with BLACK ink (the same as the overworld / trainer-intro
textbox), via the proven whiteBoxFill + flipGlyphs(true) path.

Removed:
- TEXTBOX FILL ModSetting (HALF/OFF) and its OPTIONS row
- halfBoxFill / withoutBoxFill shims and the textboxFillStyle / isTextboxKey
  queries and the dead snapHUDs slab

The box colour now matches the overworld textbox at every window aspect.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The always-white box wrapper referenced `battle` before it was defined,
passing nil to whiteBoxFill -> innerText(nil) -> engine drawTextArea
indexed a nil `self` (BattleState.lua:5557). Define `local battle = self`
and forward `innerText(self)`.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
On iOS the snapped HUD path was skipped (it drew upside-down + opaque),
leaving semi-transparent (0.84) in-frame panels. Port the upstream PR #119
correction for our fork:

- snapHUDs now runs on iOS: the player/opponent HUDs get OPAQUE WHITE
  backplates (BattleHud.panel is frosted-only and no-ops at FROST/TINT 0,
  so a plain white fill is the right primitive) and the HUD layer is blitted
  with the vertical-flip that corrects iOS's upside-down Canvas-to-Canvas
  presentation. The text box's backing is skipped on iOS (it would ghost
  upward), so the box stays as the engine's own opaque-white panel.
- HUD glyphs on iOS stay BLACK (dark=false) to read on white; the WHITE
  arena fill still inverts to black ink + white drop-shadow.
- drawHudPanels iOS fallback draws opaque white (1,1,1,1) instead of 0.84.
- The dialogue/action/move/type boxes keep the engine's native opaque-white
  panel with black glyphs on iOS (drawTextArea returns early there).

Desktop (Windows/Android) is unchanged.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
Aligns the runtime version string with manifest.json (1.7.2) for the 1.7.2
release tag.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
… white

The iOS HUD path drew a plain opaque-white g.rectangle over the full
snapped HUD band (HUD_RECT), which covered the whole top and bottom-right
of the screen -- including empty diorama where no UI belongs -- producing
the white cards reported by iOS users. Remove those fills: the HUD glyphs
now blit straight onto the diorama (transparent, matching upstream PR #119).

Text boxes (dialogue, action-select, move-list, type, yes/no, move-learn)
stay opaque white because they are the engine's own Font.drawBox, which
drawTextArea preserves on iOS. HUD glyphs keep the engine's native white
ink (dark = true) to read over the scene.

Desktop (Windows/Android) is unchanged.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
- manifest.json version 1.7.2 -> 1.7.3
- main.lua mod.exports.version 1.7.2 -> 1.7.3
Ships the iOS player/opponent HUD transparency fix (23e97f0).

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
…ored)

The iOS HUD blit used y = shot.ph - placement.y - band[4]*scale, which
mirrored each HUD band about the screen's vertical center, dropping the
opponent/player HUD down into the diorama and under the text box. Flip the
upside-down Canvas-to-Canvas texture IN PLACE instead (y = placement.y +
band[4]*placement.scale) so each band stays in its correct screen rectangle
with upright glyphs -- matching the desktop anchor.

Bump to 1.7.4.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
The Canvas-to-Canvas HUD blit is presented upside-down on iOS, and no single
flip formula places both bands correctly (enemy [0,48] and player [48,96]
are not symmetric about the 144px canvas center). 1.7.3 center-mirrored;
1.7.4 in-place fixed the player band but dropped the enemy to the bottom.
Revert to the engine's own in-frame HUD on iOS, which the engine positions
correctly. drawHudPanels still elides the backplate there, so the HUD stays
transparent (no white cards). Text boxes remain opaque white (engine
Font.drawBox). Desktop unchanged.

Bump to 1.7.5.

Co-Authored-By: Hermes Agent <noreply@hermes-agent.com>
…1 sprites

- OverworldBattle.BattleState:picImage now returns the sprite early for both
  external and ROM images, skipping BattlePics.filled (the opaque-white
  {1,1,1,1} filler). Transparent gaps in ROM sprites now show through instead
  of being painted white. Mode-agnostic (static + animated, species + player).

- Per-slot SHINY options "FRONT SHINY FIX" / "BACK SHINY FIX" (species only;
  players can never be shiny). When ON, the resolver prefers the shiny/ folder
  and, on a missing shiny file, falls back to ROM -- never to the selected
  generation's normal art and never to player.png. Animated gens (front 2-5,
  back 3/5) are forced through the single-image shinyPrefix path so their
  atlases are suppressed and do not play over a shiny mod's sprite.

- gen1 compatibility sets: back-static/gen1 (Yellow GBC backs) and
  front-animated/gen1 (Yellow GBC fronts), 151 each, with provenance READMEs.

- Empty shiny/ folders (front/back x static/animated) ship with READMEs so
  shiny mods have a documented drop target.

- Ported upstream PRs (lib hunks only, tests/VR stripped): #125 flat-top
  meshing fix, #75 LÖVE 12 ShadowMap z-clip, #79 1ST label rename.

Co-Authored-By: Hermes Agent <noreply@nousresearch.com>
Added checks courtesy https://github.com/FelizNavidad-D/DramaticShapeVoxelMod so Modern UI can take control of Battle UI rendering.
@ArmstrongThomas ArmstrongThomas changed the title Modern UI Compatibility Patch Expose a generic battle presentation compatibility hook Aug 9, 2026
@absol89

absol89 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

@ArmstrongThomas So you tried merging into master instead of into feature/battle-art which makes this hard to read.

I also don't know which version you started working from, if it was 1.7.6 or 1.68 based etc. Can you redo the PR target?

And also include screenshots of how everything looks with and without sprites found, for the following modes options:
Arena fill: WHITE and OFF
Sprite light: Shaded and Unlit
Shiny fix: ON or OFF, front and back
gen5 with an without shiny fix ON/OFF, with and without another sprite mod handling shiny sprites like Crystal shiny mod

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants