Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / commits / d639034

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

Fix shoot down token from wall With Claude

Christian Immanuel · 2026-06-27 09:21 · d6390342470a9ffedf8bf2b7ce231fa8cde35264

 src/states/game_state.cpp         |  1 +
 src/systems/projectile_system.cpp | 44 +++++++++++++++++++++++++++++--------
 src/systems/projectile_system.hpp |  3 +++
 src/text/handler.cpp              | 46 +++++++++++++++++++++++++--------------
 4 files changed, 69 insertions(+), 25 deletions(-)

diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index 45d1b0c..b96ea91 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -622,6 +622,7 @@ GameState::projectile_LandOnWallText(ProjectileResult& result)
                     Vec3 impact = lines[line_idx].tokens[token_idx]->pos;
                     for (const Text::TokenPtr& word : result.words)
                         if (word) { impact = word->pos; break; }
+                    if (result.use_aim) impact = result.aim_point;
 
                     const Text::Handler::EditorHit hit = handler->classify_Impact(impact);
 
diff --git a/src/systems/projectile_system.cpp b/src/systems/projectile_system.cpp
index 58f7c75..1965cbf 100644
--- a/src/systems/projectile_system.cpp
+++ b/src/systems/projectile_system.cpp
@@ -189,11 +189,14 @@ checkWallTextIntercept(const Projectile& projectile, const Vec3& centroid, doubl
             if (SDL_fabs(aim_y - center_y) > Config::Wall::line_height * 0.5)
                 continue;
 
-            // Horizontal: only shoot the word down if the aim lands in its
-            // central 2/3. Outer 1/3 each side is "between words" → merge.
+            // Horizontal: the aim must land within the token's column span at all.
+            // The fine drop-vs-merge split (central 90% drop, outer 5% each edge
+            // merge) is applied downstream by classify_Impact, so the gate here is
+            // the full span — anything tighter would turn edge aims into "stick new
+            // text" instead of the intended merge.
             const double center_z = (wt.aabb_Min.z + wt.aabb_Max.z) * 0.5;
             const double half_z   = (wt.aabb_Max.z - wt.aabb_Min.z) * 0.5;
-            if (SDL_fabs(aim_z - center_z) > half_z * (2.0 / 3.0))
+            if (SDL_fabs(aim_z - center_z) > half_z)
                 continue;
 
             // In the central band → shoot it down (no projectile-width inflation
@@ -334,10 +337,30 @@ handleWallHit(Projectile& projectile, const Vec3& centroid, Wall::Position hit_p
     }
 
     const bool   editor = (Wall::role_Of(hit_pos) == Wall::Role::Editor);
-    // Aim at where the phrase ACTUALLY is (its current centroid), so the gravity
-    // arc and any prior bounce are respected. The old launch-ray aim ignored both.
-    const double aim_y  = editor ? centroid.y : 0.0;
-    const double aim_z  = editor ? centroid.z : 0.0;
+
+    // Drop/merge is decided from the exact crosshair COLUMN — the launch ray
+    // intersected with the text plane — not the projectile's physical landing or
+    // its mesh hit. The arc, the shooting angle, and any object between the camera
+    // and the wall all corrupt the landing/mesh point; the ray∩plane is pure
+    // geometry and maps cleanly to a column. After a bounce the launch ray is
+    // stale, so fall back to the real landing centroid (keeps Bug 1 fixed).
+    Vec3 decide  = centroid;
+    bool use_aim = false;
+    if (editor && !projectile.collided && SDL_fabs(projectile.dir.x) > 1e-6) {
+        // aim_point lies on the crosshair ray, so (aim_point, dir) IS that ray.
+        // Intersect it with the text plane for the exact aimed column.
+        const Vec3   n       = Wall::inward_Normal(hit_pos);
+        const double plane_x = wallPlaneCoord(hit_pos, ws) + n.x * static_cast<double>(Config::Wall::text_offset);
+        const double t       = (plane_x - projectile.aim_point.x) / projectile.dir.x;
+        if (t > 0.0) {
+            decide  = Vec3{ plane_x,
+                            projectile.aim_point.y + projectile.dir.y * t,
+                            projectile.aim_point.z + projectile.dir.z * t };
+            use_aim = true;
+        }
+    }
+    const double aim_y = editor ? decide.y : 0.0;
+    const double aim_z = editor ? decide.z : 0.0;
 
     size_t hit_idx = 0;
     if (projectile.words.size() == 1 &&
@@ -348,6 +371,8 @@ handleWallHit(Projectile& projectile, const Vec3& centroid, Wall::Position hit_p
         res.wall_pos        = hit_pos;
         res.wall_text_index = hit_idx;
         res.vel             = projectile.vel;
+        res.aim_point       = decide;
+        res.use_aim         = use_aim;
         res.words           = std::move(projectile.words);
         results.push_back(std::move(res));
         return true;
@@ -413,6 +438,7 @@ ProjectileSystem::launch(DynArray<Projectile>& out,
     proj.words         = std::move(words);
     proj.dir           = fwd;
     proj.launch_origin = centroid;
+    proj.aim_point     = aim.point;   // a point on the crosshair ray; rebuilds the ray with dir
     proj.prev_centroid = centroid;
 
     if (aim.track) {
@@ -426,8 +452,8 @@ ProjectileSystem::launch(DynArray<Projectile>& out,
 
 
 void
-ProjectileSystem::update(DynArray<Projectile>& projectiles,
-                         DynArray<ProjectileResult>& results,
+ProjectileSystem::update(DynArray<Projectile>&        projectiles,
+                         DynArray<ProjectileResult>&  results,
                          const DynArray<WallTextHitTest>& wall_text_hits,
                          double dt, bool program_running)
 {
diff --git a/src/systems/projectile_system.hpp b/src/systems/projectile_system.hpp
index c57a3f9..1a6cd58 100644
--- a/src/systems/projectile_system.hpp
+++ b/src/systems/projectile_system.hpp
@@ -31,6 +31,7 @@ struct Projectile
     Vec3 dir{};
     Vec3 prev_centroid{};
     Vec3 launch_origin{};
+    Vec3 aim_point    {};
 
     Vec3 track_local  {};
     Vec3 track_last   {};
@@ -68,8 +69,10 @@ struct ProjectileResult
     DynArray<Text::TokenPtr> words;
     ProjectileLanding   landing = ProjectileLanding::None;
 
+    Vec3   aim_point       {};
     Vec3   vel             {};
     size_t wall_text_index = 0;
+    bool   use_aim         = false;
 
     Wall::Position
            wall_pos        = Wall::Position::NegZ;
diff --git a/src/text/handler.cpp b/src/text/handler.cpp
index e519633..8b18312 100644
--- a/src/text/handler.cpp
+++ b/src/text/handler.cpp
@@ -556,21 +556,28 @@ Handler::classify_Impact(const Vec3& impact) const
 
     if (N == 0) { hit.kind = HitKind::NewLine; hit.row = 0; hit.col = col_From_Z(); return hit; }
 
-    // ── vertical: 80% band owns the row, the 20% straddling each boundary inserts a line ──
-    const double rf   = (_row0_y - impact.y) / lh;
-    const int    i    = static_cast<int>(SDL_lround(rf));
-    const double frac = rf - static_cast<double>(i);
-
-    if (i < 0)  { hit.kind = HitKind::NewLine; hit.row = i;       hit.col = col_From_Z(); return hit; } // prepend
-    if (i >= N) { hit.kind = HitKind::NewLine; hit.row = i;       hit.col = col_From_Z(); return hit; } // append
-    if (SDL_fabs(frac) > 0.4) {
+    // ── vertical: a row is "owned" only within the glyph cap height (≈ an 'A'/'M',
+    //    ±_he_y about the row centre) — NOT the whole line height. Aiming in the gap
+    //    between two rows inserts a new line there; aiming above the first row or
+    //    below the last appends/prepends. This mirrors the uniform cell box used for
+    //    the horizontal hit, so "clearly above/below a token" makes a new line
+    //    instead of dropping it, while the inter-row gap can no longer masquerade as
+    //    a hit on the nearest token. ──
+    const double rf = (_row0_y - impact.y) / lh;
+    const int    i  = static_cast<int>(SDL_lround(rf));
+
+    if (i < 0)  { hit.kind = HitKind::NewLine; hit.row = i; hit.col = col_From_Z(); return hit; } // prepend
+    if (i >= N) { hit.kind = HitKind::NewLine; hit.row = i; hit.col = col_From_Z(); return hit; } // append
+
+    const double row_y = _row0_y - static_cast<double>(i) * lh;
+    if (SDL_fabs(impact.y - row_y) > _he_y) {            // in the inter-row gap → new line
         hit.kind = HitKind::NewLine;
-        hit.row  = (frac > 0.0) ? i + 1 : i;          // gap between rows → new line there
+        hit.row  = (impact.y < row_y) ? i + 1 : i;       // below this row → line after it
         hit.col  = col_From_Z();
         return hit;
     }
 
-    // ── horizontal on row i: re-chunk the buffer line and apply 80/20 per token span ──
+    // ── horizontal on row i: re-chunk the buffer line and apply 90/10 per token span ──
     hit.row = i; hit.line_idx = static_cast<size_t>(i);
     const std::string& s = blines[static_cast<size_t>(i)];
     const double vcf = (_code_left_z - impact.z) / _char_w;   // fractional visual column
@@ -609,11 +616,11 @@ Handler::classify_Impact(const Vec3& impact) const
         if (vcf >= vc && vcf < vc + vs) {
             if (ws) { hit.kind = HitKind::Insert; hit.col = b1; return hit; }  // split: word after the space
             const double center = vc + vs * 0.5;
-            if (SDL_fabs(vcf - center) <= 0.4 * vs) {                          // central 80% → drop
+            if (SDL_fabs(vcf - center) <= 0.45 * vs) {                         // central 90% → drop
                 hit.kind = HitKind::DropToken; hit.line_idx = static_cast<size_t>(i); hit.token_idx = k;
                 return hit;
             }
-            hit.kind = HitKind::Insert;                                        // outer 20% → beside
+            hit.kind = HitKind::Insert;                                        // outer 5% each side → beside
             hit.col  = (vcf < center) ? b0 : b1;
             return hit;
         }
@@ -955,10 +962,17 @@ Handler::relayout_Mono()
             bt.t->pos   = _transform.position;
             bt.t->pos.y = row_y;
             bt.t->pos.z = left_z - tok_w * 0.5;
-            if (bt.t->is_Whitespace()) {
-                bt.t->half_extent        = (float)(tok_w * 0.5);
-                bt.t->shape.half_extents.x = tok_w * 0.5;
-            }
+            // Uniform monospace cell for collision/aim: EVERY token (not just
+            // whitespace) gets a cell-width × cap-height box. The projectile
+            // intercept tests shape.half_extents (via aabb_Min/Max), so any token
+            // keeping its ink extents ("," tiny, "M" wide, "g" tall with descender)
+            // made the aim AABBs drift off the visible monospace columns and rows.
+            // Width = one cell; height = the cap height of an 'A'/'M' (_he_y), the
+            // same metric classify_Impact uses for its vertical row ownership.
+            bt.t->half_extent          = (float)(tok_w * 0.5);
+            bt.t->half_extent_y        = (float)_he_y;
+            bt.t->shape.half_extents.x = tok_w * 0.5;
+            bt.t->shape.half_extents.y = _he_y;
             ln.tokens.push_back(std::move(bt.t));
         }