DramaticShapeVoxelMod archival mirror · read-only
d8d1d7e336d155004e93da68493c15ec7429e7b6

fix white screen flash on shake on versions > 1.3.0

DramaticShape · 2026-07-28 · parent 2e0ae37bcd

fix white screen flash on shake on versions > 1.3.0

Changed files

CHANGELOG.md            | 12 ++++++++++++
 lib/BattleScene.lua     | 16 ++++++++++++----
 lib/OverworldBattle.lua | 41 +++++++++++++++++++++++++++++++++++++++++
 lib/Voxel3D.lua         |  7 +++++--
 main.lua                |  2 +-
 manifest.json           |  2 +-
 6 files changed, 72 insertions(+), 8 deletions(-)

Diff

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f76a85..5be5443 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
 # Changelog
 
+## 1.1.1
+
+### Fixed
+
+- A move that shakes the screen no longer whites out the frame. The zone pass
+  fills each zone with its blank colour before drawing the shifted copy -- the
+  hardware showing empty BG in the strip the shake vacated -- and a shake
+  program alternates offset and no-offset frames, so over the map that read as
+  the whole battle screen, menu box included, flashing white a few times a
+  second. The fill is dropped while a battle is staged on the map; the shake
+  itself still moves the HUD.
+
 ## 1.2.0
 
 ### Added
diff --git a/lib/BattleScene.lua b/lib/BattleScene.lua
index 42b452f..43c6003 100644
--- a/lib/BattleScene.lua
+++ b/lib/BattleScene.lua
@@ -278,10 +278,16 @@ end
 -- yet (the terrain mesh is still building, the driver has no depth support).
 -- nil is not a failure: the caller simply leaves the battle screen as the
 -- engine drew it for that frame.
--- White, for the hit flash. The shader replaces the card's colour outright
--- rather than multiplying it, so this is the sprite's own silhouette turned
--- solid white -- not a lightened picture of itself.
+-- White, for the hit flash, and how far toward it the card goes.
+--
+-- The shader replaces the card's colour rather than multiplying it, so at
+-- full strength this is the sprite turned into a solid white silhouette --
+-- which is what the effect is on a flat GB screen and far too much on a
+-- sprite standing in a lit world. Held well short of 1, the mon's own
+-- shading still reads through the flash: it looks struck rather than
+-- deleted.
 BattleScene.FLASH_COLOR = { 1, 1, 1 }
+BattleScene.FLASH_STRENGTH = 0.5
 
 function BattleScene.render(state, arena, textures, token)
   if not (state and state.map and arena) then return nil end
@@ -369,7 +375,9 @@ function BattleScene.render(state, arena, textures, token)
     -- (see OverworldBattle) and put back HERE, on the two things it was ever
     -- about: the mons themselves go solid white for those frames.
     local flashing = textures and textures.flash
-    if flashing then Voxel3D.flatten(BattleScene.FLASH_COLOR) end
+    if flashing then
+      Voxel3D.flatten(BattleScene.FLASH_COLOR, BattleScene.FLASH_STRENGTH)
+    end
     for _, card in ipairs(monCards(arena, groundY, textures)) do
       Voxel3D.draw(BattleBillboard.mesh(), card.tex, card.model,
                    BattleBillboard.PULL)
diff --git a/lib/OverworldBattle.lua b/lib/OverworldBattle.lua
index 0cf2ab7..f3998f0 100644
--- a/lib/OverworldBattle.lua
+++ b/lib/OverworldBattle.lua
@@ -645,6 +645,47 @@ function OverworldBattle.install()
     if not ok then error(err, 0) end
   end
 
+  -- The engine's flash has a SECOND half, and it is the one that reaches the
+  -- menu. Beside the white rectangle (dropped above) the flash moves are
+  -- driven by a BGP palette fade -- BGP_LIGHT and friends -- which the
+  -- colorized pipeline applies in drawZonePass to the WHOLE background
+  -- canvas. That canvas carries the HUD glyphs and the text box, so a fade
+  -- meant for the two mons washed the menu out with them.
+  --
+  -- The fade is left switched on for the pics, which read it through
+  -- picImage, and switched off for the zone pass alone. So the mons flash
+  -- and the furniture around them does not.
+  --
+  -- The zone pass has a SECOND thing it paints, and this is the one that
+  -- reads as the menu box flashing. A screen shake makes it fill every zone
+  -- with the zone's own color 0 before it draws the offset copy -- the
+  -- hardware showing empty BG in the strip the shake vacated. On a white
+  -- battle field that fill is invisible; over a world it is an opaque white
+  -- sheet across the whole frame, and since a shake program alternates
+  -- offset and no-offset frames (SE_SHAKE_SCREEN steps dx 1, 0, 1, 0...) it
+  -- switches on and off a few times a second. It is dropped: the background
+  -- here is the map, so what the shake vacates should show the map.
+  local innerZone = BattleState.drawZonePass
+  function BattleState:drawZonePass(src, sx, sy)
+    if not self.dramaticShapeShot then return innerZone(self, src, sx, sy) end
+    -- shadow the method on the instance for this call only; putting the
+    -- field back to whatever it was (normally nil) lets the class method be
+    -- found again
+    local had = rawget(self, "activeBgp")
+    self.activeBgp = function() return nil end
+    local g = love.graphics
+    local rectangle = g.rectangle
+    g.rectangle = function(mode, ...)
+      -- the pass draws no other rectangle; the shake still shifts the copy
+      if mode == "fill" then return end
+      return rectangle(mode, ...)
+    end
+    local ok, err = pcall(innerZone, self, src, sx, sy)
+    g.rectangle = rectangle
+    self.activeBgp = had
+    if not ok then error(err, 0) end
+  end
+
   -- Black glyphs on grass are not readable; over a frosted panel measured
   -- dark they are not readable either, so they go white. Mapped rather than
   -- rewritten: the HUD sets pure black for its text and nothing else, and in
diff --git a/lib/Voxel3D.lua b/lib/Voxel3D.lua
index cb4344f..78679b7 100644
--- a/lib/Voxel3D.lua
+++ b/lib/Voxel3D.lua
@@ -544,12 +544,15 @@ end
 -- simply wants to come out one colour, which is what a hit flash on a sprite
 -- is. beginScene resets the uniform every frame, so a pass that forgets to
 -- clear it cannot leak into the next one.
-function Voxel3D.flatten(color)
+-- `amount` is how far toward that colour, 0..1; omitted is all the way.
+-- Anything short of 1 leaves the sprite's own shading showing through, which
+-- is the difference between a hit flash and a white cut-out.
+function Voxel3D.flatten(color, amount)
   if not (active and activeShader) then return end
   local sh = activeShader
   if color then
     pcall(sh.send, sh, "ghostColor", color)
-    pcall(sh.send, sh, "ghost", 1)
+    pcall(sh.send, sh, "ghost", math.max(0, math.min(1, amount or 1)))
   else
     pcall(sh.send, sh, "ghost", 0)
   end
diff --git a/main.lua b/main.lua
index d07292b..faf2595 100644
--- a/main.lua
+++ b/main.lua
@@ -620,7 +620,7 @@ mod.events:on("battle.ended", function()
   OverworldBattle.finish()
 end)
 
-mod.exports.version = "1.2.0"
+mod.exports.version = "1.1.1"
 -- exposed so a companion mod can pin its own tiles' shapes or read the
 -- camera without reaching into this mod's file layout
 mod.exports.lib = V
diff --git a/manifest.json b/manifest.json
index e53591d..f5a7633 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
 {
   "id": "DRAMATIC_SHAPE",
   "name": "Dramatic Shape Voxel Mod",
-  "version": "1.2.0",
+  "version": "1.1.1",
   "api": 2,
   "entry": "main.lua",
   "profile": "content",