The consumer notes tell agents that commands pick their project's app and how to reach another with -i, the README's discovery row says the same, and the agent notes describe CRAFTER_DEBUG_PROJECT and how an app is matched to its project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.5 KiB
4.5 KiB
Crafter.Debug
This app can be driven while it runs: call its commands, read and set its state, pause and step it, wait for conditions, read its log, take screenshots. It is only in debug builds, under this project's debug flag (the notes above name it, usually --debug-api).
Driving it
- Build with the debug flag first, in the foreground:
crafter-build <debug flag>. That build also compiles the client./build/crafter-debugruns. The first one takes a few minutes. - Then start the app in the background, since it does not exit:
crafter-build <debug flag> -r. - From the project directory, use
./build/crafter-debug:ls --wait 60suntil it is listed;status,cmds,help <command>,get '*'call <command> key=value ...,set <path> <value>pause --when '<condition>'thenwait --paused;step 3,resumewait '<condition>',wait --frames 30,wait --event <Name>trace <path> --frames 120,log --level warn,log --follow(under Monitor)screenshot: open the PNG it prints with Readquitwhen done
./build/crafter-debug guideis the full reference. Errors name the next step.- Exit codes: 2 usage, 3 no instance, 4 app and client from different versions, 5 timed out, 6 the app answered an error.
- Requests run when the app calls
Debug::Pump(), once per frame;statusshows when it last did. - Stopping the background
-rtask can leave the app running. Stop it withquit, orquit --forceif its loop is stuck. - Run long
waits in the background, and chain short steps with&&. - Other projects' debug apps may be running too. Commands pick this project's app and leave the others alone, and
lsmarks this project's.-i <name|id>picks another project's app, or one of two copies of this one. debuggerprints how to attach lldb or gdb for breakpoints and memory.
Coding with it
Missing a command or watch for the task at hand? Add it. It is debug-only code.
- Where it goes:
- The hook points sit in
main, under the project's debug define, or in a shim module:Debug::Session debug({ .name = "..." });afterThreadPool::Start(), andDebug::Pump()once per frame on the app's thread. - Registrations go in debug-only sources, which
project.cppadds only when the flag is on.
- The hook points sit in
- Pause and step hold only what is gated on them. In a frame loop:
while (acc >= dt && Debug::Advance()) { Tick(); acc -= dt; }. In a fixed-step loop:Debug::Paused()andDebug::TakeSteps(). - Registering (
import Crafter.Debug;, namespaceCrafter::Debug):
struct SpawnArgs { Debug::Required<std::string> kind; float x = 0; float y = 0; }; // fields become key=value
Debug::Command("World_Spawn", "Spawn an entity; returns its id.",
[](const SpawnArgs& a) -> Debug::Result<std::int32_t> {
if (!world) return Debug::Error{ "NoWorld", "no world loaded; call Level_Load first" };
return world->Spawn(*a.kind, a.x, a.y);
});
Debug::Watch("physics.gravity", gravity, { .min = -50, .max = 0, .help = "m/s^2" }); // get and set
Debug::Watch("scene", [] { return SceneName(); }); // read-only
Debug::Sample("solver.residual", residual); // hot code, any thread; `trace --history`
Debug::Check(mass > 0, "physics", "body {} has mass {}", id, mass); // a soft assert; also Log, LogWarn, LogError, Emit
- Rules:
- Threading: handlers, getters and setters run on the app's thread inside
Pump, so they read state without locks, but must not block. For a result that takes frames (a GPU readback, a network round trip), returnDebug::Async<T>with a poller. - Reuse the app's own paths: call the same code the UI or game uses, rather than re-implementing it in the command, because a re-implemented command drifts from the real behaviour.
- Errors are
Debug::Error{ "Code", "message" }, with the message saying what to do next. - Names: commands are
Area_Verb; watches are dotted paths. - Per-object state goes through a
Debug::Groupmember,Debug::Group debug{ std::format("fort{}", slot) }, which prefixes paths and unregisters with its owner. It can't be moved, so keep such objects instd::unique_ptrorstd::deque. - Types: arguments and results are aggregates (public fields with default initializers), scoped enums, strings, numbers or std containers. For anything else, specialize
Debug::Codec<T>. - Screenshots:
Debug::SetCapturemakes them work. Crafter.Debug's README has the Crafter.Graphics version.
- Threading: handlers, getters and setters run on the app's thread inside