local artwork_defs={
  {path="mechagirl.pod",   pal_path="mechagirl.pod.pal"},
  {path="cybergorilla.pod",pal_path="cybergorilla.pod.pal"},
}

artworks = {
  cur=0,
  prev_img_sel=0,
  
  _reveal=0.0      -- 0=hidden, 1=fully shown

}

-------------------------
-- rocket param layout (params[2..12])
-------------------------
-- [2]  image       (1-based index; 0 or same = no change)
-- [3]  reveal      (0=hidden, 1=fully revealed; drives tile reveal)
-- [4]  ripple_amp  (v-ripple amplitude in pixels, 0=off)
-- [5]  glitch      (line-glitch intensity 0=off, 1=normal, higher=more)
-- [6]  shake       (screenshake intensity in pixels, 0=off)
-- [7]  contrast    (1=normal, >1=boost, 0=none)
-- [8]  fade        (0=black, 1=normal, 2=white)

local images={}
local w,h=240,135
local sqrt,flr,min,max,sin,cos,abs=sqrt,flr,min,max,sin,cos,abs
local atan2=atan2

-- transition
local fx_id=0
local fx_dur=90
local fx_dither_edge=0.09

-- rocket-controlled state
local _ripple_amp=0    -- v-ripple amplitude (pixels, 0=off)
local _glitch=0        -- line-glitch intensity (0=off)
local _shake=0         -- screenshake (pixels, 0=off)
local _contrast=1.0    -- contrast amount (1=normal)
local _fade=1.0        -- fade (0=black, 1=normal, 2=white)

-- v-ripple state
local vripple_t=0
local vripple_freq=0.02
local vripple_speed=0.9
local linebuf

-- line glitch config
local glitch_bands_min=3
local glitch_bands_max=8
local glitch_max_height=2
local glitch_max_shift=8

-- contrast/fade palette
local base_pal_colors={}
local _pal_dirty=false

-- RNG for shake/glitch
local _rng=1
local function irnd(n)
  _rng = (_rng * 1103515245 + 12345) & 0x7fffffff
  return _rng % n
end

-- tile reveal
local TILE=2
local tile_cols,tile_rows,tile_total
local reveal_map={}
local init_pending= false

-------------------------------------------------
-- helpers
-------------------------------------------------
local function is_u8_image(obj)
  if not obj then return false end
  local ok=pcall(function() return obj:width() end)
  return ok
end

local function find_bmp(obj)
  if is_u8_image(obj) then return obj end
  if type(obj)~="table" then return nil end
  if is_u8_image(obj.bmp) then return obj.bmp end
  if is_u8_image(obj.image) then return obj.image end
  if is_u8_image(obj.img) then return obj.img end
  return nil
end

local function load_image(path)
  local obj=fetch(path)
  local bmp=unpod(obj)
  if not bmp then bmp=find_bmp(obj) end
  return bmp
end

local function apply_palette_fx()
  -- apply contrast + fade to base palette using rocket-controlled values
  for i=0,63 do
    local v=base_pal_colors[i]
    if v then
      local r,g,b=v[1],v[2],v[3]
      -- contrast
      if _contrast ~= 1.0 then
        local c=_contrast
        r= (r-128)*c+128
        g= (g-128)*c+128
        b= (b-128)*c+128
      end
      -- fade: 0=black, 1=normal, 2=white
      if _fade<1 then
        local f=_fade
        r=r*f  g=g*f  b=b*f
      elseif _fade>1 then
        local f=_fade-1
        r=r+(255-r)*f  g=g+(255-g)*f  b=b+(255-b)*f
      end
      r=flr(max(0,min(255, r+0.5)))
      g=flr(max(0,min(255, g+0.5)))
      b=flr(max(0,min(255, b+0.5)))
      local packed=(0xff<<24)|(r<<16)|(g<<8)|b
      pal(i, packed, 2)
    end
  end
end

local function apply_pod_palette(obj)
  if type(obj)=="table" then obj=table.concat(obj,"\n") end
  if type(obj)~="string" then return end
  local colors={}
  local i=0
  for token in obj:gmatch("0x([0-9a-fA-F]+)") do
    local v=tonumber(token,16)
    if v then colors[i]=v; i+=1 end
  end
  -- store base palette for contrast manipulation
  base_pal_colors={}
  for i=0,63 do
    local v=colors[i]
    if v then
      local r=(v>>16)&0xff
      local g=(v>>8)&0xff
      local b=v&0xff
      base_pal_colors[i]={r,g,b}
--      local packed=(0xff<<24)|(r<<16)|(g<<8)|b
--      for bank=0,3 do
--        poke4(PAL0+bank*0x100+i*4, packed)
--      end
    end
  end
--  memset(0x5400,0,120)
--  apply_palette_fx()
end

-------------------------------------------------
-- reveal map builders
-------------------------------------------------

-- normalize raw values in-place to 0..1
local function normalize_map(raw)
  local vmin,vmax=raw[0],raw[0]
  for i=1,tile_total-1 do
    local v=raw[i]
    if v<vmin then vmin=v end
    if v>vmax then vmax=v end
  end
  local range=vmax-vmin
  if range<0.001 then range=1 end
  for i=0,tile_total-1 do
    reveal_map[i]=(raw[i]-vmin)/range
  end
end

-- plasma bubble: overlapping sine waves create organic shapes
local function build_plasma()
  local s1,s2,s3,s4=rnd(1),rnd(1),rnd(1),rnd(1)
  local raw={}
  for i=0,tile_total-1 do
    local nx=(i%tile_cols+0.5)/tile_cols
    local ny=(flr(i/tile_cols)+0.5)/tile_rows
    local v=sin(nx*3.7+s1)
         + sin(ny*2.3+s2)
         + sin((nx+ny)*2.1+s3)
         + sin(sqrt((nx-0.5)^2+(ny-0.5)^2)*5+s4)
    raw[i]=v
  end
  normalize_map(raw)
end

-- multi-arm spiral: spinning arms sweep outward from center
local function build_spiral()
  local arms=3+flr(rnd(3))  -- 3-5 arms
  local twist=2+rnd(2)
  local raw={}
  for i=0,tile_total-1 do
    local nx=(i%tile_cols+0.5)/tile_cols
    local ny=(flr(i/tile_cols)+0.5)/tile_rows
    local dx,dy=nx-0.5,ny-0.5
    local dist=sqrt(dx*dx+dy*dy)
    local ang=atan2(dx,dy)  -- 0..1
    -- spiral value: angle arms + distance twist
    local v=(ang*arms+dist*twist)%1
    -- weight toward center revealing first
    v=v*0.5+dist*0.5
    raw[i]=v
  end
  normalize_map(raw)
end

-------------------------------------------------
-- start transition
-------------------------------------------------
artworks.show= function(this, idx)
  if idx < 1 or idx > #artwork_defs then return end
  this.cur=idx
--  apply_pod_palette(artwork_defs[this.cur].pal_path)
  -- mechagirl(1)=spiral/twirl, cybergorilla(2)=plasma
  if idx == 1 then build_spiral() else build_plasma() end
  apply_pod_palette(artwork_defs[this.cur].pal)
end

-------------------------------------------------
-- draw
-------------------------------------------------
local function draw_artwork(bmp)
  local bw,bh=bmp:width(),bmp:height()
  spr(bmp, flr((w-bw)*0.5), flr((h-bh)*0.5))
end

-- 2x2 ordered Bayer matrix thresholds (0.25, 0.50, 0.75)
local bayer={[0]={[0]=0.75,0.25},{[0]=0.00,0.50}}

local function draw_reveal(bmp, prog)
  -- smoothstep easing
  local t=prog*prog*(3-2*prog)

  draw_artwork(bmp)

  -- 3-band dither edge width
  local edge=fx_dither_edge
  local band=edge/3  -- each sub-band ~0.03
  local tc=tile_cols
  local T=TILE
  local l_pset=pset
  local l_rectfill=rectfill
  for i=0,tile_total-1 do
    local rv=reveal_map[i]
    if rv>t then
      local tx=(i%tc)*T
      local ty=flr(i/tc)*T
      local d=rv-t
      if d>=edge then
        -- fully hidden
        l_rectfill(tx,ty, tx+T-1,ty+T-1, 0)
      else
        -- dither band: 75% -> 50% -> 25% black
        local density=d/edge  -- 0=almost revealed, 1=almost solid
        local x1=min(tx+T-1,w-1)
        local y1=min(ty+T-1,h-1)
        for py=ty,y1 do
          local by=bayer[py%2]
          for px=tx,x1 do
            if density>by[px%2] then
              l_pset(px,py,0)
            end
          end
        end
      end
    end
  end
end

-------------------------------------------------
-- rocket-ready methods (colon-call convention)
-------------------------------------------------
function artworks.load(_self)
  w,h=240,135
  tile_cols=flr((w+TILE-1)/TILE)
  tile_rows=flr((h+TILE-1)/TILE)
  tile_total=tile_cols*tile_rows

  for i,a in ipairs(artwork_defs) do
    images[i]=load_image(a.path)
    a.pal= fetch(a.pal_path)
  end
  _self:show(1)
end

function artworks.init(_self)

--  init_pending= true

  _self.cur=1
  _self.prev_img_sel=0
  vripple_t=0
  _self._reveal=0.0
  _ripple_amp=0
  _glitch=0
  _shake=0
  _contrast=1.0
  _fade=0.0
  _pal_dirty=false
  _rng=1
  fx_id=1
--  show(1)

--  init_pending= false
end

function artworks.setparams(_self, params)
  -- image selection (1-based; change triggers new reveal map)
  local img_sel = flr(params[2])

  -- reveal progress: 0=hidden, 1=fully visible
  _self._reveal    = params[3]
  -- v-ripple amplitude (pixels, 0=off)
  _ripple_amp = params[4]
  -- line-glitch intensity (0=off, 1=normal)
  _glitch    = params[5]
  -- screenshake intensity (pixels, 0=off)
  _shake     = params[6]
  -- contrast (1=normal, >1=boost)
  _contrast  = params[7]
  if _contrast == 0 then _contrast = 1.0 end
  -- fade (0=black, 1=normal, 2=white)
  _fade      = params[8]

  if img_sel >= 1 and img_sel <= #artwork_defs and img_sel ~= _self.prev_img_sel then
    _self:show(img_sel)
    _self.prev_img_sel = img_sel
  end
end

function artworks.update(_self)
  -- advance ripple time when active
  if _ripple_amp > 0 then vripple_t += 1 end

  -- apply palette effects (contrast + fade) every frame
--  if (init_pending == true) return
  apply_palette_fx()
end

function artworks.draw(_self)
--  if (init_pending == true) return

  -- screenshake
  local shk = _shake
  if shk > 0 then
    local si = flr(shk + 0.5)
    local sx = irnd(si * 2 + 1) - si
    local sy = irnd(si * 2 + 1) - si
    camera(-sx, -sy)
  else
    camera(0, 0)
  end

  local bmp = images[_self.cur]
  if not bmp then cls(0) return end

  -- when v-ripple is on, draw into offscreen buffer
  local do_ripple = _ripple_amp > 0
  if do_ripple then
    if not linebuf then linebuf = userdata("u8", w, h) end
    set_draw_target(linebuf)
  end

  cls(0)

  -- reveal: 0=hidden, 1=fully visible
  if _self._reveal >= 1.0 then
    draw_artwork(bmp)
  elseif _self._reveal > 0 then
    draw_reveal(bmp, _self._reveal)
  end
  -- reveal=0 → screen stays black (cls already done)

  -- v-ripple: blit rows with horizontal sine displacement
  if do_ripple then
    set_draw_target()
    cls(0)
    local t = vripple_t * 0.03
    local l_sspr, l_sin, l_flr = sspr, sin, flr
    local amp, freq, spd = _ripple_amp, vripple_freq, vripple_speed
    for y = 0, h - 1 do
      local dx = l_flr(l_sin(t * spd + y * freq) * amp + 0.5)
      l_sspr(linebuf, 0, y, w, 1, dx, y, w, 1)
    end
  end

  -- line glitch
  if _glitch > 0 then
    local scale = _glitch
    local nb = glitch_bands_min + irnd(glitch_bands_max - glitch_bands_min + 1)
    local ms = flr(glitch_max_shift * scale + 0.5)
    if ms < 1 then ms = 1 end
    for g = 1, nb do
      local y0 = irnd(h)
      local bh = 1 + irnd(glitch_max_height)
      local shift = irnd(ms * 2 + 1) - ms
      local y1 = min(y0 + bh - 1, h - 1)
      for row = y0, y1 do
        if shift > 0 then
          for px = w - 1, 0, -1 do
            local sx = px - shift
            if sx >= 0 and sx < w then
              pset(px, row, pget(sx, row))
            else
              pset(px, row, 0)
            end
          end
        elseif shift < 0 then
          for px = 0, w - 1 do
            local sx = px - shift
            if sx >= 0 and sx < w then
              pset(px, row, pget(sx, row))
            else
              pset(px, row, 0)
            end
          end
        end
      end
    end
  end

  -- reset shake camera
  if shk > 0 then camera(0, 0) end
end
