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

fp_shots.lua

tests/fp_shots.lua6.0 KB100644e972bfed5c
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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
-- Scratch driver: shots of the 1ST (first-person) rung -- the rig standing
-- in the player's head, billboards yawing to face it, the sky meeting the
-- horizon, the shadow box following the look, water seen from eye level,
-- and an interior with its figures.
--
--   POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/fp_shots.lua \
--   SHOT_DIR=.scratchpad/fpshots lovec.exe .
return function(game)
  local U = dofile("tests/drivers/util.lua")
  local Pipelines = require("src.render.Pipelines")

  local ROOT = (os.getenv("SHOT_DIR") or "shots/fp")

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

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

  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")

  local Zoom = require("src.render.Zoom")
  pcall(function()
    game.save.options.zoom = 0
    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 FirstPerson.blend >= 1 and Voxel.ready
         and ChunkMesher.pending() == 0 then break end
      U.wait(1)
    end
    U.wait(40)
  end

  -- Teleport to the nearest WALKABLE cell: a guessed coordinate inside a
  -- building footprint buries the eye in the geometry, which is what the
  -- first cut of every Pallet shot did.
  local function place(mapId, x, y)
    U.teleport(game, mapId, x, y, "down")
    local ow = game.stack:top()
    local map = ow and ow.map
    if not map or map:isWalkableCell(x, y) then return end
    for r = 1, 8 do
      for dy = -r, r do
        for dx = -r, r do
          if math.max(math.abs(dx), math.abs(dy)) == r then
            local cx, cy = x + dx, y + dy
            if map:inBounds(cx, cy) and map:isWalkableCell(cx, cy) then
              U.teleport(game, mapId, cx, cy, "down")
              print(("[fp] (%d,%d) not walkable; standing at (%d,%d)")
                    :format(x, y, cx, cy))
              return
            end
          end
        end
      end
    end
  end

  -- yaw is a world bearing: 0 south, pi/2 east, pi north, -pi/2 west
  local SCENES = {
    -- Pallet Town, mid-street: houses, the lab, NPCs -- the town seen
    -- from inside it, in each compass direction plus a diagonal
    { map = "PALLET_TOWN", x = 13, y = 14, yaw = math.pi, label = "pallet_north" },
    { map = "PALLET_TOWN", x = 13, y = 14, yaw = 0, label = "pallet_south" },
    { map = "PALLET_TOWN", x = 9, y = 7, yaw = math.pi / 2, label = "pallet_east" },
    { map = "PALLET_TOWN", x = 9, y = 7, yaw = 3 * math.pi / 4,
      label = "pallet_diag" },
    -- the shoreline: water at eye level, which is where the battle pass
    -- says a low placed camera reads the reflection wrong -- the shot
    -- decides whether 1ST keeps it
    { map = "PALLET_TOWN", x = 9, y = 12, yaw = 0, label = "pallet_water" },
    -- looking up: the sky's bands and the horizon line
    { map = "PALLET_TOWN", x = 13, y = 14, yaw = math.pi,
      pitch = -math.rad(25), label = "pallet_skyward" },
    -- and down: the ground, the feet-level shadow
    { map = "PALLET_TOWN", x = 13, y = 14, yaw = math.pi,
      pitch = math.rad(45), label = "pallet_down" },
    -- Route 1: grass rows and ledges from inside them
    { map = "ROUTE_1", x = 10, y = 28, yaw = math.pi, label = "route1_north" },
    -- an interior: the Center's counter, machines and couch figures
    { map = "VIRIDIAN_POKECENTER", x = 3, y = 5, yaw = math.pi,
      label = "center_north" },
    { map = "VIRIDIAN_POKECENTER", x = 6, y = 4, yaw = -math.pi / 2,
      label = "center_west" },
  }

  local shots = 0
  for _, s in ipairs(SCENES) do
    place(s.map, s.x, s.y)
    Pipelines.setLevel("voxel", Voxel.FP_LEVEL)
    Pipelines.setLevel("tiltshift", 0)
    settle()
    FirstPerson.yaw = s.yaw
    FirstPerson.pitch = s.pitch or FirstPerson.PITCH_DEFAULT
    U.wait(20)
    if U.shot(game, ("%s/%s.png"):format(ROOT, s.label)) then
      shots = shots + 1
    end
  end

  -- one mid-blend shot: step the ladder onto 1ST from 75 and catch the
  -- dive halfway
  place("PALLET_TOWN", 13, 14)
  Pipelines.setLevel("voxel", 5)
  settle()
  Pipelines.setLevel("voxel", Voxel.FP_LEVEL)
  for _ = 1, 300 do
    if FirstPerson.blend >= 0.5 then break end
    U.wait(1)
  end
  if U.shot(game, ROOT .. "/blend_mid.png") then shots = shots + 1 end

  -- ------- the free walk, exercised
  --
  -- Hold forward with the head yawed off-grid and confirm the player
  -- GLIDES: the position moves along the look direction, lands off the
  -- 16px grid (which no grid step can do), and the logical cell follows.
  place("PALLET_TOWN", 13, 14)
  Pipelines.setLevel("voxel", Voxel.FP_LEVEL)
  settle()
  local ow = game.stack:top()
  local p = ow.player
  FirstPerson.yaw = 3 * math.pi / 4       -- northeast, deliberately off-grid
  FirstPerson.pitch = FirstPerson.PITCH_DEFAULT
  local x0, y0, c0x, c0y = p.px, p.py, p.cellX, p.cellY
  U.hold(game, "up", 90)
  U.wait(5)
  local moved = math.abs(p.px - x0) + math.abs(p.py - y0)
  print(("[fp] walk: (%.1f,%.1f) cell(%d,%d) -> (%.1f,%.1f) cell(%d,%d)")
        :format(x0, y0, c0x, c0y, p.px, p.py, p.cellX, p.cellY))
  print(("[fp] walk moved %.1f px; off-grid: %s; diagonal: %s")
        :format(moved,
                tostring(p.px % 16 ~= 0 or p.py % 16 ~= 0),
                tostring(math.abs(p.px - x0) > 8
                         and math.abs(p.py - y0) > 8)))
  if U.shot(game, ROOT .. "/walked.png") then shots = shots + 1 end

  print(("[fp] %d shots into %s"):format(shots, ROOT))
  love.event.quit()
end