DEMI-PROJECT-PACKAGE :{"files":[{"hash":"sha256:20a6c9c0e8dfd20565dcdfae375924d87010c9081df2dcc3285b6e591e4f4f58","path":"README.md","size":166},{"hash":"sha256:3ec97cc58bf0fc34d1c79bfe7fb5522df352b9d529f7aa94bf291eee4f570c53","path":"scripts/demi/gameplay/checkpoints.lua","size":1392},{"hash":"sha256:2819ce41c93d2e643a139c95327595056a508cef575ca89dc33463d9f2ae5a79","path":"tests/checkpoints_test.lua","size":1058}],"format_version":1,"manifest":{"dependencies":{"demi.gameplay.core":"^1.0.0"},"engine":"^0.1.0","exported_events":["checkpoint_changed","respawn_requested","scene_entrance_requested"],"files":["README.md","scripts/demi/gameplay/checkpoints.lua","tests/checkpoints_test.lua"],"format_version":1,"name":"demi.gameplay.traversal","public_modules":["demi.gameplay.checkpoints"],"version":"1.0.0"},"package_type":"DemiProjectPackage"}# demi.gameplay.traversal Serializable checkpoint and scene-entrance state. Scene preparation and activation are injected by the game, keeping transitions deferred. local Checkpoints = {} Checkpoints.__index = Checkpoints function Checkpoints.new(events) return setmetatable({ events = assert(events), current = nil }, Checkpoints) end function Checkpoints:set(values) assert(type(values.id) == "string" and type(values.scene) == "string") self.current = { id = values.id, scene = values.scene, entrance = values.entrance, prefab = values.prefab, version = values.version or 1, data = values.data } self.events:emit("checkpoint_changed", self:save()) end function Checkpoints:save() if not self.current then return nil end local copy = {}; for key, value in pairs(self.current) do copy[key] = value end return copy end function Checkpoints:load(value) if type(value) ~= "table" or type(value.id) ~= "string" or type(value.scene) ~= "string" then return false, "invalid_checkpoint" end self.current = value; return true end function Checkpoints:respawn() if not self.current then return false end self.events:emit("respawn_requested", self:save()); return true end function Checkpoints:enter(values) if type(values) ~= "table" or type(values.scene) ~= "string" or type(values.entrance) ~= "string" then return false, "invalid_entrance" end self.events:emit("scene_entrance_requested", { scene = values.scene, entrance = values.entrance, data = values.data, }) return true end return Checkpoints local Events = require("demi.gameplay.events") local Checkpoints = require("demi.gameplay.checkpoints") Test.case("checkpoint round trips stable entrance data", function() local events = Events.new(); local first = Checkpoints.new(events) first:set({ id = "cp-2", scene = "scene://cave", entrance = "east", version = 3 }) local second = Checkpoints.new(events) Test.equal(second:load(first:save()), true); Test.equal(second.current.entrance, "east") end) Test.case("respawn without checkpoint is rejected", function() Test.equal(Checkpoints.new(Events.new()):respawn(), false) end) Test.case("scene entrance request requires stable identifiers", function() local events, requested = Events.new(), nil events:on("scene_entrance_requested", function(value) requested = value end) local checkpoints = Checkpoints.new(events) Test.equal(checkpoints:enter({ scene = "scene://town" }), false) Test.equal(checkpoints:enter({ scene = "scene://town", entrance = "north" }), true) events:flush() Test.equal(requested.entrance, "north") end)