World_Reset kept counting, so after one reset the ball dropped from 60 m was ball 10, not the ball 5 the plain build's output and the log from before the reset name. The agent in the first check noticed and worked around it. Clear now restarts ids at 1, so Reset brings back balls 1 to 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
2.3 KiB
C++
66 lines
2.3 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
module Basic.World;
|
|
import std;
|
|
import Basic.DebugShim;
|
|
namespace Debug = Crafter::Debug;
|
|
|
|
void Basic::World::Reset() {
|
|
Clear();
|
|
constexpr std::array<float, 5> heights = { 1, 3, 8, 20, 60 };
|
|
for (std::size_t i = 0; i < heights.size(); ++i) {
|
|
Spawn(static_cast<float>(i) - 2.0f, heights[i], 0, 0.1f);
|
|
}
|
|
}
|
|
|
|
void Basic::World::Clear() {
|
|
balls.clear();
|
|
nextId = 1;
|
|
time = 0;
|
|
fastest = 0;
|
|
}
|
|
|
|
std::int32_t Basic::World::Spawn(float x, float y, float vy, float radius) {
|
|
const std::int32_t id = nextId++;
|
|
balls.push_back({ .id = id, .x = x, .y = y, .vy = vy, .radius = radius });
|
|
Debug::Log("world", "ball {} dropped from {:.1f} m", id, y);
|
|
return id;
|
|
}
|
|
|
|
void Basic::World::Tick(float dt) {
|
|
const std::int32_t steps = std::max(1, physics.substeps);
|
|
const float h = dt / static_cast<float>(steps);
|
|
const float bottom = floor.top - floor.thickness;
|
|
fastest = 0;
|
|
for (Ball& ball : balls) {
|
|
for (std::int32_t step = 0; step < steps; ++step) {
|
|
ball.vy += physics.gravity * h;
|
|
ball.x += ball.vx * h;
|
|
ball.y += ball.vy * h;
|
|
if (ball.lost) continue;
|
|
// A falling ball that overlaps the slab bounces off its top.
|
|
if (ball.vy < 0 && ball.y - ball.radius < floor.top && ball.y + ball.radius > bottom) {
|
|
const float speed = -ball.vy;
|
|
ball.y = floor.top + ball.radius;
|
|
ball.vy = speed * physics.restitution;
|
|
if (speed > 1) Debug::Emit("Bounce", BounceEvent{ .ball = ball.id, .speed = speed });
|
|
}
|
|
}
|
|
if (!ball.lost && !Debug::Check(ball.y + ball.radius >= bottom, "physics", "ball {} is below the floor: y {:.2f} m, vy {:.1f} m/s", ball.id, ball.y, ball.vy)) {
|
|
ball.lost = true;
|
|
}
|
|
fastest = std::max(fastest, std::hypot(ball.vx, ball.vy));
|
|
}
|
|
Debug::Sample("solver.fastest", fastest);
|
|
time += dt;
|
|
}
|
|
|
|
const Basic::Ball* Basic::World::Find(std::int32_t id) const {
|
|
const auto found = std::ranges::find(balls, id, &Ball::id);
|
|
return found == balls.end() ? nullptr : &*found;
|
|
}
|
|
|
|
std::int32_t Basic::World::Lost() const {
|
|
return static_cast<std::int32_t>(std::ranges::count(balls, true, &Ball::lost));
|
|
}
|