docs(tooling): tell consumers' agents how to code with Crafter.Debug
The consumer notes only said how to drive a running app. They now also say how to add to what an agent can reach: where the hook points and registrations go, gating pause and step, registering commands, watches, samples and checks, and the rules that keep that code right (handlers run on the app's thread and must not block; Async for results that take frames; call the app's own code paths instead of re-implementing them, the lesson from 3DForts' drifting verbs; Group for per-object state; which types travel). All of it stays in tooling/AGENTS.consumer.md, which every build merges through the unconditional pragma. Notes beside the root project.cpp would only come with debug builds, when the library is in the graph, and would then duplicate these; AGENTS.template.md now says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8b995cd76a
commit
2a954b7f1f
2 changed files with 44 additions and 10 deletions
|
|
@ -17,6 +17,10 @@ The client is one of the library's dependencies only so that a consumer's debug
|
||||||
|
|
||||||
`tooling/` is a separate project, `Crafter.Debug.Build`. Consumers load it with `#pragma crafter dependency ... project "tooling/project.cpp"`. Its `Debug::Use` adds the library under the consumer's flag and writes the consumer's `build/crafter-debug` launcher at load time.
|
`tooling/` is a separate project, `Crafter.Debug.Build`. Consumers load it with `#pragma crafter dependency ... project "tooling/project.cpp"`. Its `Debug::Use` adds the library under the consumer's flag and writes the consumer's `build/crafter-debug` launcher at load time.
|
||||||
|
|
||||||
|
`tooling/AGENTS.consumer.md` is the only notes file consumers get, and it covers both driving the app and coding with it. Crafter.Build merges a dependency's notes only when that dependency is in the build:
|
||||||
|
- **The tooling** comes in through the pragma, which is unconditional, so its notes are there after every build.
|
||||||
|
- **The root library** comes in only under the consumer's debug flag. Notes beside the root `project.cpp` would vanish after plain builds and appear twice after debug builds, so the root has none.
|
||||||
|
|
||||||
`examples/Basic` is a consumer that uses this checkout through a local pragma path.
|
`examples/Basic` is a consumer that uses this checkout through a local pragma path.
|
||||||
|
|
||||||
## Dev environment
|
## Dev environment
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
## Crafter.Debug
|
## 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.
|
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`).
|
||||||
|
|
||||||
- Start its debug build in the background, since it does not exit: `crafter-build <debug flag> -r`. This project's notes above name the flag, usually `--debug-api`.
|
### Driving it
|
||||||
- Drive it with `./build/crafter-debug` from the project directory:
|
|
||||||
|
- Start the debug build in the background, since it does not exit: `crafter-build <debug flag> -r`.
|
||||||
|
- From the project directory, use `./build/crafter-debug`:
|
||||||
- `ls --wait 60s` until it is listed; `status`, `cmds`, `help <command>`, `get '*'`
|
- `ls --wait 60s` until it is listed; `status`, `cmds`, `help <command>`, `get '*'`
|
||||||
- `call <command> key=value ...`, `set <path> <value>`
|
- `call <command> key=value ...`, `set <path> <value>`
|
||||||
- `pause --when '<condition>'` then `wait --paused`; `step 3`, `resume`
|
- `pause --when '<condition>'` then `wait --paused`; `step 3`, `resume`
|
||||||
|
|
@ -13,11 +15,39 @@ This app can be driven while it runs: call its commands, read and set its state,
|
||||||
- `quit` when done
|
- `quit` when done
|
||||||
- `./build/crafter-debug guide` is the full reference. Errors name the next step.
|
- `./build/crafter-debug guide` is 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.
|
- 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; `status` shows when it last did.
|
||||||
Gotchas:
|
- Stopping the background `-r` task can leave the app running. Stop it with `quit`, or `quit --force` if its loop is stuck.
|
||||||
- Requests run when the app calls `Debug::Pump()`, once per frame. `status` shows when it last did.
|
|
||||||
- `pause` and `step` only hold what the app gates on `Debug::Advance()` or `Debug::Paused()`.
|
|
||||||
- Stopping the background `-r` task can leave the app running. Stop it with `./build/crafter-debug quit`, or `quit --force` if its loop is stuck.
|
|
||||||
- Run long `wait`s in the background, and chain short steps with `&&`.
|
- Run long `wait`s in the background, and chain short steps with `&&`.
|
||||||
- For breakpoints and memory, `./build/crafter-debug debugger` prints how to attach lldb or gdb inside the app's sandbox.
|
- `debugger` prints how to attach lldb or gdb for breakpoints and memory.
|
||||||
- To give the agent more to work with, register commands and watches in the app's debug-only code (`Debug::Command`, `Debug::Watch`, `Debug::Group`). Crafter.Debug's README shows how.
|
|
||||||
|
### 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 = "..." });` after `ThreadPool::Start()`, and `Debug::Pump()` once per frame on the app's thread.
|
||||||
|
- Registrations go in debug-only sources, which `project.cpp` adds only when the flag is on.
|
||||||
|
- **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()` and `Debug::TakeSteps()`.
|
||||||
|
- **Registering** (`import Crafter.Debug;`, namespace `Crafter::Debug`):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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), return `Debug::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::Group` member, `Debug::Group debug{ std::format("fort{}", slot) }`, which prefixes paths and unregisters with its owner. It can't be moved, so keep such objects in `std::unique_ptr` or `std::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::SetCapture` makes them work. Crafter.Debug's README has the Crafter.Graphics version.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue