// 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 heights = { 1, 3, 8, 20, 60 }; for (std::size_t i = 0; i < heights.size(); ++i) { Spawn(static_cast(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(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::ranges::count(balls, true, &Ball::lost)); }