DEMI-PROJECT-PACKAGE ?{"files":[{"hash":"sha256:f128f5b6aff97064e1be2c4c7e284dfa7904f251a6a30ed06678685701e50be0","path":"README.md","size":2168},{"hash":"sha256:3ec97cc58bf0fc34d1c79bfe7fb5522df352b9d529f7aa94bf291eee4f570c53","path":"scripts/demi/gameplay/checkpoints.lua","size":1392},{"hash":"sha256:9abf871189c03e84939a39d57c7517625c8eeb6a8df5a4d2ca9505a369dbba72","path":"tests/checkpoints_test.lua","size":1093}],"format_version":1,"manifest":{"dependencies":{"demi.gameplay.events":"^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.checkpoints","public_modules":["demi.gameplay.checkpoints"],"version":"1.0.0"},"package_type":"DemiProjectPackage"}# Checkpoints and respawn Package: `demi.gameplay.checkpoints`. Stores the current checkpoint and queues respawn or scene-entrance requests. Exports `demi.gameplay.checkpoints`; depends on `demi.gameplay.events`. This checkout uses the renamed package; install from the local `packages` registry as described in [package installation](../../README.md). Renaming a local source does not publish the new name to the hosted registry. ```lua local Events = require("demi.gameplay.events") local Checkpoints = require("demi.gameplay.checkpoints") local events = Events.new() local checkpoints = Checkpoints.new(events) local requested local unsubscribe = events:on("respawn_requested", function(value) requested = value end) checkpoints:set({ id = "gate", scene = "scene://game/cave", entrance = "east", version = 1, }) assert(checkpoints:respawn()) events:flush() assert(requested.entrance == "east") local saved = checkpoints:save() unsubscribe() ``` `set` requires string `id` and `scene`, and retains optional `entrance`, `prefab`, `data`, and `version` (default 1). It queues `checkpoint_changed`. `respawn` returns `false` without a checkpoint; otherwise it queues `respawn_requested`. `enter({ scene = ..., entrance = ..., data = ... })` requires string scene/entrance values and queues `scene_entrance_requested` without changing the current checkpoint. The caller owns the event bus, flushes it at a chosen gameplay boundary, and unsubscribes listeners when their owner is destroyed. Listeners implement scene preparation, activation, positioning, and respawn rules. The package does not perform a transition or resolve scene, entrance, or prefab references. `save` returns `nil` or a shallow copy of the checkpoint table; nested `data` remains shared. It does not write a file. `load` accepts a table with string `id` and `scene`, retains that table directly, and emits no event. It neither validates the remaining fields nor migrates versions. The game owns durable save formatting, validation, migrations, and disk I/O. From the repository root, test with: ```sh ./build/linux-debug/demi package test packages/sources/demi.gameplay.checkpoints ``` 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 Test = require("demi.test") 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)