feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
|
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
// Three configurations:
|
|
|
|
|
// Crafter.Debug.Core what the app and the client share (values, codecs)
|
|
|
|
|
// crafter.debug-cli the client, crafter-debug
|
|
|
|
|
// Crafter.Debug what an app links (returned), depending on both
|
|
|
|
|
// The client is one of the library's dependencies only so that a consumer's
|
|
|
|
|
// debug build compiles it in the same pass as the app; an executable adds no
|
|
|
|
|
// link flags to what depends on it. Consumers load this file through
|
|
|
|
|
// Debug::Use (tooling/), which also writes their launcher.
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
import std;
|
|
|
|
|
import Crafter.Build;
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
using namespace Crafter;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
// The configurations this file makes besides the one it returns, at
|
|
|
|
|
// stable addresses for the pointers in `dependencies`. One set per load,
|
|
|
|
|
// not statics: a static would keep the first load's args for every load.
|
|
|
|
|
std::deque<Configuration> owned;
|
|
|
|
|
|
|
|
|
|
// This file's directory. A configuration a project file makes for itself
|
|
|
|
|
// keeps the path it is given, and "./" would later resolve against the
|
|
|
|
|
// consumer's directory instead of this one.
|
|
|
|
|
fs::path Root() {
|
|
|
|
|
return fs::path(std::source_location::current().file_name()).parent_path();
|
|
|
|
|
}
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
|
feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:40:59 +01:00
|
|
|
// Tests of the core, linked against it alone.
|
|
|
|
|
constexpr std::array<std::string_view, 4> coreTests = {
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
"ShouldRoundTripJson",
|
|
|
|
|
"ShouldRoundTripAggregates",
|
|
|
|
|
"ShouldReportDecodeErrorsWithPaths",
|
|
|
|
|
"ShouldDescribeDefaultsAndRequired",
|
|
|
|
|
};
|
feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:40:59 +01:00
|
|
|
|
|
|
|
|
// Tests of the app library.
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
constexpr std::array<std::string_view, 7> libraryTests = {
|
feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:40:59 +01:00
|
|
|
"ShouldInvokeCommands",
|
|
|
|
|
"ShouldResolveWatchPaths",
|
|
|
|
|
"ShouldRecordTimeline",
|
|
|
|
|
"ShouldEvaluateConditions",
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
"ShouldAnswerRequests",
|
|
|
|
|
"ShouldPauseAndStep",
|
|
|
|
|
"ShouldWaitTraceAndFollow",
|
feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:40:59 +01:00
|
|
|
};
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
|
|
|
|
|
Configuration cfg;
|
|
|
|
|
cfg.path = "./";
|
|
|
|
|
cfg.name = "Crafter.Debug";
|
|
|
|
|
cfg.outputName = "Crafter.Debug";
|
|
|
|
|
cfg.type = ConfigurationType::LibraryStatic;
|
|
|
|
|
ApplyStandardArgs(cfg, args);
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
|
|
|
|
|
Configuration& core = owned.emplace_back();
|
|
|
|
|
core.path = Root();
|
|
|
|
|
core.name = "Crafter.Debug.Core";
|
|
|
|
|
core.outputName = "Crafter.Debug.Core";
|
|
|
|
|
core.type = ConfigurationType::LibraryStatic;
|
|
|
|
|
ApplyStandardArgs(core, args);
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
{
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
std::array<fs::path, 6> interfaces = {
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
"interfaces/Crafter.Debug.Core-Value",
|
|
|
|
|
"interfaces/Crafter.Debug.Core-Reflect",
|
|
|
|
|
"interfaces/Crafter.Debug.Core-Codec",
|
|
|
|
|
"interfaces/Crafter.Debug.Core-Result",
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
"interfaces/Crafter.Debug.Core-Protocol",
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
"interfaces/Crafter.Debug.Core",
|
|
|
|
|
};
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
std::array<fs::path, 3> implementations = {
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
"implementations/Crafter.Debug.Core-Value",
|
|
|
|
|
"implementations/Crafter.Debug.Core-Codec",
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
"implementations/Crafter.Debug.Core-Protocol",
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
};
|
|
|
|
|
core.GetInterfacesAndImplementations(interfaces, implementations);
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
}
|
|
|
|
|
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
// The client, built for the library's target. Not through
|
|
|
|
|
// ApplyStandardArgs: a consumer's --lib would make it a library.
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
Configuration& client = owned.emplace_back();
|
|
|
|
|
client.path = Root();
|
|
|
|
|
client.name = "crafter.debug-cli";
|
|
|
|
|
client.outputName = "crafter-debug";
|
|
|
|
|
client.type = ConfigurationType::Executable;
|
|
|
|
|
client.target = cfg.target;
|
|
|
|
|
client.march = cfg.march;
|
|
|
|
|
client.mtune = cfg.mtune;
|
|
|
|
|
client.sysroot = cfg.sysroot;
|
|
|
|
|
client.debug = cfg.debug;
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
client.dependencies = { &core };
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
{
|
|
|
|
|
std::array<fs::path, 0> interfaces = {};
|
|
|
|
|
std::array<fs::path, 1> implementations = { "implementations/main" };
|
|
|
|
|
client.GetInterfacesAndImplementations(interfaces, implementations);
|
|
|
|
|
}
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
|
|
|
|
|
cfg.dependencies = { &core, &client };
|
|
|
|
|
{
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
std::array<fs::path, 5> interfaces = {
|
feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:40:59 +01:00
|
|
|
"interfaces/Crafter.Debug-Registry",
|
|
|
|
|
"interfaces/Crafter.Debug-Timeline",
|
|
|
|
|
"interfaces/Crafter.Debug-Condition",
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
"interfaces/Crafter.Debug-Session",
|
feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:40:59 +01:00
|
|
|
"interfaces/Crafter.Debug",
|
|
|
|
|
};
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
std::array<fs::path, 4> implementations = {
|
feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:40:59 +01:00
|
|
|
"implementations/Crafter.Debug-Registry",
|
|
|
|
|
"implementations/Crafter.Debug-Timeline",
|
|
|
|
|
"implementations/Crafter.Debug-Condition",
|
feat(session): serve requests without blocking a thread, pause and step
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:53:15 +01:00
|
|
|
"implementations/Crafter.Debug-Session",
|
feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:40:59 +01:00
|
|
|
};
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
cfg.GetInterfacesAndImplementations(interfaces, implementations);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tests only when this is the project being built: a consumer's load of
|
|
|
|
|
// this file has no use for them.
|
|
|
|
|
if (IsTopLevelProject()) {
|
feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:40:59 +01:00
|
|
|
for (const std::string_view test : coreTests) cfg.AddTest(test).Dependencies({ &core });
|
|
|
|
|
for (const std::string_view test : libraryTests) cfg.AddTest(test).Dependencies({ &cfg });
|
feat(core): Value, reflection, codecs and results, shared by app and client
Crafter.Debug.Core is what the app side and the crafter-debug client
both need, without the session:
- Value: a JSON value with its own parser and printer, so no consumer
interface sees a JSON library. Integers stay integers and whole
doubles stay doubles; NaN and the infinities, which JSON cannot
spell, are written as strings rather than hidden, because a debugger
must not lose a NaN; invalid UTF-8 is replaced so output always
parses. Objects keep insertion order, the order an agent reads.
- Reflect: aggregate members and names through structured-binding
packs and source_location, taken from Crafter.DB, plus scoped-enum
names by probing a range of values. Self-tested with static_asserts
so a compiler that changes function_name()'s shape fails the build.
- Codec<T>: encode, decode and describe for bool, integers, floating
point, strings, enums, optional, vector, array, string-keyed maps,
Value, Required<T> and every aggregate. Decode reports every
problem in a call with its path and, for a typo, the likely intended
name, and never throws: a bad call cannot take an app down, which a
wrongly typed parameter could in 3DForts' hand-written from_json.
- Result, Error and Async: handler results, including ones that take
frames to arrive, so callers need no ready:false polling.
project.cpp now builds Crafter.Debug.Core as its own library, used by
both the app library and the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:31:58 +01:00
|
|
|
}
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
return cfg;
|
|
|
|
|
}
|