DramaticShapeVoxelMod archival mirror · read-only
tests/voxel_fix_shots.lua

voxel_fix_shots.lua

tests/voxel_fix_shots.lua5.1 KB100644ab408cbe39
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
-- Scratch driver: one shot of each fixture the wall-top / statue pass
-- touched, so the change can be looked at rather than reasoned about.
--
--   POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/voxel_fix_shots.lua \
--   SHOT_DIR=mods/DramaticShapeVoxelMod/.claude/voxel_fix AB_TAG=. lovec.exe .
--
-- SHOT_DIR is relative to the PROJECT ROOT, which is where lovec runs from --
-- the mod's own .claude/ is where these belong, so it has to be spelt out.
--
-- Every scene stands the player on the nearest WALKABLE cell to the vantage
-- named below (teleporting into a wall puts the close cameras inside the
-- geometry), faces the fixture, and shoots at voxel rung 3 -- the higher
-- top-down camera, which is the one that shows what a wall wears on TOP.
return function(game)
  local U = dofile("tests/drivers/util.lua")
  local Pipelines = require("src.render.Pipelines")

  local ROOT = (os.getenv("SHOT_DIR")
                or "mods/DramaticShapeVoxelMod/.claude/voxel_fix")
    .. "/" .. (os.getenv("AB_TAG") or ".")

  local handle = game.mods.exports["DRAMATIC_SHAPE"]
  if not (handle and handle.lib) then
    print("[voxfix] DRAMATIC_SHAPE mod not loaded")
    return
  end
  local V = handle.lib
  local DayNight = V.require("DayNight")
  local ChunkMesher = V.require("ChunkMesher")
  local Voxel = V.require("VoxelState")
  local TileShape = V.require("TileShape")

  -- prove the game is reading THIS tree, not a stale installed copy
  local ht = TileShape.wallTop and TileShape.wallTop("HOUSE")
  print(("[voxfix] wallTop present=%s HOUSE(45)=%s")
        :format(tostring(TileShape.wallTop ~= nil),
                tostring(ht and ht(45))))

  require("src.world.OverworldController").rollEncounter = function() return nil end
  local TileRenderer = require("src.render.TileRenderer")
  TileRenderer.tick = function() end
  TileRenderer.animFrame = function() return 0 end
  DayNight.setting:sync("day")

  pcall(os.execute, 'mkdir -p "' .. ROOT .. '" 2>/dev/null')
  pcall(os.execute, 'mkdir "' .. ROOT:gsub("/", "\\") .. '" 2>nul')

  local Zoom = require("src.render.Zoom")
  pcall(function()
    game.save.options.zoom = 1
    Zoom.applyOptions(game.save.options)
  end)

  local function settle()
    for _ = 1, 900 do
      if ChunkMesher.pending() == 0 then break end
      U.wait(1)
    end
    for _ = 1, 300 do
      if Voxel.t >= 1 and Voxel.ready and ChunkMesher.pending() == 0 then break end
      U.wait(1)
    end
    U.wait(40)
  end

  -- the nearest walkable cell to the vantage, searched in rings -- a
  -- vantage picked off a map dump is often the fixture itself
  local function place(x, y, face)
    local m = game.overworld.map
    for r = 0, 8 do
      for dy = -r, r do
        for dx = -r, r do
          if math.max(math.abs(dx), math.abs(dy)) == r then
            local px, py = x + dx, y + dy
            if m:inBounds(px, py) and m:isWalkableCell(px, py) then
              game.overworld.player.cellX = px
              game.overworld.player.cellY = py
              game.overworld.player.facing = face
              return px, py
            end
          end
        end
      end
    end
    return x, y
  end

  local SCENES = {
    { map = "BLUES_HOUSE",             x = 4,  y = 3,  face = "up",
      label = "house_wall_top" },
    { map = "VIRIDIAN_POKECENTER",     x = 4,  y = 3,  face = "up",
      label = "pokecenter_wall_top" },
    { map = "ROCKET_HIDEOUT_ELEVATOR", x = 2,  y = 3,  face = "up",
      label = "rocket_lift_wall_top" },
    { map = "REDS_HOUSE_2F",           x = 6,  y = 3,  face = "up",
      label = "reds_2f_wall_top" },
    { map = "REDS_HOUSE_1F",           x = 5,  y = 3,  face = "up",
      label = "reds_1f_wall_top" },
    { map = "LANCES_ROOM",             x = 6,  y = 15, face = "up",
      label = "lances_room_statues" },
    { map = "INDIGO_PLATEAU",          x = 2,  y = 5,  face = "up",
      label = "indigo_plateau_statue" },
    -- the west-edge statue itself, close enough to count the birds on it
    { map = "INDIGO_PLATEAU",          x = 1,  y = 4,  face = "up",
      label = "indigo_plateau_statue_close", rung = 5 },
    { map = "INDIGO_PLATEAU",          x = 2,  y = 4,  face = "up",
      label = "indigo_plateau_statue_near" },
  }

  local only = os.getenv("VF_ONLY")
  local shots = 0
  for _, s in ipairs(SCENES) do
   if not (only and only ~= "" and not only:find(s.label, 1, true)) then
    local ok, err = pcall(function()
      U.teleport(game, s.map, s.x, s.y, s.face)
      local px, py = place(s.x, s.y, s.face)
      Pipelines.setLevel("voxel", s.rung or 3)
      Pipelines.setLevel("tiltshift", 0)
      settle()
      local path = ("%s/%s.png"):format(ROOT, s.label)
      game.capturePath = path
      U.wait(8)
      local f = io.open(path, "rb")
      if f then
        f:close()
        shots = shots + 1
        print(("[voxfix] %s -> %s (stood %d,%d)"):format(s.map, path, px, py))
      else
        print("[voxfix] capture missed: " .. path)
      end
    end)
    if not ok then print("[voxfix] " .. s.map .. " failed: " .. tostring(err)) end
   end
  end
  print(("[voxfix] %d/%d shots into %s"):format(shots, #SCENES, ROOT))
  love.event.quit()
end