133 lines
7.6 KiB
Lua
Executable File
133 lines
7.6 KiB
Lua
Executable File
-- Native headless M1 preflight, staged ONLY in a new disposable course world.
|
|
-- No fake players, no command privileges granted, no exported generator calls.
|
|
-- Enrollment/registration is eligibility, NOT proof of consent or generation.
|
|
local C, H = core, hearth
|
|
local token = C.settings:get("hearth_smoke_token")
|
|
local nonce = C.settings:get("hearth_m1_course_nonce")
|
|
assert(C.settings:get("hearth_launcher_mode") == "course", "Invalid course probe mode")
|
|
assert(type(token) == "string" and #token == 36
|
|
and token:match("^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$")
|
|
and token == nonce, "Missing/mismatched launcher course token")
|
|
local finished = false
|
|
local LOW, HIGH = {x = 0, y = 48, z = 0}, {x = 90, y = 58, z = 40}
|
|
local no_run = "No course run in this session. Use /course."
|
|
local function finish(ok, message)
|
|
if finished then return end
|
|
finished = true
|
|
if ok then C.log("action", "LAST_HEARTH_COURSE_PASS " .. token)
|
|
else C.log("error", "LAST_HEARTH_COURSE_FAIL: " .. tostring(message)) end
|
|
C.request_shutdown("Last Hearth course preflight complete", false, 0)
|
|
end
|
|
local function guarded(fn)
|
|
if finished then return end
|
|
local ok, message = pcall(fn)
|
|
if not ok then finish(false, message) end
|
|
end
|
|
local function registry()
|
|
assert(C.settings:get("hearth_environment") == "staging" and not C.is_singleplayer())
|
|
assert(C.settings:get_bool("hearth_m1_course") == true, "Course flag disabled")
|
|
assert(C.settings:get_bool("hearth_m1_course_snag") ~= true, "Unexpected optional snag")
|
|
assert(C.settings:get("max_users") == "0" and #C.get_connected_players() == 0)
|
|
assert(C.settings:get("bind_address") == "127.0.0.1" and C.settings:get_bool("server_announce") == false)
|
|
assert(C.settings:get_bool("enable_pvp") == false and C.settings:get_bool("secure.enable_security") == true)
|
|
assert(C.settings:get("hearth_preview_date") == "1782043200"
|
|
and C.settings:get("hearth_preview_weather") == "clear", "Course must use summer fixture")
|
|
assert(tonumber(C.settings:get("hearth_latitude")) == 45 and tonumber(C.settings:get("hearth_longitude")) == 0)
|
|
local world = C.get_worldpath():gsub("\\", "/"):gsub("/+$", "")
|
|
assert(world:match("([^/]+)$") == "course", "Not a disposable course world")
|
|
-- A bounded read of the fixed launcher-owned filename; never mint a marker.
|
|
local marker = assert(io.open(world .. "/.hearth-m1-course-nonce", "rb"), "Course marker missing")
|
|
local bytes = marker:read(65); marker:close()
|
|
assert(bytes == nonce or bytes == nonce .. "\n", "Course file nonce mismatch")
|
|
assert(type(H) == "table" and H.storage:get_string("m1_course_world_nonce") == nonce,
|
|
"Real course did not enroll before normal persistence initialization")
|
|
assert(next(H.players) == nil and next(H.sessions) == nil, "Headless course must not invent player sessions")
|
|
for module, methods in pairs({movement = {"step", "delta"}, combat = {"valid_attack", "valid_guard", "sample"},
|
|
construction = {"preview"}, course = {"report"}}) do
|
|
assert(type(H[module]) == "table", "Missing wired module: " .. module)
|
|
for _, method in ipairs(methods) do assert(type(H[module][method]) == "function", module .. "." .. method) end
|
|
end
|
|
for _, method in ipairs({"feedback", "feedback_step", "feedback_sound", "environment", "player_join", "clock_update"}) do
|
|
assert(type(H[method]) == "function", "Missing wired API: " .. method)
|
|
end
|
|
assert(H.course.report("") == no_run, "Enrollment incorrectly created a course run")
|
|
local summer = H.model.solar(1782043200, 45, 0)
|
|
assert(H.clock and H.clock.date == summer.date and H.clock.season == "Summer"
|
|
and H.clock.timeofday == summer.timeofday and H.clock.elevation == summer.elevation
|
|
and H.weather == "clear", "Real clock does not match the fixed summer fixture")
|
|
local slab = assert(C.registered_nodes["hearth:m1_course_half_step"], "Enrolled course half step not registered")
|
|
assert(slab.drawtype == "nodebox" and slab.walkable == true and slab.drop == "")
|
|
assert(slab.groups.not_in_creative_inventory == 1 and slab.node_box.type == "fixed")
|
|
local bounds = slab.node_box.fixed
|
|
assert(#bounds == 6 and bounds[1] == -0.5 and bounds[2] == -0.5 and bounds[3] == -0.5
|
|
and bounds[4] == 0.5 and bounds[5] == 0 and bounds[6] == 0.5, "Half step bounds mismatch")
|
|
for _, name in ipairs({"stone", "planks", "wall", "roof", "water_source", "log", "loose_stone"}) do
|
|
assert(C.registered_nodes["hearth:" .. name], "Missing course material: " .. name)
|
|
end
|
|
for _, name in ipairs({"axe", "knife", "spear", "berries", "water_clean"}) do
|
|
assert(C.registered_items["hearth:" .. name], "Missing course kit item: " .. name)
|
|
end
|
|
local sparring = assert(C.registered_entities["hearth:sparring"]).initial_properties
|
|
assert(sparring.visual == "mesh" and sparring.mesh == "hearth_person.b3d"
|
|
and sparring.textures[1] == "hearth_person.png" and sparring.static_save == false)
|
|
assert(C.registered_entities["hearth:practice"].initial_properties.visual == "cube")
|
|
for _, name in ipairs({"course", "practice", "spar", "comfort"}) do
|
|
assert(type(assert(C.registered_chatcommands[name], "Missing M1 command: " .. name).func) == "function")
|
|
end
|
|
end
|
|
local function terrain()
|
|
local result = {}
|
|
for x = LOW.x, HIGH.x do for z = LOW.z, HIGH.z do for y = LOW.y, HIGH.y do
|
|
local node = C.get_node_or_nil({x = x, y = y, z = z})
|
|
assert(node and node.name ~= "ignore", "Course volume was not loaded for observation")
|
|
assert(node.name ~= "hearth:m1_course_half_step", "Half steps generated without live consent")
|
|
result[#result + 1] = node.name .. ":" .. node.param1 .. ":" .. node.param2
|
|
end end end
|
|
assert(#result == 41041)
|
|
return result
|
|
end
|
|
local function denied()
|
|
-- Empty name is the real nonlive console context, not a fabricated account.
|
|
-- Staging may deny at privilege or arrival checks; never bypass either gate.
|
|
assert(C.get_player_by_name("") == nil)
|
|
for _, param in ipairs({"", "retry", "report"}) do
|
|
local ok, message = C.registered_chatcommands.course.func("", param)
|
|
assert(ok == false and type(message) == "string", "Course accepted a nonlive caller")
|
|
if C.check_player_privs("", {server = true}) then
|
|
assert(message == "Join, finish arrival and be alive before opting in.", "Live-arrival gate missing")
|
|
else
|
|
assert(message:find("Course denied:", 1, true) == 1, "Staging privilege gate missing")
|
|
end
|
|
end
|
|
assert(H.course.report("") == no_run and next(H.players) == nil and next(H.sessions) == nil)
|
|
assert(H.storage:get_string("m1_course_world_nonce") == nonce, "Command changed enrollment")
|
|
end
|
|
C.after(1, function()
|
|
guarded(function()
|
|
registry()
|
|
-- Emergence loads ordinary terrain for observation, NOT course generation.
|
|
C.emerge_area(LOW, HIGH, function(_, action, remaining)
|
|
guarded(function()
|
|
assert(action ~= C.EMERGE_ERRORED and action ~= C.EMERGE_CANCELLED, "Course observation emergence failed")
|
|
if remaining ~= 0 then return end
|
|
C.after(0, function()
|
|
guarded(function()
|
|
local before = terrain()
|
|
denied()
|
|
C.after(0.5, function()
|
|
guarded(function()
|
|
denied()
|
|
local after = terrain()
|
|
for i, value in ipairs(before) do assert(after[i] == value, "Nonlive command changed observed terrain") end
|
|
C.log("action", "Last Hearth: native course enrollment/registry and nonlive consent guards passed; 41041 observed nodes unchanged. No course generation, player traversal, client FPS, rendered mesh or audible playback proven; mock course_spec owns generation checks.")
|
|
finish(true)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
C.after(60, function() finish(false, "Course preflight deadline exceeded") end)
|