-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathprobe_spawn_ceiling.lua
More file actions
168 lines (152 loc) · 5.69 KB
/
Copy pathprobe_spawn_ceiling.lua
File metadata and controls
168 lines (152 loc) · 5.69 KB
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
160
161
162
163
164
165
166
167
168
-- The population ceiling, and the two nil-shaped holes the first cut had.
--
-- v1 keyed on self.activeMapId and returned false whenever a target was 0.
-- On the WATER path both are true -- trySpawnWater derives its own mapId from
-- the overworld and targetWaterCount can be 0 -- so the ceiling never engaged
-- and 791 water spawns went through in one session. It now counts every live
-- entity, with no map key and no zero-target escape.
local pass, fail = 0, 0
local function check(name, got, want)
if got == want then pass = pass + 1
else fail = fail + 1 print((" FAIL %-54s got %s want %s")
:format(name, tostring(got), tostring(want))) end
end
local POPULATION_SLACK, ABSOLUTE_MAX = 2, 18
local function newLogic(land, water)
local self = { entities = {}, targetSpawnCount = land, targetWaterCount = water }
function self:liveEntityCount()
local n = 0
for _ in pairs(self.entities or {}) do n = n + 1 end
return n
end
function self:populationBudget()
local a = tonumber(self.targetSpawnCount) or 0
local b = tonumber(self.targetWaterCount) or 0
local budget = a + b
if budget <= 0 then budget = ABSOLUTE_MAX end
if budget > ABSOLUTE_MAX then budget = ABSOLUTE_MAX end
return budget + POPULATION_SLACK
end
function self:atPopulationCeiling()
return self:liveEntityCount() >= self:populationBudget()
end
function self:fill(n) for i = 1, n do self.entities["e" .. i] = {} end end
return self
end
-- ------- the hole that let 791 through: no target at all
do
local L = newLogic(0, 0)
check("a zero budget falls back, it does not disable", L:populationBudget(),
ABSOLUTE_MAX + POPULATION_SLACK)
L:fill(19)
check("19 live is under the fallback", L:atPopulationCeiling(), false)
L:fill(20)
check("20 live hits it", L:atPopulationCeiling(), true)
end
-- nil targets are the same story
do
local L = newLogic(nil, nil)
check("nil targets still budget", L:populationBudget(), ABSOLUTE_MAX + POPULATION_SLACK)
L:fill(25)
check("and still cap", L:atPopulationCeiling(), true)
end
-- ------- normal operation: the mod's real numbers
do
local L = newLogic(12, 6)
check("budget is target + slack", L:populationBudget(), 20)
L:fill(19)
check("under budget spawns continue", L:atPopulationCeiling(), false)
L:fill(20)
check("at budget they stop", L:atPopulationCeiling(), true)
end
-- ------- an absurd target cannot lift the ceiling
do
local L = newLogic(500, 500)
check("a runaway target is clamped", L:populationBudget(),
ABSOLUTE_MAX + POPULATION_SLACK)
L:fill(21)
check("and the cap still bites", L:atPopulationCeiling(), true)
end
-- ------- despawning frees it again
do
local L = newLogic(12, 6)
L:fill(25)
check("capped", L:atPopulationCeiling(), true)
for i = 6, 25 do L.entities["e" .. i] = nil end
check("count follows the entity table", L:liveEntityCount(), 5)
check("and spawning resumes", L:atPopulationCeiling(), false)
end
-- ------- no map id anywhere: the ceiling must not care
do
local L = newLogic(12, 6)
L.activeMapId = nil
L.byMap = nil
L:fill(25)
check("no map key is consulted at all", L:atPopulationCeiling(), true)
end
-- ------- the ceiling is an upper bound on ANYTHING the loops can do
do
local L = newLogic(12, 6)
local spawned = 0
-- the three refill loops, run 400 times the way a tick would
for _ = 1, 400 do
for _ = 1, 4 do
if not L:atPopulationCeiling() then
spawned = spawned + 1
L.entities["s" .. spawned] = {}
end
end
end
check("1600 attempts cannot exceed the budget", spawned, 20)
check("which is nothing like 791", spawned < 100, true)
end
-- ------- counting the WORLD, not the bookkeeping
--
-- Twenty Pokemon on screen and the ceiling never engaged, because it counted
-- self.entities. ow.entities is the list _attach registers into and the one
-- the renderer walks every frame; the larger of the two wins so neither view
-- can undercount.
do
local function logic(mine, world)
local self = { entities = {}, mod = { world = { overworld = true } },
targetSpawnCount = 12, targetWaterCount = 6 }
for i = 1, mine do self.entities["m" .. i] = {} end
local ents = {}
for i = 1, world do ents[i] = { __wildsPopulation = true } end
-- a few real NPCs that must NOT be counted
ents[#ents + 1] = { id = "CERULEAN_CITY_OBJ_003" }
ents[#ents + 1] = { id = "CERULEAN_CITY_OBJ_004" }
self.mod.world.overworld = function() return { entities = ents } end
function self.mod.world:overworld() return { entities = ents } end
function self:liveEntityCount()
local m = 0
for _ in pairs(self.entities or {}) do m = m + 1 end
local w = self.mod and self.mod.world
local okOw, ow = pcall(function()
return w and w.overworld and w:overworld()
end)
local n = 0
if okOw and ow and type(ow.entities) == "table" then
for _, e in pairs(ow.entities) do
if type(e) == "table" and e.__wildsPopulation then n = n + 1 end
end
end
return (n > m) and n or m, m, n
end
return self
end
local L = logic(0, 25)
local live, mine, world = L:liveEntityCount()
check("bookkeeping empty, world full", mine, 0)
check("the world count is found", world, 25)
check("and it is the one that counts", live, 25)
local L2 = logic(25, 0)
check("the reverse also holds", select(1, L2:liveEntityCount()), 25)
local L3 = logic(3, 9)
check("the larger view wins", select(1, L3:liveEntityCount()), 9)
-- real map objects are not this spawner's population
local L4 = logic(0, 0)
check("cartridge NPCs are not counted", select(1, L4:liveEntityCount()), 0)
end
print(("spawn ceiling: %d passed, %d failed"):format(pass, fail))
if fail > 0 then os.exit(1) end