23 Commits

Author SHA1 Message Date
67970a5164 add draw/update stat readout
surprisingly, update is most of my problem
2025-06-21 17:50:03 -07:00
eaea42f993 fix shot axis 2025-06-21 17:38:05 -07:00
929f47fc78 bullet microoptimization and velocity fix 2025-06-21 17:36:27 -07:00
430a0a4b14 ship_m:move micro-optimizations 2025-06-21 17:17:44 -07:00
e4062d3ccd back out of fast bullet changes, keep optimizations
in the "nothing but turrets" worst-case scenario, fast bullet logic costs 133% of slow bullet logic even when almost all shots on screen are slow. when I back out of this, that scenario is _still_ over 300% CPU but at least it's not 400%. note that this is with a screen mostly full of enemies, so processing all of them and their potential collisions also has cost, so the actual bullet-specific change is closer to 150%, maybe 200%. this is genuinely not as bad as I had thought but it doesn't feel like it will be workable; while my worst-case scenario is implausibly bad it's not actually 3x-likely-peak bad. so I'm going to need to find more optimizations, and probably give up on fast bullets. But I can keep the fast bullet branch around in case I find the headroom to reintroduce it later.
2025-06-21 17:05:36 -07:00
c9d7437ffe trim up draw costs a bit 2025-06-21 16:40:23 -07:00
d0de757b0e test the worst case scenario for shots
way too slow
2025-06-21 16:36:36 -07:00
a8b5b9dbe6 fast shot rendering prototype
it's slow *and* it sucks, and making it not suck will make it much slower. this is bad
2025-06-21 16:07:23 -07:00
2596f8aa6c I can't spell 2025-06-21 15:13:07 -07:00
ef40c245f8 multi-step shot prototype
nothing reaches this new logic yet, and multiple steps aren't drawn
2025-06-21 15:12:40 -07:00
6d6e13cf3b special case strip(call_move) to stripmove()
this gets called so much the extra function overhead actually seems bad
2025-06-21 14:55:20 -07:00
99323be298 despawn shots aggressively when offscreen
avoids hitting enemies before they spawn in.

may need to revisit this for the X coordinate if I want to implement a "Wild Ball" kind of weapon, but I think I won't have the tokens for that anyway
2025-06-20 19:13:28 -07:00
85c5091804 pbullets also move just before the collision check.
This also sets up for the "fast shots" refactor.
2025-06-20 19:08:58 -07:00
a77180d89a bullet_base:die doesn't actually make sense 2025-06-20 18:55:48 -07:00
c01c3400b7 fix eternal horizontal bullets 2025-06-20 18:51:58 -07:00
0f791b193c more efficient collision iteration, fix eternal shots 2025-06-20 18:50:04 -07:00
d3351d9a05 Use eship_collider for ship collisions, too. 2025-06-20 17:56:57 -07:00
ecddb56d72 loops work better when you increment them 2025-06-20 17:50:03 -07:00
723c0f791c Refactor collider to collaborate with linked_list.
The only use of a collider is intertwined with the ship list, so I can combine the "prepare to yoink" and "populate collider" and "iterate list" steps, and I can combine the "hide" and "yoink" steps. This doesn't save tokens now but it's about to, when I use the eship collider to test pship collision.
2025-06-20 17:48:02 -07:00
e018578754 keep animating bullets while dead 2025-06-20 16:36:18 -07:00
bf8297eb72 prevify eships when setting up collider
I will refactor this next: collider.new will take the linked list to ingest, perform the `insert` and "prevify" loops itself, then replace `hide` with `yoink`, which yoinks the item out of the original list. This pairs the `yoink` operation with the context that makes it possible to do (that is, the context when prevification was implemented); eships therefore cannot be edited in complex ways while the collider is still valid, but we can append to it as long as we don't expect those items to be procesesd correctly this frame. a new_eships+vore plan might be better if we turn out to need it, but presently we don't; flotilla spawning and raider spawning happen at a different point.
2025-06-20 16:31:18 -07:00
1c8bcae44c put ebullet moves inside the collision check loop 2025-06-20 16:01:59 -07:00
325d7444e7 invert eship/pbullet collision
Also mildly trims up linked_list impl.
2025-06-20 15:09:18 -07:00

View File

@ -60,7 +60,7 @@ function mknew(tt)
end end
-- intrusive singly-linked list. -- intrusive singly-linked list.
-- cannot be nested! -- cannot be nested or crossed!
linked_list = mknew{ linked_list = mknew{
is_linked_list=true, is_linked_list=true,
init = function(x) init = function(x)
@ -93,9 +93,7 @@ end
-- strip calls f(x) for each -- strip calls f(x) for each
-- node, removing each node for -- node, removing each node for
-- which f(x) returns true. it -- which f(x) returns true.
-- returns the new tail; nil
-- if the list is now empty.
function linked_list:strip(f) function linked_list:strip(f)
local p, n = self, self.next local p, n = self, self.next
while n do while n do
@ -107,7 +105,22 @@ function linked_list:strip(f)
n = n.next n = n.next
end end
self.tail = p self.tail = p
return p end
-- stripmove calls x:move() for
-- each node, removing each node
-- for which x:move() is true.
function linked_list:stripmove()
local p, n = self, self.next
while n do
if n:move() then
p.next = n.next
else
p = n
end
n = n.next
end
self.tail = p
end end
-- optimized special case - -- optimized special case -
@ -186,14 +199,7 @@ end
function _update60() function _update60()
mode:update() mode:update()
end ustat = stat(1)
function call_f(x)
return x:f()
end
function call_move(x)
return x:move()
end end
function ones(n) function ones(n)
@ -233,58 +239,43 @@ function updategame()
interlude -= 1 interlude -= 1
else else
current_wave = flotilla.new() current_wave = flotilla.new()
current_wave:load(0, 0, min(ones(waves_complete)\2, 4)) current_wave:load(rnd() > 0.5 and 7 or 0, 0, min(ones(waves_complete)\2, 4))
end end
events:vore(new_events) events:vore(new_events)
events:strip(call_move) for _, lst in ipairs{events, intangibles_bg, eships} do
for _, lst in ipairs{intangibles_bg, eships, pbullets, ebullets} do lst:stripmove()
lst:strip(call_move)
end end
-- eship collider will be used
-- both for pship and pbullets.
local eship_collider = collider.new{from=eships}
if not ps.dead then if not ps.dead then
ps:move() ps:move()
local pbox = hurtbox(ps) local pbox = hurtbox(ps)
eships:strip(function(es) for es in eship_collider:iterate_collisions(pbox) do
if(not collides(pbox, hurtbox(es))) return
ps:hitship(es) ps:hitship(es)
return es:hitship(ps) if(es:hitship(ps)) eship_collider:yoink(es)
end) end
ebullets:strip(function(eb) ebullets:strip(function(eb)
if (eb:move()) return true
if (not collides(pbox, hurtbox(eb))) return if (not collides(pbox, hurtbox(eb))) return
ps:hitbullet(eb) ps:hitbullet(eb)
return eb:hitship(ps) return eb:hitship(ps)
end) end)
else
ebullets:stripmove()
end end
-- many bullets and many enemy ships; pbullets:strip(function(pb)
-- use bucket collider for efficiency if (pb:move()) return true
local pbullet_collider = collider.new() for es in eship_collider:iterate_collisions(hurtbox(pb)) do
local p, n = pbullets, pbullets.next if (es:hitbullet(pb)) eship_collider:yoink(es)
while n do if (pb:hitship(es)) return true
n.prev = p
pbullet_collider:insert(n)
p = n
n = p.next
end
eships:strip(
function(es)
for pb in all(pbullet_collider:get_collisions(es)) do
if pb:hitship(es) then
pbullet_collider:hide(pb)
pb.prev.next = pb.next
if pb.next then
pb.next.prev = pb.prev
else
pbullets.tail = pb.prev
end
end
if (es:hitbullet(pb)) return true
end
end end
) end)
intangibles_fg:strip(call_move) intangibles_fg:stripmove()
if waves_complete == 32767 and not eships.next and not ebullets.next and not events.next then if waves_complete == 32767 and not eships.next and not ebullets.next and not events.next then
game_state = win game_state = win
@ -305,6 +296,8 @@ end
function _draw() function _draw()
mode:draw() mode:draw()
local ds = stat(1)
print(tostr(ustat).." + "..tostr(ds-ustat), 0, 122, 7)
end end
function drawgame_top() function drawgame_top()
@ -595,17 +588,22 @@ end
function ship_m:move() function ship_m:move()
self:refresh_shield() self:refresh_shield()
local dx, dy, shoot_spec1, shoot_spec2 = self:act() local dx, dy, shoot_spec1, shoot_spec2 = self:act()
dx = self:constrain(self.x, self.xmomentum, self.xmin, self.xmax, dx) local sg, xm, ym = self.special_guns, self.xmomentum, self.ymomentum
dy = self:constrain(self.y, self.ymomentum, self.ymin, self.ymax, dy) dx = self:constrain(self.x, xm, self.xmin, self.xmax, dx)
dy = self:constrain(self.y, ym, self.ymin, self.ymax, dy)
self:maybe_shoot(self.main_gun) self:maybe_shoot(self.main_gun)
if (shoot_spec1 and self.special_guns) self:maybe_shoot(self.special_guns[1]) if sg then
if (shoot_spec2 and self.special_guns) self:maybe_shoot(self.special_guns[2]) if (shoot_spec1) self:maybe_shoot(sg[1])
if (shoot_spec2) self:maybe_shoot(sg[2])
end
spark(self.sparks, self.x + 4*self.size, self.y + 4*self.size, dx*2.5, dy*2.5, self.sparkodds) spark(self.sparks, self.x + 4*self.size, self.y + 4*self.size, dx*2.5, dy*2.5, self.sparkodds)
self.xmomentum = self:calc_velocity(self.xmomentum, dx) xm = self:calc_velocity(xm, dx)
self.ymomentum = self:calc_velocity(self.ymomentum, dy) ym = self:calc_velocity(ym, dy)
self.x += self.xmomentum self.x += xm
self.y += self.ymomentum self.y += ym
self.xmomentum = xm
self.ymomentum = ym
return false return false
end end
@ -701,9 +699,7 @@ end
-- default: die, return true. -- default: die, return true.
-- returns whether to delete -- returns whether to delete
-- the bullet -- the bullet
-- die -- on-removal event, bullet_base = mknew{}
-- default no-op
bullet_base = mknew{ }
gun_base = mknew{ gun_base = mknew{
shoot_ready = -32768, shoot_ready = -32768,
@ -826,22 +822,17 @@ remainder:
end end
function bullet_base:hitship(_) function bullet_base:hitship(_)
self:die()
return true return true
end end
function bullet_base:die()
end
function bullet_base:move() function bullet_base:move()
self.x += self.dx local x,y,f = self.x + self.dx, self.y+self.dy,self.f
self.y += self.dy self.x,self.y=x,y
if (self.f) self.f -= 1 if f then
if (self.y > 145) or (self.y < -8 * self.height) or (self.f and self.f < 0) then self.f = f-1
self:die() if (f <= 0) return true
return true
end end
return false return (y> 130) or (y < -(self.height<<3)) or (x > 128) or (x < -(self.width<<3))
end end
function bullet_base:draw() function bullet_base:draw()
@ -883,25 +874,20 @@ function gun_base:actually_shoot(x, y)
self.shot_idx = idx self.shot_idx = idx
shots = shots[idx] shots = shots[idx]
for s in all(shots) do for s in all(shots) do
local a,xo,v = unpack(s) local a,xo,v = s[1]+aim, s[2] or 0, s[3] or veloc
v = v or veloc
xo = xo or 0
-- reverse x-offset for negative base angle -- reverse x-offset for negative base angle
if (aim < 0) xo = -xo if (aim < 0) xo = -xo
a += aim
-- todo: switch munition -- todo: switch munition
-- depending on angle -- depending on angle
-- (allows for non-round -- (allows for non-round
-- sprites and hitboxes on -- sprites and hitboxes on
-- shots from guns with -- shots from guns with
-- widely varying angles) -- widely varying angles)
local m = munition.new{} local m = munition.new{
-- todo: automatically make dx=cos(a)*v,
-- high velocity shots do dy=sin(a)*v
-- multiple collision checks }
m.dy = sin(a) * veloc m:spawn_at(x+xo, y)
m.dx = cos(a) * veloc
m:spawn_at(x+(xo or 0), y)
end end
end end
@ -909,44 +895,32 @@ end
-->8 -->8
-- bullets and guns -- bullets and guns
zap_e = mknew(bullet_base.new{ zap_p = mknew(bullet_base.new{
--shape --shape
sprite = 9, --index of ammo sprite sprite = 8, --index of ammo sprite
width = 1, --in 8x8 blocks width = 0.25, --in 8x8 blocks
height = 1, height = 1,
hurt = { -- hurtbox - where this ship can be hit hurt = { -- hurtbox - where this ship can be hit
x_off = 0, -- upper left corner x_off = 0, -- upper left corner
y_off = 0, -- relative to sprite y_off = 0, -- relative to sprite
width = 2, width = 2,
height = 8 height = 8,
}, },
x_off = 1, -- how to position by ship x_off = 1, -- how to position by ship
y_off = 8, y_off = 0,
damage = 1, damage = 1,
hitship = const_fxn(true), hitship = const_fxn(true),
category = enemy_blt_cat,
})
zap_p = mknew(zap_e.new{
sprite = 8,
y_off = 0,
category = player_blt_cat, category = player_blt_cat,
}) })
zap_gun_e = mknew(gun_base.new{ zap_gun_p = mknew(gun_base.new{
cooldown = 0x0.0020, -- frames between shots
veloc = 4,
munition = zap_e,
})
zap_gun_p = mknew(zap_gun_e.new{
icon = 19, icon = 19,
munition = zap_p, cooldown = 0x0.0020, -- frames between shots
veloc = 8, veloc = 7,
aim = 0.25, aim = 0.25,
munition = zap_p,
hdr = "mAIN gUN", hdr = "mAIN gUN",
}) })
@ -1078,8 +1052,8 @@ rate: 2/sec
vulcan_e = mknew(bullet_base.new{ vulcan_e = mknew(bullet_base.new{
--shape --shape
sprite = 21, sprite = 21,
width = 1, --in 8x8 blocks width = 0.125, --in 8x8 blocks
height = 1, height = 0.5,
hurt = { -- hurtbox - where this ship can be hit hurt = { -- hurtbox - where this ship can be hit
x_off = 0, -- upper left corner x_off = 0, -- upper left corner
y_off = 0, -- relative to sprite y_off = 0, -- relative to sprite
@ -1339,7 +1313,7 @@ chasey = mknew(ship_m.new{
drag = 0.075, drag = 0.075,
init = function(ship) init = function(ship)
ship.main_gun=ship.main_gun or zap_gun_e.new{} --ship.main_gun=ship.main_gun or zap_gun_e.new{}
end end
}) })
@ -1445,6 +1419,22 @@ end
collider = mknew{ collider = mknew{
init = function(x) init = function(x)
x.suppress = {} x.suppress = {}
local p, n = x.from, x.from.next
while n do
-- insert
for i in all(collider_indexes(hurtbox(n))) do
local a = x[i]
if not a then
a = {}
x[i] = a
end
add(a, n)
end
-- prepare yoink
n.prev = p
p = n
n = n.next
end
end, end,
} }
@ -1458,40 +1448,39 @@ function collider_indexes(box)
return ret return ret
end end
function collider:insert(item) function collider:yoink(item)
-- todo: separate "big items" list?
local bdx = collider_indexes(hurtbox(item))
for i in all(bdx) do
local x = self[i]
if not x then
x = {}
self[i] = x
end
add(x, item)
end
end
function collider:hide(item)
self.suppress[item]=true self.suppress[item]=true
local p,n = item.prev,item.next
p.next = n
if n then
n.prev = p
else
self.from.tail = p
end
end end
function collider:get_collisions(item) function collider:iterate_collisions(box)
local found = { }
local seen = { } local seen = { }
local box = hurtbox(item)
local bucket_ids = collider_indexes(box) local bucket_ids = collider_indexes(box)
for b_idx in all(bucket_ids) do local bii, bidl, bucket, bi, blen = 1, #bucket_ids, false, 1, 0
local bucket = self[b_idx] return function()
if bucket then while bii <= bidl do
for candidate in all(bucket) do if not bucket then
if not (seen[candidate] or self.suppress[candidate]) then bucket,blen = self[bucket_ids[bii]],0
seen[candidate] = true if (bucket) blen=#bucket
if (collides(box, hurtbox(candidate))) add(found, candidate)
end
end end
while bi <= blen do
local candidate = bucket[bi]
bi += 1
if not seen[candidate] then
seen[candidate] = true
if (not self.suppress[candidate] and collides(box, hurtbox(candidate))) return candidate
end
end -- done with this bucket
bi=1
bii += 1
end end
end end -- end of closure def
return found
end end
-->8 -->8
@ -1770,7 +1759,6 @@ function xp_gem:draw()
end end
function xp_gem:move() function xp_gem:move()
if not primary_ship.dead and abs(self.x + 1 - primary_ship.x - primary_ship.hurt.x_off) <= primary_ship.magnet and abs(self.y + 1 - primary_ship.y - primary_ship.hurt.y_off) <= primary_ship.magnet then if not primary_ship.dead and abs(self.x + 1 - primary_ship.x - primary_ship.hurt.x_off) <= primary_ship.magnet and abs(self.y + 1 - primary_ship.y - primary_ship.hurt.y_off) <= primary_ship.magnet then
if (self.x < primary_ship.x + 3) self.x += 1 if (self.x < primary_ship.x + 3) self.x += 1
if (self.x > primary_ship.x + 5) self.x -= 1 if (self.x > primary_ship.x + 5) self.x -= 1
@ -1780,9 +1768,6 @@ function xp_gem:move()
return bullet_base.move(self) return bullet_base.move(self)
end end
-- todo: "magnetic" behavior
-- when near player ship
function xp_gem:hitship(ship) function xp_gem:hitship(ship)
if (ship ~= primary_ship or primary_ship.dead) return false if (ship ~= primary_ship or primary_ship.dead) return false
primary_ship.xp += self.val primary_ship.xp += self.val
@ -2032,8 +2017,8 @@ __gfx__
00077000067c665000000000cdd10cd10b3dd350094dd42000c0000cb3350b35b7000000a8000000e88e2882484e24423ba77ab328a77a820000000000000000 00077000067c665000000000cdd10cd10b3dd350094dd42000c0000cb3350b35b7000000a8000000e88e2882484e24423ba77ab328a77a820000000000000000
00077000067d665000000000cd10cdd100b3350000944200c0000000b350b335b7000000a8000000e88e2882e84e28823ba77ab328a77a820000000000000000 00077000067d665000000000cd10cdd100b3350000944200c0000000b350b335b7000000a8000000e88e2882e84e28823ba77ab328a77a820000000000000000
0070070065666765000000000ddddd100b33355009444220c000000c03333350b7000000a800000008888820048488203bbaabb3288aa8820000000000000000 0070070065666765000000000ddddd100b33355009444220c000000c03333350b7000000a800000008888820048488203bbaabb3288aa8820000000000000000
000000006506506500000000001111000b0b5050090920200c0000c00055550037000000a2000000008882000048420003bbbb30028888200000000000000000 000000006506506500000000001111000b0b5050090920200c0000c000555500b7000000a2000000008882000048420003bbbb30028888200000000000000000
00000000650000650000000000000000000b50000009200000c0cc00000000003b00000082000000000820000008200000333300002222000000000000000000 00000000650000650000000000000000000b50000009200000c0cc0000000000b700000082000000000820000008200000333300002222000000000000000000
0000000000065000000650000003b0000070070080000000700000000bb0000008800000000000000009200000000000cccccccd000650000000000000000000 0000000000065000000650000003b0000070070080000000700000000bb0000008800000000000000009200000000000cccccccd000650000000000000000000
000000000067500000076500000370000005500080000000b0000000b76300008a920000000000009009200200000000c111111d006765000000000000000000 000000000067500000076500000370000005500080000000b0000000b76300008a920000000000009009200200000000c111111d006765000000000000000000
00000000006d6500006d6500000b7000700660079000000030000000b663000089920000000550009994444200000000c111111d006d65000000000000000000 00000000006d6500006d6500000b7000700660079000000030000000b663000089920000000550009994444200000000c111111d006d65000000000000000000
@ -2224,8 +2209,11 @@ __gff__
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000212060a0e01091119000000000000002232363a3e050d151d 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000212060a0e01091119000000000000002232363a3e050d151d
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
__map__ __map__
00006b6b00006f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00006b6b00006f6a6a6a6a6a6a6a6a6a6a6a6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
7b6a00006a7b6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 7b6a00006a7b6e6a6a6a6a6a6a6a6a6a6a6a6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
7778686878776c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 7778686878776c6a6a6a6a6a6a6a6a6a6a6a6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
6767777767676c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 6767777767676c6a6a6a6a6a6a6a6a6a6a6a6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
7877676777787d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 7877676777787d6a6a6a6a6a6a6a6a6a6a6a6d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000006a6a6a6a6a6a6a6a6a6a6a6d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000006a6a6a6a6a6a6a6a6a6a6a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000006a6a6a6a6a6a6a6a6a6a6a7c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000