Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / commits / 5b0d5db

sdl_runtime_compiler git · main

SDL3 game for running and compiling code at runtime

sdl3 c++ compiler dlopen cmake · first commit 2026-04-19 · last commit 2026-07-03 (3 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-runtime-compiler

C++ 72.3% C 26.2%
git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gz

Cleanup With Claude

Christian Immanuel · 2026-05-31 07:51 · 5b0d5db92505bb9bfcfd875b7d689f9ae4c8e619

 src/states/game_state.cpp          | 155 ++++---------------------------------
 src/states/game_state.hpp          |  17 +---
 src/systems/celebration_system.cpp | 110 ++++++++++++++++++++++++++
 src/systems/celebration_system.hpp |  24 ++++++
 src/systems/word_ai_system.cpp     |  22 +++---
 src/utils/text_wrap.hpp            |  55 +++++++++++++
 6 files changed, 216 insertions(+), 167 deletions(-)

diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index 906f29b..971e639 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -10,6 +10,7 @@
 #include "levels/level_check.hpp"
 #include "levels/level_list.hpp"
 #include "rendering/gl_renderer.hpp"
+#include "systems/celebration_system.hpp"
 #include "systems/game_mod_abi.h"
 #include "systems/input_system.hpp"
 #include "systems/physics_system.hpp"
@@ -18,6 +19,7 @@
 #include "text_3d/text_label.hpp"
 #include "utils/config.hpp"
 #include "utils/frame_profiler.hpp"
+#include "utils/text_wrap.hpp"
 #include "wall/text_wall.hpp"
 #include "wall/text_wall_scroll.hpp"
 
@@ -38,6 +40,7 @@ GameState::GameState(size_t starting_level)
     freelook_sys    = cpputils::make_unique<FreelookSystem>();
     particle_sys    = cpputils::make_unique<ParticleSystem>();
     mod_loader      = cpputils::make_unique<ModLoader>();
+    celebration_sys = cpputils::make_unique<CelebrationSystem>();
 
     floor_sys       = cpputils::make_unique<FloorMandelbrot>();
     floor_sys->generate(ctx.world->scale(), -ctx.world->scale());
@@ -623,7 +626,7 @@ GameState::update(double dt)
     }
 
     { FP_ZONE("update.goals");
-        celebration_Tick(dt);
+        celebration_sys->tick(dt, *particle_sys);
         level_TryAdvance();
     }
 
@@ -702,9 +705,8 @@ GameState::render(SDL_Window& window, GlRenderer& renderer)
     { FP_ZONE("render.particles");   particle_sys->draw(renderer, *ctx.camera, *ctx.light); }
     { FP_ZONE("render.floor_sys");   floor_sys   ->draw(renderer, *ctx.camera, *ctx.light); }
 
-    if (celebration.banner) {
-        FP_ZONE("render.banner");
-        celebration.banner->draw(renderer, *ctx.camera, *ctx.light);
+    { FP_ZONE("render.celebration");
+        celebration_sys->draw(renderer, *ctx.camera, *ctx.light);
     }
 
     { FP_ZONE("render.end_Frame"); renderer.end_Frame(&window); }
@@ -749,45 +751,7 @@ GameState::infowall_StepdetailsShow()
     Wall::Transform  wall_transform = Wall::get_Transform(Wall::Role::Info, world_scale);
 
     constexpr size_t MAX_INFO_LINE_CHARS = 92;
-
-    std::vector<std::string> lines;
-    std::string current_line;
-
-    auto flush_line = [&]() {
-        while (!current_line.empty() && current_line.back() == ' ')
-            current_line.pop_back();
-        if (!current_line.empty()) {
-            lines.push_back(current_line);
-            current_line.clear();
-        } else {
-            lines.push_back("");
-        }
-    };
-
-    std::string word;
-    auto flush_word = [&]() {
-        if (word.empty()) return;
-        if (current_line.size() + word.size() + (current_line.empty() ? 0 : 1)
-                > MAX_INFO_LINE_CHARS) {
-            flush_line();
-        }
-        if (!current_line.empty()) current_line += ' ';
-        current_line += word;
-        word.clear();
-    };
-
-    for (char c : details) {
-        if (c == '\n') {
-            flush_word();
-            flush_line();
-        } else if (c == ' ' || c == '\t') {
-            flush_word();
-        } else {
-            word += c;
-        }
-    }
-    flush_word();
-    flush_line();
+    std::vector<std::string> lines = wrap_Text(details, MAX_INFO_LINE_CHARS);
 
     const float bottom_y = -world_scale + 1.0f;
     float y = bottom_y + static_cast<float>(lines.size() - 1) * Wall::TEXT_LINE_SPACING;
@@ -799,15 +763,11 @@ GameState::infowall_StepdetailsShow()
         if (line.empty()) continue;
 
         Text3dPtr t = make_Text3d(line, Text3d::depth_wall, 0.0025f,
-                                  pos,  Text3d::color_white);
+                                  pos, Text3d::color_white);
         t->yaw = wall_transform.yaw;
 
         ctx.wall_texts.push_back(cpputils::make_unique<TextWall>(
-                            TextWall{
-                                 wall_transform.wall_position,
-                                 Wall::Role::Info,
-                                 std::move(t)
-                             }));
+            TextWall{ wall_transform.wall_position, Wall::Role::Info, std::move(t) }));
     }
 }
 
@@ -1046,9 +1006,9 @@ GameState::goal_ContextBuild() const
 bool
 GameState::level_TryAdvance()
 {
-    if (celebration.active) {
-        if (celebration.t >= celebration.duration) {
-            celebration_Finish();
+    if (celebration_sys->active()) {
+        if (celebration_sys->progress() >= 1.0) {
+            celebration_sys->finish(*particle_sys);
             level_Advance();
             return true;
         }
@@ -1063,7 +1023,8 @@ GameState::level_TryAdvance()
     if (!level_runner || level_runner->complete())
         return false;
 
-    celebration_Start();
+    auto [anchor, right, glyph_yaw] = anchorframe_Current();
+    celebration_sys->start(*ctx.camera, glyph_yaw);
     return false;
 }
 
@@ -1097,94 +1058,6 @@ GameState::level_Advance()
 }
 
 
-void
-GameState::celebration_Start()
-{
-    celebration.active       = true;
-    celebration.t            = 0.0;
-    celebration.next_burst_t = 0.0;
-
-    auto [anchor, right, glyph_yaw] = anchorframe_Current();
-
-    Vec3 banner_pos {
-        ctx.camera->pos.x + ctx.camera->fwd.x * 10.0,
-        ctx.camera->pos.y + ctx.camera->fwd.y * 10.0,
-        ctx.camera->pos.z + ctx.camera->fwd.z * 10.0,
-    };
-
-    if (banner_pos.y < static_cast<double>(-WORLD_BOX_SCALE+1.0f) )
-        banner_pos.y = -WORLD_BOX_SCALE+1.0f;
-
-    celebration.banner = make_Text3d("Success",
-                                     Text3d::depth_floor * 2.0f,
-                                     0.012f, banner_pos,
-                                     Text3d::color_white);
-    celebration.banner->yaw       = static_cast<float>(glyph_yaw);
-    celebration.banner->body.mode = PhysicsMode::Static;
-}
-
-
-void
-GameState::celebration_Tick(double dt)
-{
-    if (!celebration.active) return;
-
-    celebration.t += dt;
-
-    // Slow banner spin
-    if (celebration.banner)
-        celebration.banner->yaw += static_cast<float>(dt * 0.6);
-
-    // Fireworks bursts on a schedule. We aim for ~7 bursts across the
-    // duration, with slight jitter to avoid mechanical pacing.
-    if (celebration.t >= celebration.next_burst_t &&
-        celebration.t <  celebration.duration - 0.3)
-    {
-        // Random position across the play area, biased to upper half so
-        // bursts read as airborne fireworks not floor_sys explosions.
-        const double ws = ctx.world->scale();
-        const double margin = 3.0;
-        auto rnd = []() {
-            return static_cast<double>(rand()) / static_cast<double>(RAND_MAX);
-        };
-
-        Vec3 center {
-            (rnd() * 2.0 - 1.0) * (ws - margin),
-            rnd() * (ws - margin),                  // 0 .. ws-margin (upper half)
-            (rnd() * 2.0 - 1.0) * (ws - margin),
-        };
-
-        // Random saturated hue. Convert HSV(h, 1, 1) to RGB cheaply.
-        double h = rnd() * 6.0;
-        int    i = static_cast<int>(h);
-        double f = h - i;
-        float r=0, g=0, b=0;
-        switch (i % 6) {
-            case 0: r=1;     g=(float)f; b=0;          break;
-            case 1: r=(float)(1-f); g=1; b=0;          break;
-            case 2: r=0;     g=1;        b=(float)f;   break;
-            case 3: r=0;     g=(float)(1-f); b=1;      break;
-            case 4: r=(float)f; g=0;     b=1;          break;
-            case 5: r=1;     g=0;        b=(float)(1-f); break;
-        }
-
-        particle_sys->emit_FireworkBurst(center, SDL_FColor{r, g, b, 1.0f}, 80);
-
-        // Next burst in 0.5..1.0s.
-        celebration.next_burst_t = celebration.t + 0.5 + rnd() * 0.5;
-    }
-}
-
-
-void
-GameState::celebration_Finish()
-{
-    celebration.active = false;
-    celebration.banner.reset();
-    particle_sys->clear();
-}
-
-
 void
 GameState::mod_Apply(const std::vector<ModObject>& objs)
 {
diff --git a/src/states/game_state.hpp b/src/states/game_state.hpp
index a88f9cf..f7d9948 100644
--- a/src/states/game_state.hpp
+++ b/src/states/game_state.hpp
@@ -13,6 +13,7 @@
 #include "rendering/floor_mandelbrot.hpp"
 #include "states/game_context.hpp"
 #include "systems/carry_system.hpp"
+#include "systems/celebration_system.hpp"
 #include "systems/compiler_system.hpp"
 #include "systems/freelook_system.hpp"
 #include "systems/hint_system.hpp"
@@ -50,15 +51,6 @@ struct StepLedger {
 };
 
 
-struct Celebration {
-    bool      active        { false };
-    double    t             { 0.0 };
-    double    duration      { 3.5 };
-    double    next_burst_t  { 0.0 };
-    Text3dPtr banner;
-};
-
-
 struct StepPlate {
     Plate*      plate;
     std::string tag;
@@ -70,7 +62,6 @@ class GameState : public IState
     GameContext ctx;
     FrameInput  frame_input;
     CarriedLine carried;
-    Celebration celebration;
     EditorWall  editor_wall;
 
     StepLedger               step_ledger;
@@ -106,6 +97,7 @@ class GameState : public IState
     cpputils::unique_ptr<FloorMandelbrot>  floor_sys;
     cpputils::unique_ptr<LevelRunner>      level_runner;
     cpputils::unique_ptr<ModLoader>        mod_loader;
+    cpputils::unique_ptr<CelebrationSystem>celebration_sys;
 
 
     bool on_Pick();
@@ -150,11 +142,6 @@ class GameState : public IState
     void        level_Advance();
     GoalContext goal_ContextBuild() const;
 
-    void celebration_Start();
-    void celebration_Tick(double dt);
-    void celebration_Finish();
-
-
     void projectile_LandOnFloor(ProjectileResult& result);
     void projectile_LandOnWall (ProjectileResult& result);
     void walltext_HandleShootDown(ProjectileResult& result, std::vector<size_t>& to_erase);
diff --git a/src/systems/celebration_system.cpp b/src/systems/celebration_system.cpp
new file mode 100644
index 0000000..080f93e
--- /dev/null
+++ b/src/systems/celebration_system.cpp
@@ -0,0 +1,110 @@
+#include "celebration_system.hpp"
+#include "entities/camera.hpp"
+#include "systems/particle_system.hpp"
+
+void
+CelebrationSystem::start(const Camera& cam, double& glyph_yaw)
+{
+    active_     = true;
+    t_          = 0.0;
+    next_burst_ = 0.0;
+
+    Vec3 banner_pos {
+        cam.pos.x + cam.fwd.x * 10.0,
+        cam.pos.y + cam.fwd.y * 10.0,
+        cam.pos.z + cam.fwd.z * 10.0,
+    };
+
+    if (banner_pos.y < static_cast<double>(-WORLD_BOX_SCALE+1.0f) )
+        banner_pos.y = -WORLD_BOX_SCALE+1.0f;
+
+    banner_ = make_Text3d("Success",
+                         Text3d::depth_floor * 2.0f,
+                         0.012f, banner_pos,
+                         Text3d::color_white);
+    banner_->yaw       = static_cast<float>(glyph_yaw);
+    banner_->body.mode = PhysicsMode::Static;
+}
+
+
+void
+CelebrationSystem::tick(double dt, ParticleSystem& particle_sys)
+{
+    if (!active_) return;
+
+    t_ += dt;
+
+    // Slow banner spin
+    if (banner_)
+        banner_->yaw += static_cast<float>(dt * 0.6);
+
+    // Fireworks bursts on a schedule. We aim for ~7 bursts across the
+    // duration, with slight jitter to avoid mechanical pacing.
+    if (t_ >= next_burst_ &&
+        t_ <  duration_ - 0.3)
+    {
+        // Random position across the play area, biased to upper half so
+        // bursts read as airborne fireworks not floor_sys explosions.
+        const double ws = WORLD_BOX_SCALE;
+        const double margin = 3.0;
+        auto rnd = []() {
+            return static_cast<double>(rand()) / static_cast<double>(RAND_MAX);
+        };
+
+        Vec3 center {
+            (rnd() * 2.0 - 1.0) * (ws - margin),
+            rnd() * (ws - margin),
+            (rnd() * 2.0 - 1.0) * (ws - margin),
+        };
+
+        // Random saturated hue. Convert HSV(h, 1, 1) to RGB cheaply.
+        double h = rnd() * 6.0;
+        int    i = static_cast<int>(h);
+        double f = h - i;
+        float r=0, g=0, b=0;
+        switch (i % 6) {
+            case 0: r=1;     g=(float)f; b=0;          break;
+            case 1: r=(float)(1-f); g=1; b=0;          break;
+            case 2: r=0;     g=1;        b=(float)f;   break;
+            case 3: r=0;     g=(float)(1-f); b=1;      break;
+            case 4: r=(float)f; g=0;     b=1;          break;
+            case 5: r=1;     g=0;        b=(float)(1-f); break;
+        }
+
+        particle_sys.emit_FireworkBurst(center, SDL_FColor{r, g, b, 1.0f}, 80);
+
+        // Next burst in 0.5..1.0s.
+        next_burst_ = t_ + 0.5 + rnd() * 0.5;
+    }
+}
+
+
+void
+CelebrationSystem::finish(ParticleSystem& particle_sys)
+{
+    active_ = false;
+    banner_.reset();
+    particle_sys.clear();
+}
+
+
+bool
+CelebrationSystem::active() const
+{
+    return active_;
+}
+
+
+double
+CelebrationSystem::progress() const
+{
+    return (duration_ > 0.0) ? (t_ / duration_) : 1.0;
+}
+
+
+void
+CelebrationSystem::draw(GlRenderer& gl, const Camera& cam, const Light& light) const
+{
+    if (!active_ || !banner_) return;
+    banner_->draw(gl, cam, light);
+}
diff --git a/src/systems/celebration_system.hpp b/src/systems/celebration_system.hpp
new file mode 100644
index 0000000..36dbe45
--- /dev/null
+++ b/src/systems/celebration_system.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "utils/globals.hpp"
+
+struct Light;
+struct Camera;
+class  ParticleSystem;
+class  GlRenderer;
+
+
+class CelebrationSystem {
+    bool   active_     = false;
+    double t_          = 0,
+           duration_   = 3.5,
+           next_burst_ = 0;
+    Text3dPtr banner_;
+public:
+    void   start(const Camera& cam, double& glyph_yaw);
+    void   tick(double dt, ParticleSystem& particle_sys);
+    void   finish(ParticleSystem& particle_sys);
+    bool   active() const;
+    double progress() const;
+    void   draw(GlRenderer&, const Camera&, const Light&) const;
+};
diff --git a/src/systems/word_ai_system.cpp b/src/systems/word_ai_system.cpp
index 5f8c22a..1b20f43 100644
--- a/src/systems/word_ai_system.cpp
+++ b/src/systems/word_ai_system.cpp
@@ -361,17 +361,17 @@ WordAiSystem::apply_Locomotion(Text3d& w, const Vec3& target_vel, double dt)
     w.pos.z += w.body.vel.z * dt;
 
     // Clamp to world bounds
-    const double ws  = static_cast<double>(world_scale);
-    const double hex = w.shape.half_extents.x;
-    const double hey = w.shape.half_extents.y;
-    const double hez = w.shape.half_extents.z;
-
-    if (w.pos.x - hex < -ws) { w.pos.x = -ws + hex; w.body.vel.x = 0; }
-    if (w.pos.x + hex >  ws) { w.pos.x =  ws - hex; w.body.vel.x = 0; }
-    if (w.pos.y - hey < -ws + 0.5) { w.pos.y = -ws + 0.5 + hey; w.body.vel.y = 0; }
-    if (w.pos.y + hey >  ws) { w.pos.y =  ws - hey; w.body.vel.y = 0; }
-    if (w.pos.z - hez < -ws) { w.pos.z = -ws + hez; w.body.vel.z = 0; }
-    if (w.pos.z + hez >  ws) { w.pos.z =  ws - hez; w.body.vel.z = 0; }
+    const double ws = static_cast<double>(world_scale);
+
+    auto clamp_axis = [](double& p, double& v, double he,
+                         double lo, double hi) {
+        if (p - he < lo) { p = lo + he; v = 0.0; }
+        if (p + he > hi) { p = hi - he; v = 0.0; }
+    };
+
+    clamp_axis(w.pos.x, w.body.vel.x, w.shape.half_extents.x, -ws,       ws);
+    clamp_axis(w.pos.y, w.body.vel.y, w.shape.half_extents.y, -ws + 0.5, ws);
+    clamp_axis(w.pos.z, w.body.vel.z, w.shape.half_extents.z, -ws,       ws);
 }
 
 
diff --git a/src/utils/text_wrap.hpp b/src/utils/text_wrap.hpp
new file mode 100644
index 0000000..53b15af
--- /dev/null
+++ b/src/utils/text_wrap.hpp
@@ -0,0 +1,55 @@
+#pragma once
+#include <string>
+#include <vector>
+
+// ─────────────────────────────────────────────────────────────────────────
+// wrap_Text — break `text` into lines no longer than `max_chars`.
+//
+// Rules (matching the infowall behavior it was extracted from):
+//   - explicit '\n' forces a line break (and can produce an empty line)
+//   - spaces/tabs separate words; a word that would overflow the current
+//     line starts a new line instead of being split
+//   - trailing spaces are trimmed from each line
+//   - a single word longer than max_chars is NOT split — it occupies its
+//     own (over-length) line, same as the original
+// ─────────────────────────────────────────────────────────────────────────
+inline std::vector<std::string>
+wrap_Text(const std::string& text, size_t max_chars)
+{
+    std::vector<std::string> lines;
+    std::string current_line;
+    std::string word;
+
+    auto flush_line = [&]() {
+        while (!current_line.empty() && current_line.back() == ' ')
+            current_line.pop_back();
+        lines.push_back(current_line);   // may be empty (blank line)
+        current_line.clear();
+    };
+
+    auto flush_word = [&]() {
+        if (word.empty()) return;
+        if (current_line.size() + word.size() + (current_line.empty() ? 0 : 1)
+                > max_chars) {
+            flush_line();
+        }
+        if (!current_line.empty()) current_line += ' ';
+        current_line += word;
+        word.clear();
+    };
+
+    for (char c : text) {
+        if (c == '\n') {
+            flush_word();
+            flush_line();
+        } else if (c == ' ' || c == '\t') {
+            flush_word();
+        } else {
+            word += c;
+        }
+    }
+    flush_word();
+    flush_line();
+
+    return lines;
+}