This commit is contained in:
2026-11-05 09:57:11 -05:00
parent d34388bf2c
commit 89e392cc2e
81 changed files with 12410 additions and 1135 deletions
+176 -13
View File
@@ -13,7 +13,7 @@ local items = {
["hearth:log"] = {description="Timber log"}, ["hearth:stone"] = {description="Weathered stone"},
["hearth:alluvium"] = {description="Alluvium"},
}
local species = {"oak", "birch", "pine", "willow", "alder", "beech", "aspen", "rowan", "hazel", "spruce"}
local species = {"oak", "birch", "pine", "willow", "alder", "beech", "aspen", "rowan", "hazel", "spruce", "crab_apple", "wild_cherry", "field_maple"}
local rocks = {"granite", "basalt", "limestone", "slate", "sandstone"}
for _, s in ipairs(species) do
items["hearth:"..s.."_log"] = {description=s.." log"}
@@ -61,6 +61,11 @@ function core.show_formspec(name, form, spec) screens[#screens+1]={name=name,for
hearth = {sessions={alice={}}, model={location=function() end}}
dofile(target)
local player={get_player_name=function() return "alice" end}
local player_metadata={}
function player:get_meta()
return {get_string=function(_,key) return player_metadata[key] or "" end,
set_string=function(_,key,value) player_metadata[key]=value end}
end
function player:get_pos() return {x=10,y=4,z=-2} end
function player:set_inventory_formspec(spec) self.inventory_spec=spec end
local hud = {}
@@ -264,14 +269,16 @@ test("ecosystem command and guide button only read one habitat and status snapsh
end,step=forbidden,forage=forbidden,deposit=forbidden}
hearth.wildlife={status=function()
snapshots=snapshots+1
return {hare=3,deer=2,fox=1,total=6,global_cap=32,local_cap=6,player_cap=6}
return {hare=3,deer=2,fox=1,total=6,global_cap=32,local_cap=6,player_cap=6,
recruitment={attempts=8,successes=2,last_reason="admitted"}}
end}
assert(commands.ecosystem.func("alice"))
assert(reads==1 and snapshots==1 and latest().form=="hearth:ecosystem")
for _,text in ipairs({"Natural grass cover: 3", "Flower-support cover: 2", "Shrub / fern / aquatic / tree cover: 4",
"Moisture: 65%", "Hares: 3", "Deer: 2", "Foxes: 1", "Active total: 6 / 32 globally",
"not a local census", "Loaded sample", "No terrain is loaded", "Ground: Alluvium",
"Pollinator-support plants: 7", "Nutrient reserve: 2 / 8", "pine and spruce stay evergreen", "deciduous canopy thins"}) do
"Pollinator-support plants: 7", "Nutrient reserve: 2 / 8", "pine and spruce stay evergreen", "deciduous canopy thins",
"Admissions: 2 / 8 attempts", "Latest outcome: admitted"}) do
assert(latest().spec:find(text,1,true),text)
end
registered.fields(player,"hearth:guide",{ecosystem_survey=true})
@@ -323,14 +330,14 @@ end)
-- Actual catalogue factory/methods, not an invented list of test species.
local botany_path = target:match("^(.*[/\\])") or ""
local actual_botany = dofile(botany_path .. "botany.lua")(hearth,{})
test("actual botanical catalogue displays eighty species in eight ten-row pages", function()
test("actual botanical catalogue displays one hundred species in ten ten-row pages and eight uneven groups", function()
hearth.botany=actual_botany
local records, groups = actual_botany.list(), {}
assert(#records==80)
for page=1,8 do
assert(#records==100)
for page=1,10 do
hearth.botanical_guide(player,"",page)
local spec,rows=latest().spec,0
assert(spec:find("Page "..page.." / 8 / 80 matches",1,true))
assert(spec:find("Page "..page.." / 10 / 100 matches",1,true))
for _ in spec:gmatch("Habitat:") do rows=rows+1 end
assert(rows==10,"each page must contain ten species rows")
for i=(page-1)*10+1,page*10 do
@@ -339,7 +346,8 @@ test("actual botanical catalogue displays eighty species in eight ten-row pages"
assert(spec:find("Active seasons:",1,true) and spec:find("Roles / conditional policy or descriptors:",1,true))
end
end
local count=0; for _,n in pairs(groups) do assert(n==10); count=count+1 end; assert(count==8)
local sizes={trees=13,shrubs=14,grasses=10,herbs=22,ferns_mosses=11,fungi=10,aquatic=10,lichens=10}
local count=0; for g,n in pairs(groups) do assert(n==sizes[g]); count=count+1 end; assert(count==8)
hearth.guide(player,6)
for _,r in ipairs(records) do assert(latest().spec:find(core.formspec_escape(r.common.." ("..r.latin..")"),1,true),r.id) end
end)
@@ -376,7 +384,7 @@ test("botanical query escaping literal search and finite pagination bounds", fun
hearth.botanical_guide(player,"\n\tlichen",-math.huge)
assert(hearth.sessions.alice.botany_query==" lichen" and hearth.sessions.alice.botany_page==1)
for _,p in ipairs({0/0,-8,"bad",0}) do hearth.botanical_guide(player,"",p); assert(hearth.sessions.alice.botany_page==1) end
hearth.botanical_guide(player,"",math.huge); assert(hearth.sessions.alice.botany_page==8)
hearth.botanical_guide(player,"",math.huge); assert(hearth.sessions.alice.botany_page==10)
local record=actual_botany.list()[1]; record.common=query
hearth.botany={list=function() return {record} end}
hearth.botanical_guide(player,"",1)
@@ -487,7 +495,7 @@ test("HUD accepts legacy mocks without optional hotbar image setters", function(
assert(hearth.sessions.alice.hud.needs)
player.hud_set_hotbar_image,player.hud_set_hotbar_selected_image=image,selected
end)
assert(total==38,"The original 38 recipe/journal checks must remain intact")
assert(total==41,"The original checks plus three new tree recipe searches must remain intact")
test("inspect command uses exact species and rejects absent session unknown and URL input",function()
hearth.botany=actual_botany
hearth.wiki_articles=dofile(sibling.."wiki_articles.lua")
@@ -501,9 +509,9 @@ test("inspect command uses exact species and rejects absent session unknown and
end)
test("botanical article selection uses trusted current-page numeric rows only",function()
hearth.botanical_guide(player,"",2)
assert(latest().spec:find("tooltip[species_choice;Species article]",1,true))
assert(latest().spec:find("tooltip[species_choice;Species article;#181825;#cdd6f4]",1,true))
registered.fields(player,"hearth:botany",{species_choice="2",species_article=true,id="oak"})
assert(latest().form=="hearth:species" and hearth.sessions.alice.species_reader.id=="blackberry")
assert(latest().form=="hearth:species" and hearth.sessions.alice.species_reader.id==actual_botany.list()[12].id)
for _,v in ipairs({"oak","0","11","2.5","1e0","999",{},string.rep("1",1000)}) do
hearth.botanical_guide(player,"",2); local n=#screens
registered.fields(player,"hearth:botany",{species_choice=v,species_article=true})
@@ -566,5 +574,160 @@ test("inventory appearance entry opens only the requesting player's creator", fu
hearth.character = nil
registered.fields(player,"",{character_creator=true}); assert(called == 1)
end)
assert(total==44,"Unexpected recipe/journal/character integration count")
assert(total==47,"Unexpected recipe/journal/character integration count")
test("inventory and journal tooltips contain escaped text, never an invalid third argument",function()
local specs={}
hearth.inventory(player); specs[#specs+1]=player.inventory_spec
local text="Preview woodland clothing and features. Cosmetic only; Save / Apply to keep changes."
assert(player.inventory_spec:find("tooltip[character_creator;"..core.formspec_escape(text)..";#181825;#cdd6f4]",1,true))
hearth.guide(player,6); specs[#specs+1]=latest().spec
hearth.recipes(player,"",1); specs[#specs+1]=latest().spec
hearth.location_form(player); specs[#specs+1]=latest().spec
hearth.botanical_guide(player,"",1); specs[#specs+1]=latest().spec
hearth.ecosystem(player); specs[#specs+1]=latest().spec
local n=0
for _,spec in ipairs(specs) do for body in spec:gmatch("tooltip%[(.-)%]") do
local clean=body:gsub("\\.",""); local _,separators=clean:gsub(";","")
assert(separators==1 or separators==3,"invalid tooltip arity: "..body); n=n+1
end end
assert(n>=10)
end)
test("survey loops actual counts for all 23 animals without changing the world",function()
hearth.lifecycle=dofile(sibling.."lifecycle.lua")
hearth.fauna=dofile(sibling.."fauna.lua")(hearth,{})
local counts={}; for i,r in ipairs(hearth.fauna.list()) do counts[r.id]=i end
local calls=0
hearth.wildlife={status=function() calls=calls+1; return {counts=counts,total=64,global_cap=64,local_cap=12,player_cap=16} end}
hearth.ecosystem(player); assert(calls==1)
for id,n in pairs(counts) do
local title=({hare="Hares",deer="Deer",fox="Foxes"})[id] or id:gsub("_"," ")
assert(latest().spec:find(title..": "..n,1,true),id)
end
assert(latest().spec:find("Calendar age includes unloaded time",1,true))
end)
test("actual fauna guide shows all identities sources and conservative lifecycle status read-only",function()
assert(commands.fauna.func("alice",""))
local records=hearth.fauna.list()
assert(#records==23 and latest().form=="hearth:fauna")
for page=1,5 do
hearth.fauna_guide(player,"",page)
assert(latest().spec:find("Page "..page.." / 5 / 23 matches",1,true))
for i=(page-1)*5+1,math.min(#records,page*5) do
local r=records[i]
assert(latest().spec:find(core.formspec_escape(r.source_url),1,true),r.id)
assert(latest().spec:find(core.formspec_escape(r.latin or "UNKNOWN species identity (legacy deer)"),1,true))
end
assert(latest().spec:find("automatic breeding disabled",1,true))
assert(latest().spec:find("no lifespan-endpoint death",1,true))
end
hearth.fauna_guide(player,"rabbit",1)
assert(latest().spec:find("Maturity: UNKNOWN",1,true))
hearth.fauna_guide(player,"bank_vole",1)
assert(latest().spec:find(core.formspec_escape("42-56 real days [source_reported]"),1,true))
assert(not commands.fauna.func("offline",""))
end)
test("fauna search pagination routing and escaping stay bounded and session-owned",function()
local query="];button[0,0;1,1;attack;Injected]"
hearth.fauna_guide(player,query,0/0)
assert(latest().spec:find(core.formspec_escape(query),1,true) and not latest().spec:find(";attack;Injected]",1,true))
assert(latest().spec:find("No animal matches",1,true))
hearth.fauna_guide(player,string.rep("x",80),1); assert(#hearth.sessions.alice.fauna_query==48)
hearth.fauna_guide(player,"",math.huge); assert(hearth.sessions.alice.fauna_page==5)
registered.fields(player,"hearth:guide",{fauna_index=true}); assert(hearth.sessions.alice.fauna_page==1)
registered.fields(player,"hearth:fauna",{next=true}); assert(hearth.sessions.alice.fauna_page==2)
registered.fields(player,"hearth:fauna",{previous=true}); assert(hearth.sessions.alice.fauna_page==1)
registered.fields(player,"hearth:fauna",{search=true,query="Lutra lutra"}); assert(latest().spec:find("otter",1,true))
registered.fields(player,"hearth:fauna",{botany_index=true}); assert(latest().form=="hearth:botany")
registered.fields(player,"hearth:botany",{fauna_index=true}); assert(latest().form=="hearth:fauna")
registered.fields(player,"hearth:fauna",{field_notes=true}); assert(latest().form=="hearth:guide")
registered.fields(player,"hearth:ecosystem",{fauna_index=true}); assert(latest().form=="hearth:fauna")
local original=hearth.fauna
local r=original.list()[1]; r.common=query; r.source_url=query
hearth.fauna={list=function() return {r} end}; hearth.fauna_guide(player,"",1)
assert(latest().spec:find(core.formspec_escape(query),1,true) and not latest().spec:find(";attack;Injected]",1,true))
hearth.fauna=nil; hearth.fauna_guide(player,"",1); assert(latest().spec:find("catalogue unavailable",1,true))
hearth.fauna=original
local session,n=hearth.sessions.alice,#screens; hearth.sessions.alice=nil
assert(not commands.fauna.func("alice","")); hearth.fauna_guide(player,"",1)
registered.fields(player,"hearth:fauna",{next=true}); assert(#screens==n)
hearth.sessions.alice=session
end)
test("fauna species selection and live inspection show bounded read-only species details",function()
local records=hearth.fauna.list(); local before=hearth.wildlife.status()
hearth.fauna_guide(player,"",1)
assert(latest().spec:find("species_choice;hare,deer,fox,rabbit,red_squirrel",1,true))
registered.fields(player,"hearth:fauna",{species_choice="3",species_details=true})
assert(latest().form=="hearth:fauna_detail" and latest().spec:find("Vulpes vulpes",1,true))
assert(latest().spec:find("Modeled prey:",1,true) and latest().spec:find("Winter rest policy:",1,true))
registered.fields(player,"hearth:fauna_detail",{fauna_index=true})
assert(latest().form=="hearth:fauna" and hearth.sessions.alice.fauna_page==1)
local now=os.time()
assert(hearth.inspect_fauna(player,"hare",{born_at=now-500*86400,origin="born",foodreserve=.42,
resident=true,observation="rest"}))
local spec=latest().spec
for _,text in ipairs({"Lepus europaeus","Lifecycle: mature","model years","real calendar days",
"Coarse modeled food reserve (policy, not biology): 42%","Current animation observation: resting",
"persistent registered resident","Modeled diet: browse","Activity / sleep policy: night / solar_night",
"Winter rest policy:","Modeled behavior:","Modeled prey:","Reproduction: enabled only within the coarse model",
"Active loaded animals of this species:"}) do
assert(spec:find(core.formspec_escape(text),1,true),text)
end
local after=hearth.wildlife.status()
assert(after.total==before.total and after.counts.hare==before.counts.hare,"panel reads status without game changes")
local original_get=hearth.fauna.get; local injection="];button[0,0;1,1;attack;Injected]"
hearth.fauna.get=function(id)
local r=original_get(id)
if id=="hare" then r.common=injection; r.latin=injection; r.source_url=injection end
return r
end
hearth.inspect_fauna(player,"hare",nil)
assert(latest().spec:find(core.formspec_escape(injection),1,true) and not latest().spec:find(";attack;Injected]",1,true))
hearth.fauna.get=original_get
local session,count=hearth.sessions.alice,#screens; hearth.sessions.alice=nil
assert(not hearth.inspect_fauna(player,"hare",nil) and #screens==count)
hearth.sessions.alice=session
local impostor={get_player_name=function() return "alice" end}
assert(not hearth.inspect_fauna(impostor,"hare",nil) and #screens==count)
assert(not hearth.inspect_fauna(player,"hare",setmetatable({},{})) and #screens==count)
assert(not hearth.inspect_fauna(player,"unknown",nil) and #screens==count)
assert(#records==23)
end)
test("wildlife encounters add canonical per-player field-journal entries; catalogue browsing does not",function()
local key="hearth:fauna_observations_v1"
player_metadata[key]=""
hearth.fauna_guide(player,"",1)
assert(latest().spec:find("0 / 23 species recorded",1,true))
hearth.inspect_fauna(player,"hare",{born_at=os.time()-86400,origin="established",foodreserve=.5,
resident=true,observation="awake"})
assert(player_metadata[key]=="hare")
assert(latest().spec:find("Field journal: first recorded encounter",1,true))
hearth.inspect_fauna(player,"fox",{born_at=os.time()-86400,origin="established",foodreserve=.5,
resident=true,observation="awake"})
assert(player_metadata[key]=="fox,hare","journal storage is canonical and sorted")
hearth.inspect_fauna(player,"hare",{born_at=os.time()-86400,origin="established",foodreserve=.5,
resident=true,observation="awake"})
assert(latest().spec:find("already recorded",1,true))
hearth.inspect_fauna(player,"deer",nil)
assert(player_metadata[key]=="fox,hare","catalogue profile selection is not a sighting")
hearth.fauna_guide(player,"",1)
assert(latest().spec:find("2 / 23 species recorded",1,true))
assert(latest().spec:find("recorded encounter",1,true))
player_metadata[key]="hare,unknown"
assert(hearth.inspect_fauna(player,"deer",{born_at=os.time()-86400,origin="established",foodreserve=.5,
resident=true,observation="awake"}))
assert(player_metadata[key]=="hare,unknown","malformed journal data fails closed without overwriting")
player_metadata[key]=nil
end)
test("calendar guidance never promises accelerated saplings daily berries or complete biology",function()
hearth.recipes(player,"Renewal",1)
for _,s in ipairs({"365.2425 days","no 24-hour refill","at most one berry","one real year","UNKNOWN"}) do
assert(latest().spec:find(s,1,true),s)
end
hearth.guide(player,6)
for _,s in ipairs({"100 named species","23 animal identities","eight uneven","real years to decades","fungi do not accelerate growth","disable breeding"}) do
assert(latest().spec:find(s,1,true),s)
end
assert(not latest().spec:find("eight categories of ten",1,true))
end)
assert(total==54,"Unexpected focused integration count")
print("PASS "..total.." recipe discovery tests")