feat(examples): Basic, a ball simulation with a bug to find
The example was a placeholder that printed which build it was. It is now a headless simulation of five balls dropped onto a 10 cm floor slab at 60 ticks a second, with a bug planted the way such bugs are written: collision is checked only where each step lands, so the ball dropped from 60 m, at 34 m/s, steps over the slab and falls forever. It shows the consumer side end to end: - Basic.DebugShim forwards Pump, Advance, QuitRequested, Log, Check, Emit and Sample to Crafter.Debug in debug builds and does nothing in plain ones, so the simulation and main have no #ifdef but the session's. - Basic.DebugCommands, compiled only when Debug::Use returns true, registers World_Spawn, World_Reset, World_Clear and Ball_Get, and watches for the physics, the floor and the balls. - The simulation checks each ball stays above the floor, emits Bounce and samples the fastest ball, so `pause --on-check`, `wait --event` and `trace --history` have something to show. Driven over the real transport, it shows the whole loop: `log --level warn` names the ball, `pause --on-check` stops the frame it fails, `set physics.substeps 4` and `call World_Reset` show the fix holds before any code changes. The plain build simulates ten seconds and prints that one ball fell through. Its agent notes describe the app without the bug. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6da3b274a6
commit
4a0d911e69
8 changed files with 292 additions and 9 deletions
14
examples/Basic/interfaces/Basic.DebugCommands.cppm
Normal file
14
examples/Basic/interfaces/Basic.DebugCommands.cppm
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// What an agent can call and read in a debug build. project.cpp compiles
|
||||
// this module only when Debug::Use added Crafter.Debug, so the plain build
|
||||
// has none of it.
|
||||
export module Basic.DebugCommands;
|
||||
import Basic.World;
|
||||
|
||||
export namespace Basic {
|
||||
// Registers the commands and watches on `world`, which must outlive the
|
||||
// Session.
|
||||
void RegisterDebug(World& world);
|
||||
}
|
||||
26
examples/Basic/interfaces/Basic.DebugShim.cppm
Normal file
26
examples/Basic/interfaces/Basic.DebugShim.cppm
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// Crafter.Debug's frame-loop and timeline calls, in every build: the real
|
||||
// ones under --debug-api, and in a plain build stand-ins that do nothing, so
|
||||
// the simulation and main need no #ifdef around them. This is one of the
|
||||
// app's own sources, so the define Debug::Use adds reaches it.
|
||||
export module Basic.DebugShim;
|
||||
import std;
|
||||
#ifdef BASIC_DEBUG_API
|
||||
export import Crafter.Debug;
|
||||
#else
|
||||
export namespace Crafter::Debug {
|
||||
inline void Pump() {}
|
||||
inline bool Advance() { return true; }
|
||||
inline bool Paused() { return false; }
|
||||
inline std::uint32_t TakeSteps() { return 0; }
|
||||
inline bool QuitRequested() { return false; }
|
||||
template<class... A> void Log(A&&...) {}
|
||||
template<class... A> void LogWarn(A&&...) {}
|
||||
template<class... A> void LogError(A&&...) {}
|
||||
template<class... A> bool Check(bool ok, A&&...) { return ok; }
|
||||
template<class... A> void Emit(A&&...) {}
|
||||
template<class T> void Sample(std::string_view, const T&) {}
|
||||
}
|
||||
#endif
|
||||
66
examples/Basic/interfaces/Basic.World.cppm
Normal file
66
examples/Basic/interfaces/Basic.World.cppm
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// Balls dropped onto a floor slab, in two dimensions, at a fixed step. y is
|
||||
// up, in metres; the slab's top is at floor.top and it is floor.thickness
|
||||
// thick.
|
||||
export module Basic.World;
|
||||
import std;
|
||||
|
||||
export namespace Basic {
|
||||
struct Ball {
|
||||
std::int32_t id = 0;
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float vx = 0;
|
||||
float vy = 0;
|
||||
float radius = 0.1f;
|
||||
// Below the floor, and so out of the scene for good.
|
||||
bool lost = false;
|
||||
};
|
||||
|
||||
struct Physics {
|
||||
float gravity = -9.81f;
|
||||
// The share of its speed a ball keeps when it bounces.
|
||||
float restitution = 0.6f;
|
||||
// Collision checks per tick.
|
||||
std::int32_t substeps = 1;
|
||||
};
|
||||
|
||||
struct Floor {
|
||||
float top = 0;
|
||||
float thickness = 0.1f;
|
||||
};
|
||||
|
||||
// What `wait --event Bounce` waits for, sent for bounces faster than 1 m/s.
|
||||
struct BounceEvent {
|
||||
std::int32_t ball = 0;
|
||||
float speed = 0;
|
||||
};
|
||||
|
||||
class World {
|
||||
public:
|
||||
Physics physics;
|
||||
Floor floor;
|
||||
|
||||
// Removes every ball and drops the scene's five.
|
||||
void Reset();
|
||||
void Clear();
|
||||
// Returns the new ball's id.
|
||||
std::int32_t Spawn(float x, float y, float vy, float radius);
|
||||
void Tick(float dt);
|
||||
|
||||
const std::deque<Ball>& Balls() const { return balls; }
|
||||
const Ball* Find(std::int32_t id) const;
|
||||
double Time() const { return time; }
|
||||
std::int32_t Lost() const;
|
||||
// The fastest ball's speed in the last tick, in m/s.
|
||||
float Fastest() const { return fastest; }
|
||||
|
||||
private:
|
||||
std::deque<Ball> balls;
|
||||
std::int32_t nextId = 1;
|
||||
double time = 0;
|
||||
float fastest = 0;
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue