Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / commits / 1131f15

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

Better shoot between lines With Claude

Christian Immanuel · 2026-06-06 10:52 · 1131f15f227705d93eddba35a63d1bdfaf0790c0

 src/c_token/token.hpp               |  10 +-
 src/c_token/token_db.hpp            |  11 +-
 src/core/game.hpp                   |  15 ++-
 src/entities/camera.hpp             |   9 +-
 src/entities/game_object.hpp        |  45 +++----
 src/entities/player.cpp             |  13 +-
 src/entities/player.hpp             |  17 +--
 src/entities/pult.hpp               |   2 +-
 src/entities/world.hpp              |  12 +-
 src/levels/goal.hpp                 |  69 ++--------
 src/levels/level.hpp                |  27 ++--
 src/levels/level/char_copy.cpp      |  49 +++----
 src/levels/level/sandbox.cpp        |  10 +-
 src/levels/level/tutorial.cpp       | 256 ++++++++++++++++++------------------
 src/levels/level_check.hpp          |   8 +-
 src/levels/scene.hpp                |  44 +++----
 src/rendering/floor_mandelbrot.hpp  |   5 -
 src/states/game_state.cpp           |   8 +-
 src/states/game_state.hpp           |  97 +++++++-------
 src/states/menu_state.hpp           |  12 +-
 src/systems/aim.hpp                 |  24 +---
 src/systems/carry_system.hpp        |  24 ++--
 src/systems/celebration_system.hpp  |  10 +-
 src/systems/collision.hpp           |   2 +-
 src/systems/compiler_system.hpp     |   6 +-
 src/systems/dyn_loader_system.hpp   |  20 +--
 src/systems/hint_system.hpp         |   2 +-
 src/systems/input_system.hpp        |  10 +-
 src/systems/level_director.cpp      |   2 +-
 src/systems/level_director.hpp      |  16 ++-
 src/systems/level_runner_system.cpp |   2 +-
 src/systems/level_runner_system.hpp |  28 ++--
 src/systems/particle_system.hpp     |  26 ++--
 src/systems/physics_system.hpp      |   7 +-
 src/systems/pick_system.hpp         |  11 +-
 src/systems/plate_system.cpp        |   8 --
 src/systems/plate_system.hpp        |   1 -
 src/systems/program_system.cpp      |   2 +-
 src/systems/program_system.hpp      |  21 ++-
 src/systems/projectile_system.cpp   |  29 +++-
 src/systems/projectile_system.hpp   |  75 ++++++-----
 src/systems/wall_connection.hpp     |  23 ++--
 src/systems/word_ai_system.hpp      |  28 ++--
 src/systems/word_anim.hpp           |  21 +--
 src/text_3d/text_label.hpp          |  62 ++++-----
 src/utils/carry_math.hpp            |  19 ++-
 src/utils/config.hpp                |   1 +
 src/utils/frame_profiler.hpp        |   8 +-
 src/utils/math.hpp                  |  12 +-
 src/wall/editor_wall.cpp            | 186 ++++++++++++++++++++++----
 src/wall/editor_wall.hpp            | 114 +++++-----------
 src/wall/text_wall.hpp              |  13 +-
 src/wall/wall_label.hpp             |  31 ++---
 src/wall/wall_surfaces.hpp          |  14 +-
 54 files changed, 803 insertions(+), 774 deletions(-)

diff --git a/src/c_token/token.hpp b/src/c_token/token.hpp
index 924621f..88ea12b 100644
--- a/src/c_token/token.hpp
+++ b/src/c_token/token.hpp
@@ -4,18 +4,18 @@
 #include <string>
 
 enum class TokenType : uint8_t {
-    Keyword,
-    Type,
+    Delimiter,
     Identifier,
+    Keyword,
     Literal,
     Operator,
-    Delimiter,
     Preprocessor,
+    Type,
 };
 
 struct Token {
     std::string text;
     TokenType   type;
-    std::string short_desc;   // shown on info wall: "Integer type"
-    std::string long_desc;    // shown on info wall: "Used to declare integer variables"
+    std::string short_desc;
+    std::string long_desc;
 };
diff --git a/src/c_token/token_db.hpp b/src/c_token/token_db.hpp
index 12b8abd..e0a0355 100644
--- a/src/c_token/token_db.hpp
+++ b/src/c_token/token_db.hpp
@@ -5,12 +5,11 @@
 
 namespace TokenDB
 {
-    const Token*           find(const std::string& text);
-    const Token*           find_OrSynthesize(const std::string& text);
-    DynArray<const Token*> get_AllOfType(TokenType type);
-    const char*            get_TypeName(TokenType type);
+    bool         known(const std::string& text);
+    const Token* find (const std::string& text);
+    const Token* find_OrSynthesize(const std::string& text);
 
+    const char*            get_TypeName (TokenType type);
+    DynArray<const Token*> get_AllOfType(TokenType type);
     DynArray<std::string>  get_TestArrayAllKeywords();
-
-    bool                   known(const std::string& text);
 }
diff --git a/src/core/game.hpp b/src/core/game.hpp
index 0845c02..7f5bb17 100644
--- a/src/core/game.hpp
+++ b/src/core/game.hpp
@@ -11,17 +11,18 @@
 
 class Game
 {
-    GlRendererPtr _gl_renderer{};
     SDL_Window*   _window     {};
+    GlRendererPtr _gl_renderer{};
 
     cpputils::unique_ptr<IState> m_state;
 public:
     ~Game();
-    bool init();
-    void change_State(cpputils::unique_ptr<IState> next);
+    void change_State (cpputils::unique_ptr<IState> next);
+
+    void handle_Event (const SDL_Event& event);
+    bool init   ();
+    void update (double dt_ns);
+    void render ();
 
-    void handle_Event(const SDL_Event& event);
-    void update(double dt_ns);
-    void render();
-    void shutdown();
+    void shutdown ();
 };
diff --git a/src/entities/camera.hpp b/src/entities/camera.hpp
index fae6d2d..aa8f82b 100644
--- a/src/entities/camera.hpp
+++ b/src/entities/camera.hpp
@@ -7,13 +7,14 @@
 #include "entities/player.hpp"
 
 struct Camera {
-    Vec3 pos{};
+    int32_t  pitch  {};
     uint32_t yaw    {};
     double   yaw_r  {};
-    int32_t  pitch  {};
     double   pitch_r{};
-    Vec3 fwd        {};
-    Vec3 right      {};
+
+    Vec3 pos  {};
+    Vec3 fwd  {};
+    Vec3 right{};
 
     void syncFromPlayer(const Player& player)
     {
diff --git a/src/entities/game_object.hpp b/src/entities/game_object.hpp
index 8047f80..33bc68c 100644
--- a/src/entities/game_object.hpp
+++ b/src/entities/game_object.hpp
@@ -16,12 +16,12 @@ enum class ObjectType : uint8_t {
 
 
 enum class PhysicsMode : uint8_t {
-    Static,
-    Dynamic,
-    Hover,
     AiHover,
     Carried,
+    Dynamic,
+    Hover,
     Projectile,
+    Static,
 };
 
 
@@ -31,25 +31,25 @@ struct CollisionShape {
 
 
 struct RigidBody {
-    Vec3        vel        {};
-    Vec3        force      {};
-    double      mass       {1.0};
-    double      restitution{0.3};
-    double      friction   {0.8};
+    bool        hover_disturbed{false};
+    double      friction    {0.8};
+    double      hover_height{0.0};
+    double      hover_phase {0.0};
+    double      mass        {1.0};
+    double      restitution {0.3};
+    Vec3        vel  {};
+    Vec3        force{};
 
     PhysicsMode mode{PhysicsMode::Hover};
 
-    double      hover_phase    {0.0};
-    double      hover_height   {0.0};
-    bool        hover_disturbed{false};
 
     RigidBody(PhysicsMode m = PhysicsMode::Hover,
               double      mass_        = 1.0,
               double      restitution_ = 0.3,
               double      friction_    = 0.8
-            ) : mass(mass_),
+            ) : friction(friction_),
+                mass(mass_),
                 restitution(restitution_),
-                friction(friction_),
                 mode(m) {}
 
     void apply_Force(const Vec3& f) {
@@ -80,23 +80,24 @@ struct RigidBody {
 
 
 struct GameObject {
-    Vec3           pos          {};
-    float          yaw          {0.0f};
-    ObjectType     type         {ObjectType::Text};
+    bool  carriable    {true};
+    bool  interactable {true};
+    bool  solid        {true};
+
+    float yaw {0.0f};
+    Vec3  pos {};
+
+    ObjectType     type{ObjectType::Text};
     CollisionShape shape;
     RigidBody      body;
 
-    bool           carriable    {true};
-    bool           interactable {true};
-    bool           solid        {true};
-
     GameObject() {}
 
     // c = carriable, a = active, i = interactable, s = solid
     GameObject(Vec3 p, ObjectType t, CollisionShape sh, RigidBody b,
                bool c = true, bool i = true, bool s = true)
-        : pos(p), type(t), shape(sh), body(b),
-          carriable(c), interactable(i), solid(s) {}
+        : carriable(c), interactable(i), solid(s),
+          pos(p), type(t), shape(sh), body(b) {}
 
     virtual ~GameObject() = default;
     virtual void draw(GlRenderer&, const Camera&, const Light&) const = 0;
diff --git a/src/entities/player.cpp b/src/entities/player.cpp
index 1eda670..6d4f7c6 100644
--- a/src/entities/player.cpp
+++ b/src/entities/player.cpp
@@ -3,13 +3,12 @@
 
 
 Player::Player()
-    : pos{8.0, 0.0, 8.0},
-      vel{0.0, 0.0, 0.0},
-      yaw(0x20000000),
-      pitch(-0x08000000),
-      radius(0.5f),
-      height(1.5f)
-{}
+       : pitch (-0x08000000),
+         yaw   (0x20000000),
+         radius(0.5f),
+         height(1.5f),
+         pos   {8.0, 0.0, 8.0},
+         vel   {0.0, 0.0, 0.0} {}
 
 void
 Player::apply_Collision(const PlayerCollision& col)
diff --git a/src/entities/player.hpp b/src/entities/player.hpp
index fcadfef..0411920 100644
--- a/src/entities/player.hpp
+++ b/src/entities/player.hpp
@@ -9,11 +9,11 @@
 
 struct PlayerCollision
 {
-    Vec3 pos   {};
-    Vec3 vel   {};
     bool hit_x {};
     bool hit_y {};
     bool hit_z {};
+    Vec3 pos   {};
+    Vec3 vel   {};
 };
 
 
@@ -24,15 +24,15 @@ struct Player
 public:
     Player();
 
-    Vec3     pos      {};
-    Vec3     prev_pos {};
-    Vec3     vel      {};
-
-    uint32_t yaw      {};
+    bool     on_ground{false};
     int32_t  pitch    {};
+    uint32_t yaw      {};
     float    radius   {0.5f};
     float    height   {1.5f};
-    bool     on_ground{false};
+
+    Vec3     pos      {};
+    Vec3     prev_pos {};
+    Vec3     vel      {};
 
     void   compute_Movement(const FrameInput& input);
     void   add_Look        (int dx, int dy);
@@ -41,3 +41,4 @@ public:
     double get_AccZ        () const { return _acc_z; }
     void   respawn         (const Vec3& spawn_pos);
 };
+
diff --git a/src/entities/pult.hpp b/src/entities/pult.hpp
index 042947a..5983119 100644
--- a/src/entities/pult.hpp
+++ b/src/entities/pult.hpp
@@ -35,8 +35,8 @@ struct PultButton final : public SolidBox
     PultButton(const std::string& lbl, PultAction act, Vec3 world_pos);
 
     void press();
-    void tick(double dt);
 
+    void tick(double dt);
     void draw(GlRenderer& gl, const Camera& cam, const Light& light) const override;
 };
 
diff --git a/src/entities/world.hpp b/src/entities/world.hpp
index f62aca0..f244aef 100644
--- a/src/entities/world.hpp
+++ b/src/entities/world.hpp
@@ -21,19 +21,17 @@ public:
     World();
 
     PlayerCollision
-    resolve_Player(const Player& player) const;
+         resolve_Player(const Player& player) const;
 
-    void
-    draw(GlRenderer& gl, const Camera& camera, const Light& light) const;
+    void draw(GlRenderer& gl, const Camera& camera, const Light& light) const;
 
-    Vec3
-    get_SpawnPoint() const;
+    Vec3 get_SpawnPoint() const;
 
     template <typename T = float>
     inline static T
-    get_FloorY()   { return static_cast<T>(-ConfigGame::world_box_scale); }
+         get_FloorY()   { return static_cast<T>(-ConfigGame::world_box_scale); }
 
     template <typename T = float>
     inline static T
-    get_BoxScale() { return static_cast<T>(ConfigGame::world_box_scale); }
+         get_BoxScale() { return static_cast<T>(ConfigGame::world_box_scale); }
 };
diff --git a/src/levels/goal.hpp b/src/levels/goal.hpp
index 1ff6b80..46e458e 100644
--- a/src/levels/goal.hpp
+++ b/src/levels/goal.hpp
@@ -3,53 +3,19 @@
 #include <string>
 #include <variant>
 
-#include "entities/pult.hpp"   // for PultAction
+#include "entities/pult.hpp"
 #include "utils/globals.hpp"
 
 
-// ─────────────────────────────────────────────────────────────────────────
-// Goal: declarative description of "what makes the current step complete".
-//
-// Each Level::Step owns one Goal. Each frame, the level runner builds a
-// GoalContext from the live game state and asks goal_Satisfied(goal, ctx).
-// When that returns true (and the settle timer has elapsed), the step
-// advances.
-//
-// Action handlers (on_Pick, on_Compile, ...) DO NOT call into the level
-// system. They do their own job. The level system pulls.
-// ─────────────────────────────────────────────────────────────────────────
+struct ReachPlate    { std::string tag; };
+struct PressButton   { PultAction action; };
+struct CollectWord   { std::string text; };
+struct DroppedWord   {};
+struct ShotWord      {};
+struct CompilePattern{ std::string pattern; };
+struct AiWordDefeated{ std::string text; };
 
 
-// Player is standing on a plate. Empty tag = any plate this step.
-// Non-empty tag matches PlacedPlate::tag in the scene definition.
-struct ReachPlate { std::string tag; };
-
-
-// A specific pult button has been pressed this step.
-struct PressButton { PultAction action; };
-
-
-// Player is carrying (or is in the process of picking up) a word
-// with this exact text.
-struct CollectWord { std::string text; };
-
-
-// Player dropped at least one word this step (Q key).
-struct DroppedWord {};
-
-
-// Player shot at least one word this step (left click / shoot-all).
-struct ShotWord {};
-
-
-// Compiler reported success AND the editor wall matches the pattern.
-// Replaces the old AdvanceTrigger::Compile + expected_pattern combo.
-struct CompilePattern { std::string pattern; };
-
-struct AiWordDefeated { std::string text; };
-
-
-// Forward declare for recursive variant.
 struct AnyOf;
 struct AllOf;
 
@@ -75,29 +41,18 @@ struct AnyOf { DynArray<Goal> options; };
 struct AllOf { DynArray<Goal> options; };
 
 
-// ─────────────────────────────────────────────────────────────────────────
-// GoalContext: read-only snapshot of game facts a goal might query.
-//
-// Built fresh each frame in GameState::update() right before the goal
-// check. Anything a goal needs to look at MUST appear here — goals never
-// reach into GameState directly.
-// ─────────────────────────────────────────────────────────────────────────
 struct GoalContext {
     DynArray<std::string> active_plate_tags;
     DynArray<PultAction>  buttons_pressed_this_step;
     DynArray<std::string> picked_up_this_step;
     DynArray<std::string> ai_words_defeated_this_step;
 
-    // Action flags — cleared on step advance.
-    bool                     dropped_this_step    {false};
-    bool                     shot_this_step       {false};
-    bool                     compile_ok_this_step {false};
+    bool dropped_this_step    {false};
+    bool shot_this_step       {false};
+    bool compile_ok_this_step {false};
 
-    // Normalized editor text — only populated when compile_ok_this_step is true
-    // (CompilePattern is the only goal that needs it).
-    std::string              editor_normalized;
+    std::string editor_normalized;
 };
 
 
-// Evaluate a goal against a context. Pure function, no side effects.
 bool goal_Satisfied(const Goal& g, const GoalContext& ctx);
diff --git a/src/levels/level.hpp b/src/levels/level.hpp
index 864a5d6..eb9dcc7 100644
--- a/src/levels/level.hpp
+++ b/src/levels/level.hpp
@@ -9,8 +9,6 @@
 
 namespace Level
 {
-
-
     struct TokenGrant {
         DynArray<std::string> choices;
 
@@ -22,33 +20,38 @@ namespace Level
 
 
     struct Step {
-        std::string           hint;
-        std::string           details;
+        bool  reset_carry  = false;
+        int   max_ai_words = 5;
+
+        Scene scene;
+        Goal  goal = AllOf{};
+
+        std::string hint;
+        std::string details;
+
         DynArray<TokenGrant>  grants;
-        Scene                 scene;
-        bool                  reset_carry  = false;
-        Goal                  goal         = AllOf{};
-        int                   max_ai_words = 5;
     };
 
 
     struct Data {
+        bool dynamic_lib = false;
+
         std::string name;
         std::string desc;
-        bool        dynamic_lib = false;
-        std::string source_file = {};
+        std::string source_file{};
+
         DynArray<Step> steps;
     };
 
-    extern Data build_Tutorial();
     extern Data build_CharCopy();
     extern Data build_Sandbox ();
+    extern Data build_Tutorial();
 
     inline const DynArray<Data> levels =
     {
-        build_Tutorial(),
         build_CharCopy(),
         build_Sandbox (),
+        build_Tutorial(),
     };
 
     inline const Data& get_Level(size_t i)
diff --git a/src/levels/level/char_copy.cpp b/src/levels/level/char_copy.cpp
index e164e33..db2ff62 100644
--- a/src/levels/level/char_copy.cpp
+++ b/src/levels/level/char_copy.cpp
@@ -6,11 +6,16 @@ Level::Data
 Level::build_CharCopy()
 {
     return {
+        .dynamic_lib = false,
         .name = "Char Copy",
         .desc = "Implement a function that copies a char array and prints the result.",
         .steps = {
             // ── Step 0: main scaffold ─────────────────────────────────────
             {
+                .reset_carry  = false,
+                .max_ai_words = 3,
+                .scene        = {},
+                .goal         = CompilePattern{ "int main ( void ) { return 0 ; }" },
                 .hint    = "Build a working main()",
                 .details = R"(C programs start at main().
 
@@ -35,13 +40,13 @@ Grab tokens and arrange them on the editor wall.)",
                     Level::TokenGrant::give(";"),
                     Level::TokenGrant::give("}"),
                 },
-                .scene        = {},
-                .reset_carry  = false,
-                .goal         = CompilePattern{ "int main ( void ) { return 0 ; }" },
-                .max_ai_words = 3,
             },
             // ── Step 1: copy_str signature ────────────────────────────────
             {
+                .reset_carry  = false,
+                .max_ai_words = 3,
+                .scene        = {},
+                .goal         = CompilePattern{ "void copy_str ( char src [ ] , char dst [ ] ) { }" },
                 .hint    = "Declare the copy_str function",
                 .details = R"(Now add a function above main().
 
@@ -74,13 +79,13 @@ Place this BEFORE main.)",
                     Level::TokenGrant::give("{"),
                     Level::TokenGrant::give("}"),
                 },
-                .scene        = {},
-                .reset_carry  = false,
-                .goal         = CompilePattern{ "void copy_str ( char src [ ] , char dst [ ] ) { }" },
-                .max_ai_words = 3,
             },
             // ── Step 2: loop counter ──────────────────────────────────────
             {
+                .reset_carry  = false,
+                .max_ai_words = 3,
+                .scene        = {},
+                .goal         = CompilePattern{ "void copy_str ( char src [ ] , char dst [ ] ) { int i ; }" },
                 .hint    = "Add a loop counter inside copy_str",
                 .details = R"(Inside copy_str's body, declare an integer
 to track our position in the array.
@@ -95,13 +100,13 @@ of copy_str.)",
                     Level::TokenGrant::give("i"),
                     Level::TokenGrant::give(";"),
                 },
-                .scene        = {},
-                .reset_carry  = false,
-                .goal         = CompilePattern{ "void copy_str ( char src [ ] , char dst [ ] ) { int i ; }" },
-                .max_ai_words = 3,
             },
             // ── Step 3: for loop scaffold ─────────────────────────────────
             {
+                .reset_carry  = false,
+                .max_ai_words = 3,
+                .scene        = {},
+                .goal         = CompilePattern{ "for ( i = 0 ; i < strlen ( src ) ; i ++ ) { }" },
                 .hint    = "Loop through src",
                 .details = R"(Add a for loop after 'int i;'.
 
@@ -121,13 +126,13 @@ This is the loop scaffold only — the body comes next.)",
                     Level::TokenGrant::give("i"), Level::TokenGrant::give("++"), Level::TokenGrant::give(")"),
                     Level::TokenGrant::give("{"), Level::TokenGrant::give("}"),
                 },
-                .scene        = {},
-                .reset_carry  = false,
-                .goal         = CompilePattern{ "for ( i = 0 ; i < strlen ( src ) ; i ++ ) { }" },
-                .max_ai_words = 3,
             },
             // ── Step 4: copy the character ────────────────────────────────
             {
+                .reset_carry  = false,
+                .max_ai_words = 3,
+                .scene        = {},
+                .goal         = CompilePattern{ "dst [ i ] = src [ i ] ;" },
                 .hint    = "Copy one character per iteration",
                 .details = R"(Inside the for loop body, copy src[i] into dst[i].
 
@@ -142,13 +147,13 @@ Goal (inside the for loop):
                     Level::TokenGrant::give("src"), Level::TokenGrant::give("["), Level::TokenGrant::give("i"), Level::TokenGrant::give("]"),
                     Level::TokenGrant::give(";"),
                 },
-                .scene        = {},
-                .reset_carry  = false,
-                .goal         = CompilePattern{ "dst [ i ] = src [ i ] ;" },
-                .max_ai_words = 3,
             },
             // ── Step 5: call copy_str and print ──────────────────────────
             {
+                .reset_carry  = false,
+                .max_ai_words = 3,
+                .scene        = {},
+                .goal         = CompilePattern{ "char dst [ 100 ] ; copy_str ( \"hello\" , dst ) ; printf ( \"%s\" , dst ) ;" },
                 .hint    = "Call copy_str and print the result",
                 .details = R"(In main(), do three things before 'return 0;':
 
@@ -174,10 +179,6 @@ Place all three lines BEFORE the existing return.)",
                     Level::TokenGrant::give(","), Level::TokenGrant::give("dst"),
                     Level::TokenGrant::give(")"), Level::TokenGrant::give(";"),
                 },
-                .scene        = {},
-                .reset_carry  = false,
-                .goal         = CompilePattern{ "char dst [ 100 ] ; copy_str ( \"hello\" , dst ) ; printf ( \"%s\" , dst ) ;" },
-                .max_ai_words = 3,
             },
         },
     };
diff --git a/src/levels/level/sandbox.cpp b/src/levels/level/sandbox.cpp
index 15a65be..32558b4 100644
--- a/src/levels/level/sandbox.cpp
+++ b/src/levels/level/sandbox.cpp
@@ -6,12 +6,16 @@ Level::Data
 Level::build_Sandbox()
 {
     return {
+        .dynamic_lib = true,
         .name = "Sandbox",
         .desc = "Build the world yourself: compile a library that spawns objects.",
-        .dynamic_lib = true,
         .source_file = "assets/sandbox.c",
         .steps = {
             {
+                .reset_carry  = true,
+                .max_ai_words = 0,
+                .scene        = {},
+                .goal         = AllOf{},
                 .hint    = "Spawn a box with box(x,y,z, hx,hy,hz);",
                 .details = R"(This level IS the code you write.
 
@@ -27,10 +31,6 @@ Build something like:
 
 Then press the Compile pult. Edit and recompile freely.)",
                 .grants = { },
-                .scene        = {},
-                .reset_carry  = true,
-                .goal         = AllOf{},
-                .max_ai_words = 0,
             },
         },
     };
diff --git a/src/levels/level/tutorial.cpp b/src/levels/level/tutorial.cpp
index 973b690..4603f63 100644
--- a/src/levels/level/tutorial.cpp
+++ b/src/levels/level/tutorial.cpp
@@ -12,6 +12,21 @@ Level::build_Tutorial()
         .steps = {
 
             {
+                .reset_carry  = true,
+                .max_ai_words = 0,
+                .scene   = Scene{
+                    .ai_seeds  = {},
+                    .plates = {
+                        { .pos = { 8.6, World::get_FloorY<double>() + 2.2, 0.0 }, .tag = "" },
+                    },
+                    .platforms = {
+                        { .pos = { 5.0, World::get_FloorY<double>() + 0.4, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
+                        { .pos = { 6.8, World::get_FloorY<double>() + 1.1, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
+                        { .pos = { 8.6, World::get_FloorY<double>() + 1.8, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
+                    },
+                    .words = {},
+                },
+                .goal    = ReachPlate{ "" },
                 .hint    = "Jump up the steps to the plate",
                 .details = R"(Press SPACE to jump.
 
@@ -22,24 +37,22 @@ Jump with SPACE.
 
 Jump on the plate on top of the platform. )",
                 .grants  = {},
+            },
+
+            {
+                .reset_carry  = true,
+                .max_ai_words = 0,
                 .scene   = Scene{
-                    .words = {},
+                    .ai_seeds  = {},
                     .plates = {
-                        { .pos = { 8.6, World::get_FloorY<double>() + 2.2, 0.0 }, .tag = "" },
+                        { .pos = { 0.0, World::get_FloorY<double>() + 1.2, 6.0 }, .tag = "" },
                     },
                     .platforms = {
-                        { .pos = { 5.0, World::get_FloorY<double>() + 0.4, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
-                        { .pos = { 6.8, World::get_FloorY<double>() + 1.1, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
-                        { .pos = { 8.6, World::get_FloorY<double>() + 1.8, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
+                        { .pos = { 0.0, World::get_FloorY<double>() + 0.6, 6.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
                     },
-                    .ai_seeds  = {},
+                    .words  = {},
                 },
-                .reset_carry  = true,
                 .goal         = ReachPlate{ "" },
-                .max_ai_words = 0,
-            },
-
-            {
                 .hint    = "Look at each wall, then jump onto the plate",
                 .details = R"(Move the mouse to look around.
 
@@ -50,22 +63,20 @@ Four walls surround you:
   - Info:     this wall, with hints
 )",
                 .grants  = {},
-                .scene   = Scene{
-                    .words  = {},
-                    .plates = {
-                        { .pos = { 0.0, World::get_FloorY<double>() + 1.2, 6.0 }, .tag = "" },
-                    },
-                    .platforms = {
-                        { .pos = { 0.0, World::get_FloorY<double>() + 0.6, 6.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
-                    },
-                    .ai_seeds  = {},
-                },
-                .reset_carry  = true,
-                .goal         = ReachPlate{ "" },
-                .max_ai_words = 0,
             },
 
             {
+                .reset_carry  = true,
+                .max_ai_words = 0,
+                .scene   = Scene{
+                    .ai_seeds  = {},
+                    .plates    = {},
+                    .platforms = {},
+                    .words = {
+                        { .pos = { 5.0, World::get_FloorY<double>() + 1.5, 0.0 }, .text = "hello" },
+                    },
+                },
+                .goal    = CollectWord{ "hello" },
                 .hint    = "Walk into the floating word and press 'e'",
                 .details = R"(Words float in the air.
 
@@ -75,59 +86,59 @@ Picked words trail in front of you as you move.
 > hold SHIFT to free-look without turning your body.")
 )",
                 .grants  = {},
-                .scene   = Scene{
-                    .words = {
-                        { .text = "hello", .pos = { 5.0, World::get_FloorY<double>() + 1.5, 0.0 } },
-                    },
-                    .plates    = {},
-                    .platforms = {},
-                    .ai_seeds  = {},
-                },
-                .reset_carry  = true,
-                .goal         = CollectWord{ "hello" },
-                .max_ai_words = 0,
             },
 
             {
-                .hint    = "Keep 'e' pressed and pick up all words.",
-                .details = R"(You can carry several words at once.
-
-Collect "press", "any" and "key" with 'e'.
-They queue up in the order you grab them.)",
-                .grants  = {},
+                .reset_carry  = false,
+                .max_ai_words = 0,
                 .scene   = Scene{
-                    .words = {
-                        { .text = "keep",    .pos = {  5.0, World::get_FloorY<double>() + 1.5, -3.0 } },
-                        { .text = "e",       .pos = {  0.0, World::get_FloorY<double>() + 1.5, -3.0 } },
-                        { .text = "pressed", .pos = { -5.0, World::get_FloorY<double>() + 1.5, -3.0 } },
-                    },
+                    .ai_seeds  = {},
                     .plates    = {},
                     .platforms = {},
-                    .ai_seeds  = {},
+                    .words = {
+                        { .pos = {  5.0, World::get_FloorY<double>() + 1.5, -3.0 }, .text = "keep",    },
+                        { .pos = {  0.0, World::get_FloorY<double>() + 1.5, -3.0 }, .text = "e",       },
+                        { .pos = { -5.0, World::get_FloorY<double>() + 1.5, -3.0 }, .text = "pressed", },
+                    },
                 },
-                .reset_carry  = false,
                 .goal = AllOf{{
                     CollectWord{ "keep" },
                     CollectWord{ "e"    },
                     CollectWord{ "pressed" },
                 }},
-                .max_ai_words = 0,
+                .hint    = "Keep 'e' pressed and pick up all words.",
+                .details = R"(You can carry several words at once.
+
+Collect "press", "any" and "key" with 'e'.
+They queue up in the order you grab them.)",
+                .grants  = {},
             },
 
             {
+                .reset_carry  = false,
+                .max_ai_words = 0,
+                .scene   = {},
+                .goal    = DroppedWord{},
                 .hint    = "Press 'q' to drop a word",
                 .details = R"('q' drops the most recently picked word.
 
 The dropped word falls to the floor — handy for
 putting back something you grabbed by mistake.)",
                 .grants  = {},
-                .scene   = {},
-                .reset_carry  = false,
-                .goal         = DroppedWord{},
-                .max_ai_words = 0,
             },
 
             {
+                .reset_carry  = false,
+                .max_ai_words = 0,
+                .scene   = {
+                    .words = {
+                        { .pos = {  5.0, World::get_FloorY<double>() + 1.5, -3.0 }, .text = "keep"     },
+                        { .pos = {  3.0, World::get_FloorY<double>() + 1.5, -3.0 }, .text = "left"     },
+                        { .pos = {  0.0, World::get_FloorY<double>() + 1.5, -3.0 }, .text = "click"    },
+                        { .pos = {  2.0, World::get_FloorY<double>() + 1.5, -3.0 }, .text = "pressed"  },
+                    },
+                },
+                .goal    = ShotWord{},
                 .hint    = "LEFT CLICK to throw a word (you can keep it pressed)",
                 .details = R"(Left click throws the word at the front of your line.
 
@@ -135,69 +146,62 @@ Aim with the crosshair and throw — any direction is fine.
 
 Carrying several? Tap F to throw the WHOLE line at once.)",
                 .grants  = {},
+            },
+            {
+                .reset_carry  = false,
+                .max_ai_words = 0,
                 .scene   = {
                     .words = {
-                        { .text = "keep",    .pos = {  5.0, World::get_FloorY<double>() + 1.5, -3.0 } },
-                        { .text = "left",    .pos = {  3.0, World::get_FloorY<double>() + 1.5, -3.0 } },
-                        { .text = "click",   .pos = {  0.0, World::get_FloorY<double>() + 1.5, -3.0 } },
-                        { .text = "pressed", .pos = {  2.0, World::get_FloorY<double>() + 1.5, -3.0 } },
+                        { .pos = {  1.0, World::get_FloorY<double>() + 1.5, -3.0 }, .text = "f",    },
+                        { .pos = {  2.0, World::get_FloorY<double>() + 1.5, -2.0 }, .text = "for",  },
+                        { .pos = {  0.0, World::get_FloorY<double>() + 1.5, -3.0 }, .text = "flush",},
                     },
                 },
-                .reset_carry  = false,
-                .goal         = ShotWord{},
-                .max_ai_words = 0,
-            },
-            {
+                .goal    = ShotWord{},
                 .hint    = "Press 'f' to flush all carried words",
                 .details = R"(Bla blub
 ...)",
                 .grants  = {},
-                .scene   = {
-                    .words = {
-                        { .text = "f",    .pos = {  1.0, World::get_FloorY<double>() + 1.5, -3.0 } },
-                        { .text = "for",  .pos = {  2.0, World::get_FloorY<double>() + 1.5, -2.0 } },
-                        { .text = "flush",.pos = {  0.0, World::get_FloorY<double>() + 1.5, -3.0 } },
-                    },
-                },
-                .reset_carry  = false,
-                .goal         = ShotWord{},
-                .max_ai_words = 0,
             },
 
             {
-                .hint    = "Grab the ammo, then shoot the red word",
-                .details = R"(Some words turn red and chase you to steal what
-you carry.
-
-Pick up "ammo", then knock out the red "danger"
-word by throwing at it with LEFT CLICK before it
-reaches you.)",
-                .grants  = {},
+                .reset_carry = true,
+                .max_ai_words = 0,
                 .scene   = Scene{
-                    .words = {
-                        { .text = "ammo", .pos = { 6.0, World::get_FloorY<double>() + 1.5, 6.0 } },
-                        { .text = "ammo", .pos = { 3.0, World::get_FloorY<double>() + 1.5, 2.0 } },
-                        { .text = "ammo", .pos = { 6.0, World::get_FloorY<double>() + 1.5, 3.0 } },
-                        { .text = "ammo", .pos = { 4.0, World::get_FloorY<double>() + 1.5, 6.0 } },
-                        { .text = "ammo", .pos = { 6.0, World::get_FloorY<double>() + 1.5, 1.0 } },
-                    },
-                    .plates    = {},
-                    .platforms = {},
                     .ai_seeds  = {
                         {
-                            .text           = "danger",
+                            .aggressiveness = 1.0,
                             .pos            = { -6.0, World::get_FloorY<double>() + 2.0, -6.0 },
+                            .text           = "danger",
                             .initial_state  = Text3dAI::State::Attack,
-                            .aggressiveness = 1.0,
                         },
                     },
+                    .plates    = {},
+                    .platforms = {},
+                    .words = {
+                        { .pos = { 6.0, World::get_FloorY<double>() + 1.5, 6.0 }, .text = "ammo", },
+                        { .pos = { 3.0, World::get_FloorY<double>() + 1.5, 2.0 }, .text = "ammo", },
+                        { .pos = { 6.0, World::get_FloorY<double>() + 1.5, 3.0 }, .text = "ammo", },
+                        { .pos = { 4.0, World::get_FloorY<double>() + 1.5, 6.0 }, .text = "ammo", },
+                        { .pos = { 6.0, World::get_FloorY<double>() + 1.5, 1.0 }, .text = "ammo", },
+                    },
                 },
-                .reset_carry = true,
-                .goal = AllOf{{ CollectWord{ "ammo" }, AiWordDefeated{ "danger" } }},
-                .max_ai_words = 0,
+                .goal    = AllOf{{ CollectWord{ "ammo" }, AiWordDefeated{ "danger" } }},
+                .hint    = "Grab the ammo, then shoot the red word",
+                .details = R"(Some words turn red and chase you to steal what
+you carry.
+
+Pick up "ammo", then knock out the red "danger"
+word by throwing at it with LEFT CLICK before it
+reaches you.)",
+                .grants  = {},
             },
 
             {
+                .reset_carry  = true,
+                .max_ai_words = 0,
+                .scene   = {},
+                .goal    = CompilePattern{ "int main ( void ) { return 0 ; }" },
                 .hint    = "Build int main(void) { return 0; } and press BUILD",
                 .details = R"(Now make code.
 
@@ -227,13 +231,16 @@ press it (or press 'c' to compile).)",
                     Level::TokenGrant::give(";"),
                     Level::TokenGrant::give("}"),
                 },
-                .scene        = {},
-                .reset_carry  = true,
-                .goal         = CompilePattern{ "int main ( void ) { return 0 ; }" },
-                .max_ai_words = 0,
             },
 
             {
+                .reset_carry  = false,
+                .max_ai_words = 0,
+                .scene = {},
+                .goal  = AllOf{{
+                    CompilePattern{ "int main ( void ) { printf ( \"hello\" ) ; return 0 ; }" },
+                    PressButton{ PultAction::RunProgram },
+                }},
                 .hint    = "Add printf, BUILD, then press RUN",
                 .details = R"(A program that does nothing is dull. Make it print.
 
@@ -255,16 +262,22 @@ the Program wall — and that's what finishes this step.)",
                     Level::TokenGrant::give(")"),
                     Level::TokenGrant::give(";"),
                 },
-                .scene        = {},
-                .reset_carry  = false,
-                .goal = AllOf{{
-                    CompilePattern{ "int main ( void ) { printf ( \"hello\" ) ; return 0 ; }" },
-                    PressButton{ PultAction::RunProgram },
-                }},
-                .max_ai_words = 0,
             },
 
             {
+                .reset_carry  = false,
+                .max_ai_words = 0,
+                .scene   = Scene{
+                    .ai_seeds  = {},
+                    .plates = {
+                        { .pos = { 0.0, World::get_FloorY<double>() + 1.2, -6.0 }, .tag = "" },
+                    },
+                    .platforms = {
+                        { .pos = { 0.0, World::get_FloorY<double>() + 0.6, -6.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
+                    },
+                    .words  = {},
+                },
+                .goal    = ReachPlate{ "" },
                 .hint    = "Try the editor pults, then jump onto the plate",
                 .details = R"(A few tools for managing the Editor wall:
 
@@ -290,41 +303,28 @@ Have a play, then jump onto the plate to continue.)",
                     Level::TokenGrant::give("42"),
                     Level::TokenGrant::give("printf(\"Enter a Number:\\n\\t\")"),
                 },
+            },
+
+            {
+                .reset_carry  = true,
+                .max_ai_words = 0,
                 .scene   = Scene{
-                    .words  = {},
+                    .ai_seeds = {},
                     .plates = {
-                        { .pos = { 0.0, World::get_FloorY<double>() + 1.2, -6.0 }, .tag = "" },
+                        { .pos = { 0.0, World::get_FloorY<double>() + 1.2, 0.0 }, .tag = "" },
                     },
                     .platforms = {
-                        { .pos = { 0.0, World::get_FloorY<double>() + 0.6, -6.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
+                        { .pos = { 0.0, World::get_FloorY<double>() + 0.6, 0.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
                     },
-                    .ai_seeds  = {},
+                    .words  = {},
                 },
-                .reset_carry  = false,
-                .goal         = ReachPlate{ "" },
-                .max_ai_words = 0,
-            },
-
-            {
+                .goal    = ReachPlate{ "" },
                 .hint    = "Jump onto the plate to begin \"Char Copy\"",
                 .details = R"(You're ready.
 
 Jump onto the plate to start the first real level:
 "Char Copy".)",
                 .grants  = {},
-                .scene   = Scene{
-                    .words  = {},
-                    .plates = {
-                        { .pos = { 0.0, World::get_FloorY<double>() + 1.2, 0.0 }, .tag = "" },
-                    },
-                    .platforms = {
-                        { .pos = { 0.0, World::get_FloorY<double>() + 0.6, 0.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
-                    },
-                    .ai_seeds = {},
-                },
-                .reset_carry  = true,
-                .goal         = ReachPlate{ "" },
-                .max_ai_words = 0,
             },
         },
     };
diff --git a/src/levels/level_check.hpp b/src/levels/level_check.hpp
index 97b6928..21d13b6 100644
--- a/src/levels/level_check.hpp
+++ b/src/levels/level_check.hpp
@@ -1,10 +1,10 @@
 #pragma once
 #include <string>
 
-namespace LevelCheck {
-    // Normalize: trim, collapse all whitespace (tab/newline/space) to single spaces.
-    std::string normalize(const std::string& s);
+namespace LevelCheck
+{
+    std::string
+         normalize(const std::string& s);
 
-    // Does `haystack` (already normalized) contain `needle` (will be normalized)?
     bool contains_pattern(const std::string& haystack, const std::string& needle);
 }
diff --git a/src/levels/scene.hpp b/src/levels/scene.hpp
index 531da62..8f74766 100644
--- a/src/levels/scene.hpp
+++ b/src/levels/scene.hpp
@@ -8,50 +8,44 @@
 #include "utils/math.hpp"
 
 
-// A word placed at a specific world position. Becomes a Hover-mode Text3d
-// in ctx.words. If `required` is set, the PickAllWords goal will check
-// whether this word is currently in the carry line.
 struct PlacedWord {
-    std::string text;
-    Vec3        pos;
+    bool required = false;
+    Vec3 pos {};
+
     SDL_FColor  color    = ConfigColor::floor;
-    bool        required = false;
+    std::string text;
 };
 
 
-// A glowing pad. The step's goal can require the player to stand on it.
-// `tag` is optional — used by goals that target a specific plate.
 struct PlacedPlate {
-    Vec3        pos;
+    Vec3 pos;
+
     std::string tag;
 };
 
 
-// A solid platform. Inert scenery the player can walk and jump on.
-// Color defaults to a neutral grey-blue; override for visual variety.
 struct PlacedPlatform {
-    Vec3        pos;
-    Vec3        half_extents { 1.0, 0.2, 1.0 };
-    SDL_FColor  fill_color   { 0.45f, 0.45f, 0.65f, 1.0f };
-    SDL_FColor  wire_color   { 0.70f, 0.70f, 0.90f, 1.0f };
+    Vec3 pos;
+    Vec3 half_extents { 1.0, 0.2, 1.0 };
+
+    SDL_FColor fill_color   { 0.45f, 0.45f, 0.65f, 1.0f };
+    SDL_FColor wire_color   { 0.70f, 0.70f, 0.90f, 1.0f };
 };
 
 
-// A pre-aggressive AI word. Spawned in the requested state (typically Attack)
-// at the requested position.
 struct PlacedAi {
-    std::string  text;
-    Vec3         pos;
-    Text3dAI::State initial_state  = Text3dAI::State::Attack;
-    double       aggressiveness = 1.0;
+    double aggressiveness = 1.0;
+    Vec3   pos;
+
+    std::string text;
+
+    Text3dAI::State initial_state = Text3dAI::State::Attack;
 };
 
 
-// All spatial content for a single step. Spawned on step entry, removed
-// on step advance (or reset).
 struct Scene {
-    DynArray<PlacedWord>     words    {};
+    DynArray<PlacedAi>       ai_seeds {};
     DynArray<PlacedPlate>    plates   {};
     DynArray<PlacedPlatform> platforms{};
-    DynArray<PlacedAi>       ai_seeds {};
+    DynArray<PlacedWord>     words    {};
 };
diff --git a/src/rendering/floor_mandelbrot.hpp b/src/rendering/floor_mandelbrot.hpp
index b8424c1..7c68419 100644
--- a/src/rendering/floor_mandelbrot.hpp
+++ b/src/rendering/floor_mandelbrot.hpp
@@ -10,11 +10,6 @@
 class Camera;
 
 
-// FloorMandelbrot: bakes a triangle-mesh floor with the classic full
-// Mandelbrot set, colored in discrete iteration bands. Each iteration
-// count maps to one of 8 cycling pastel-rainbow colors, giving sharp
-// transitions and clear "ring" regions around the set boundary. Inside
-// the set is a near-black indigo.
 class FloorMandelbrot {
     DynArray<float> _verts;
     DynArray<int>   _indices;
diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index 936a145..51308fd 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -830,15 +830,15 @@ GameState::update_WorldSimulation(double dt)
             if (!wt || !wt->text3d) continue;
             wall_text_hits.push_back({
                     wt->wall_position,
+                    i,
                     wt->text3d->aabb_min(),
                     wt->text3d->aabb_max(),
-                    i
                 });
         }
 
         for (const EditorWall::NumberHit& nh : _editor_wall.number_Hitboxes())
-            wall_text_hits.push_back({ Wall::Position::NegX,
-                                       nh.aabb_min, nh.aabb_max, nh.index });
+            wall_text_hits.push_back({ Wall::Position::NegX, nh.index,
+                                       nh.aabb_min, nh.aabb_max });
 
         for (size_t li = 0; li < _wall_labels.size(); ++li) {
             const DynArray<Text3dPtr>& words = _wall_labels[li].label.words();
@@ -847,9 +847,9 @@ GameState::update_WorldSimulation(double dt)
                 constexpr size_t LABEL_HIT_BASE = size_t(1) << 48;
                 wall_text_hits.push_back({
                         Wall::position_of(_wall_labels[li].role),
+                        LABEL_HIT_BASE | (li << 16) | wi,
                         words[wi]->aabb_min(),
                         words[wi]->aabb_max(),
-                        LABEL_HIT_BASE | (li << 16) | wi
                         });
             }
         }
diff --git a/src/states/game_state.hpp b/src/states/game_state.hpp
index 4553153..16e8b1a 100644
--- a/src/states/game_state.hpp
+++ b/src/states/game_state.hpp
@@ -36,80 +36,79 @@
 
 class GameState : public IState
 {
-    GameContext _ctx;
-    FrameInput  _frame_input;
-    CarriedLine _carried;
-    EditorWall  _editor_wall;
+
+    cpputils::unique_ptr<CelebrationSystem>_celebration_sys;
+    cpputils::unique_ptr<CompilerSystem>   _compiler_sys;
+    cpputils::unique_ptr<DynLoaderSystem>  _dyn_loader_sys;
+    cpputils::unique_ptr<FloorMandelbrot>  _floor_sys;
+    cpputils::unique_ptr<FreelookSystem>   _freelook_sys;
+    cpputils::unique_ptr<HintSystem>       _hint_sys;
+    cpputils::unique_ptr<InputSystem>      _input_sys;
+    cpputils::unique_ptr<LevelDirector>    _level_director;
+    cpputils::unique_ptr<ParticleSystem>   _particle_sys;
+    cpputils::unique_ptr<PhysicsSystem>    _physics_sys;
+    cpputils::unique_ptr<PlateSystem>      _plate_sys;
+    cpputils::unique_ptr<ProgramSystem>    _program_sys;
+    cpputils::unique_ptr<ProjectileSystem> _projectiles_sys;
+    cpputils::unique_ptr<PultSystem>       _pult_sys;
+    cpputils::unique_ptr<WordAiSystem>     _word_ai_sys;
+    cpputils::unique_ptr<WordAnimSystem>   _word_anim_sys;
 
     double _pick_cooldown  { 0.0 };
     double _shoot_cooldown { 0.0 };
 
+    std::string    _typing_last;
 
+    CarriedLine    _carried;
+    EditorWall     _editor_wall;
+    FrameInput     _frame_input;
+    GameContext    _ctx;
     WallConnection _wall_connection;
 
-    void wallconnect_BeginProgram();
-    void wallconnect_BeginEditor ();
-    void on_WallConnectSubmit       ();
-    void on_WallConnectExit         ();
-
-    DynArray<std::string> editor_AbsorbLines();
-    void editor_Clear        ();
-    void editor_CommitToWall (const DynArray<std::string>& lines);
-    void editor_PlaceLines   (const DynArray<std::string>& lines);
-
-    DynArray<Vec3>      _freelook_return_from;
-
-    DynArray<WallLabel> _wall_labels;
-
-    DynArray<Text3d*>   _typing_words;
-    std::string         _typing_last;
 
+    DynArray<GameObject*>   _dyn_scenery;
+    DynArray<Text3d*>       _typing_words;
+    DynArray<Vec3>          _freelook_return_from;
     DynArray<Projectile>    _projectiles;
+    DynArray<WallLabel>     _wall_labels;
     DynArray<WordAnim_Add>  _anims_add;
     DynArray<WordAnim_Drop> _anims_drop;
 
-    DynArray<GameObject*>   _dyn_scenery;
+    void on_WallConnectExit      ();
+    void on_WallConnectSubmit    ();
+    void wallconnect_BeginEditor ();
+    void wallconnect_BeginProgram();
 
-    cpputils::unique_ptr<InputSystem>      _input_sys;
-    cpputils::unique_ptr<PhysicsSystem>    _physics_sys;
-    cpputils::unique_ptr<ProjectileSystem> _projectiles_sys;
-    cpputils::unique_ptr<WordAnimSystem>   _word_anim_sys;
-    cpputils::unique_ptr<CompilerSystem>   _compiler_sys;
-    cpputils::unique_ptr<ProgramSystem>    _program_sys;
-    cpputils::unique_ptr<FreelookSystem>   _freelook_sys;
-    cpputils::unique_ptr<PultSystem>       _pult_sys;
-    cpputils::unique_ptr<HintSystem>       _hint_sys;
-    cpputils::unique_ptr<WordAiSystem>     _word_ai_sys;
-    cpputils::unique_ptr<PlateSystem>      _plate_sys;
-    cpputils::unique_ptr<ParticleSystem>   _particle_sys;
-    cpputils::unique_ptr<FloorMandelbrot>  _floor_sys;
-    cpputils::unique_ptr<DynLoaderSystem>  _dyn_loader_sys;
-    cpputils::unique_ptr<CelebrationSystem>_celebration_sys;
-    cpputils::unique_ptr<LevelDirector>    _level_director;
+    DynArray<std::string>
+         editor_AbsorbLines  ();
+    void editor_Clear        ();
+    void editor_CommitToWall (const DynArray<std::string>& lines);
+    void editor_PlaceLines   (const DynArray<std::string>& lines);
 
 
     bool on_Pick         ();
-    void on_Click        ();
-    void on_DropLast     ();
     bool on_ShootOne     ();
-    void on_ShootAll     ();
+    void on_Click        ();
+    void on_Compile      ();
     void on_DebugPrint   ();
-    void on_Jump         ();
-    void on_FreelookStart();
+    void on_DropLast     ();
     void on_FreelookEnd  ();
-    void on_Compile      ();
-    void on_Run          ();
+    void on_FreelookStart();
+    void on_Jump         ();
     void on_NumberAdjust (int dt);
-    void on_TypingStart  (FrameInput&);
-    void on_TypingCommit (FrameInput&);
+    void on_Run          ();
+    void on_ShootAll     ();
     void on_TypingCancel (FrameInput&);
+    void on_TypingCommit (FrameInput&);
+    void on_TypingStart  (FrameInput&);
 
-    void update_WorldSimulation(double dt);
-    void level_TryAdvance      (double dt);
     void carriedline_Update    ();
     void carry_BeginAnim       (Text3dPtr word, bool with_pitch);
-    void typing_RebuildPreview (const std::string& s);
+    void level_TryAdvance      (double dt);
     void typing_ClearPreview   ();
+    void typing_RebuildPreview (const std::string& s);
+
+    void update_WorldSimulation(double dt);
 
     void infowall_Clear           ();
     void walltext_HandleShootDown (ProjectileResult& result, DynArray<size_t>& to_erase);
diff --git a/src/states/menu_state.hpp b/src/states/menu_state.hpp
index f0a0fe2..a82866b 100644
--- a/src/states/menu_state.hpp
+++ b/src/states/menu_state.hpp
@@ -40,17 +40,17 @@ class MenuState : public IState
 
     double _t{0.0};
 
-    void   build_Items  ();
-    void   activate     (Game& game);
+    void   build_Items ();
+    void   activate    (Game& game);
+    bool   is_LevelItem(size_t idx) const;
     size_t pick_FromCrosshair() const;
-    bool   is_LevelItem (size_t idx) const;
 
     static uint32_t yaw_FromRadians(double rad);
     static double   yaw_ToRadians  (uint32_t yaw);
 public:
     MenuState();
 
-    void handle_Event(Game&, const SDL_Event&) override;
-    void update(double dt)                     override;
-    void render(SDL_Window&, GlRenderer&)      override;
+    void handle_Event(Game&, const SDL_Event&)  override;
+    void update      (double dt)                override;
+    void render      (SDL_Window&, GlRenderer&) override;
 };
diff --git a/src/systems/aim.hpp b/src/systems/aim.hpp
index 0543889..bf54bd1 100644
--- a/src/systems/aim.hpp
+++ b/src/systems/aim.hpp
@@ -10,25 +10,14 @@
 #include "utils/math.hpp"
 #include "wall/text_wall.hpp"
 
-// Aim::crosshair_Target — the first surface the crosshair ray meets.
-//
-//   .point  world aim point (always valid: wall / floor / object hit)
-//   .track  non-null ONLY when a movable word (from `words`) was hit — that
-//           object should be hard-tracked so the throw follows it.
-//   .local  the aim point expressed in the target's local frame (yaw-derotated)
-//           so it rides the word's rotation: a hit on the right edge stays on
-//           the right edge as the word spins.
-//
-// Scenery, walls, floor, and stuck wall-text are treated as STATIC points
-// (track == nullptr): they don't move, and keeping them out of tracking avoids
-// the differing-lifetime list problem (wall_texts live in a separate vector).
 namespace Aim
 {
 
 struct Result {
-    Vec3    point {};
     Text3d* track { nullptr };
-    Vec3    local {};
+
+    Vec3 point {};
+    Vec3 local {};
 };
 
 inline Result
@@ -39,7 +28,7 @@ crosshair_Target(const Camera& camera,
                  float world_scale)
 {
     const Vec3   eye = camera.pos;
-    const Vec3   dir = camera.fwd;                 // unit
+    const Vec3   dir = camera.fwd;
     const double ws  = static_cast<double>(world_scale);
 
     // 1. Distance along the ray to the box surface (nearest bounding plane).
@@ -68,7 +57,7 @@ crosshair_Target(const Camera& camera,
             out.point = { eye.x + seg.x * sw.t,
                           eye.y + seg.y * sw.t,
                           eye.z + seg.z * sw.t };
-            win = obj;                  // word → track it; non-word → nullptr
+            win = obj;
         }
     };
 
@@ -79,7 +68,6 @@ crosshair_Target(const Camera& camera,
     for (const TextWallPtr& wt : wall_texts)
         if (wt && wt->text3d) test(wt->text3d->aabb_min(), wt->text3d->aabb_max(), nullptr);
 
-    // If a movable word won, record it and de-rotate the offset into its frame.
     if (win) {
         out.track = win;
         const double dx = out.point.x - win->pos.x;
@@ -87,7 +75,7 @@ crosshair_Target(const Camera& camera,
         const double dz = out.point.z - win->pos.z;
         const double cy = SDL_cos(win->yaw);
         const double sy = SDL_sin(win->yaw);
-        out.local = { cy * dx - sy * dz,        // R(-yaw) * (point - pos)
+        out.local = { cy * dx - sy * dz,
                       dy,
                       sy * dx + cy * dz };
     }
diff --git a/src/systems/carry_system.hpp b/src/systems/carry_system.hpp
index 2bfd76d..7e81a21 100644
--- a/src/systems/carry_system.hpp
+++ b/src/systems/carry_system.hpp
@@ -14,21 +14,21 @@ struct CarriedWordNode {
 };
 
 struct CarriedLine {
-    cpputils::unique_ptr<CarriedWordNode> first;  // Owns the first node
-    CarriedWordNode* last{nullptr};          // Raw pointer to last node (non-owning)
+    cpputils::unique_ptr<CarriedWordNode> first;
+    CarriedWordNode* last{nullptr};
     size_t size{0};
 
-    bool empty() const;
-
-    void      push(Text3dPtr w);
-    Text3dPtr pop_back();
-    Text3dPtr pop_front();
-
-    DynArray<CarriedWordNode*> nodes() const;
+    bool empty  () const;
+    void clear  ();
+    void compact();
 
-    DynArray<CarriedWordNode*> reverse_nodes() const;
 
-    void clear();
+    void      push     (Text3dPtr w);
+    Text3dPtr pop_back ();
+    Text3dPtr pop_front();
 
-    void compact();
+    DynArray<CarriedWordNode*>
+              nodes        () const;
+    DynArray<CarriedWordNode*>
+              reverse_nodes() const;
 };
diff --git a/src/systems/celebration_system.hpp b/src/systems/celebration_system.hpp
index 244de1c..dca732a 100644
--- a/src/systems/celebration_system.hpp
+++ b/src/systems/celebration_system.hpp
@@ -15,10 +15,10 @@ class CelebrationSystem {
               _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;
+    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;
+    void   draw    (GlRenderer&, const Camera&, const Light&) const;
 };
diff --git a/src/systems/collision.hpp b/src/systems/collision.hpp
index f310e03..ae8e8a0 100644
--- a/src/systems/collision.hpp
+++ b/src/systems/collision.hpp
@@ -8,8 +8,8 @@ namespace Collision
 {
     struct Manifold {
         bool   hit     = false;
-        Vec3   normal  {};
         double overlap {};
+        Vec3   normal  {};
     };
 
     struct SweptResult {
diff --git a/src/systems/compiler_system.hpp b/src/systems/compiler_system.hpp
index c96db89..af125c6 100644
--- a/src/systems/compiler_system.hpp
+++ b/src/systems/compiler_system.hpp
@@ -12,11 +12,11 @@
 struct CompilerSystem
 {
 private:
-    std::thread           _worker;
+    int                   _count {0};
+    std::atomic<bool>     _running{false};
     std::mutex            _mtx;
+    std::thread           _worker;
     DynArray<std::string> _pending;
-    std::atomic<bool>     _running{false};
-    int                   _count {0};
 
     void        push_Line  (TextWallList&, const std::string& line);
     static void run_Compile(CompilerSystem* self, std::string src);
diff --git a/src/systems/dyn_loader_system.hpp b/src/systems/dyn_loader_system.hpp
index 47ddd95..66ab185 100644
--- a/src/systems/dyn_loader_system.hpp
+++ b/src/systems/dyn_loader_system.hpp
@@ -9,16 +9,18 @@
 
 class DynLoaderSystem
 {
-    FILE*       _pipe  { nullptr };
-    int         _count { 0 };
-    int         _build_serial { 0 };
+    static constexpr float line_height = 0.25f;
+    static constexpr float start_y     = -15.0f;
+
+    FILE* _pipe   { nullptr };
+    void* _handle { nullptr };
+
+    int _count { 0 };
+    int _build_serial { 0 };
+
     std::string _pending_so;
     std::string _last_so_path;
 
-    void*       _handle { nullptr };   // current dlopen handle
-
-    static constexpr float line_height = 0.25f;
-    static constexpr float start_y     = -15.0f;
 
     static std::string
          wrap_Source(const std::string& user_body);
@@ -33,10 +35,8 @@ public:
     const std::string&
          last_so_path() const { return _last_so_path; }
 
-    // Checkpoint 2: dlopen the last successful .so, call game_build,
-    // return the spawned descriptions. Empty vector on any failure.
     DynArray<DynObject>
          load_Objects();
 
-    ~DynLoaderSystem();   // dlclose on teardown
+    ~DynLoaderSystem();
 };
diff --git a/src/systems/hint_system.hpp b/src/systems/hint_system.hpp
index 740a7d2..142345a 100644
--- a/src/systems/hint_system.hpp
+++ b/src/systems/hint_system.hpp
@@ -8,8 +8,8 @@
 class GlRenderer;
 
 class HintSystem {
-    TextLabel   _label;
     std::string _current_hint;
+    TextLabel   _label;
 
 public:
     HintSystem   ();
diff --git a/src/systems/input_system.hpp b/src/systems/input_system.hpp
index ba551cd..150e561 100644
--- a/src/systems/input_system.hpp
+++ b/src/systems/input_system.hpp
@@ -34,12 +34,12 @@ struct FrameInput {
     bool capture_typing = false;
     bool capture_vim = false;
 
-    bool        typing_mode   = false;
-    bool        typing_commit = false;
-    bool        typing_cancel = false;
+    bool typing_mode   = false;
+    bool typing_commit = false;
+    bool typing_cancel = false;
 
-    int  look_x   = 0;
-    int  look_y   = 0;
+    int look_x   = 0;
+    int look_y   = 0;
 
     std::string            typing_buffer;
     DynArray<int>          vim_keys;
diff --git a/src/systems/level_director.cpp b/src/systems/level_director.cpp
index 89b32b9..d2d1766 100644
--- a/src/systems/level_director.cpp
+++ b/src/systems/level_director.cpp
@@ -42,7 +42,7 @@ LevelDirector::enter_Step(GameContext& ctx)
 void
 LevelDirector::spawn_Grants(GameContext& ctx)
 {
-    DynArray<std::string> tokens = _runner->grants_for_step();
+    DynArray<std::string> tokens = _runner->garants_ForStep();
 
     static constexpr float spacing = 3.0f;
     static constexpr int   per_row = 8;
diff --git a/src/systems/level_director.hpp b/src/systems/level_director.hpp
index 289d9c4..76f6ecd 100644
--- a/src/systems/level_director.hpp
+++ b/src/systems/level_director.hpp
@@ -46,16 +46,20 @@ struct StepPlate {
 
 class LevelDirector
 {
-    cpputils::unique_ptr<LevelRunner> _runner;
+    cpputils::unique_ptr<LevelRunner>
+           _runner;
+    double _step_age { 0.0 };
+
     StepLedger             _ledger;
-    DynArray<GameObject*>  _step_scenery;
     DynArray<StepPlate>    _step_plates;
-    double                 _step_age { 0.0 };
+    DynArray<GameObject*>  _step_scenery;
+
+    void spawn_Grants  (GameContext& ctx);
+    void setup_Scene   (GameContext& ctx, const Scene& scene);
+    void teardown_Scene(GameContext& ctx);
 
-    void        spawn_Grants  (GameContext& ctx);
-    void        setup_Scene   (GameContext& ctx, const Scene& scene);
-    void        teardown_Scene(GameContext& ctx);
     GoalContext build_GoalCtx (const GameContext& ctx) const;
+
 public:
     struct AdvanceResult {
         bool stepped     {false};   // a step boundary was crossed this frame
diff --git a/src/systems/level_runner_system.cpp b/src/systems/level_runner_system.cpp
index 2167b83..f66dffe 100644
--- a/src/systems/level_runner_system.cpp
+++ b/src/systems/level_runner_system.cpp
@@ -8,7 +8,7 @@ bool LevelRunner::advance()
     return !complete();
 }
 
-DynArray<std::string> LevelRunner::grants_for_step() const
+DynArray<std::string> LevelRunner::garants_ForStep() const
 {
     DynArray<std::string> result;
     for (const Level::TokenGrant& grant : current().grants)
diff --git a/src/systems/level_runner_system.hpp b/src/systems/level_runner_system.hpp
index 15aea80..c1d6131 100644
--- a/src/systems/level_runner_system.hpp
+++ b/src/systems/level_runner_system.hpp
@@ -2,22 +2,24 @@
 #include "levels/level.hpp"
 
 class LevelRunner {
-    const Level::Data* _level;
-    size_t           _current_step{0};
+    const Level::Data*
+           _level;
+
+    size_t _current_step{0};
 public:
     explicit LevelRunner(const Level::Data& _level);
 
-    // Call after a successful compile
-    // Returns true if there are more steps, false if _level complete
-    bool advance();
+    bool   advance();
+    bool   complete()    const { return _current_step >= _level->steps.size(); }
+
+    size_t step()        const { return _current_step; }
+    const Level::Step&
+           current()     const { return _level->steps[_current_step]; }
 
-    bool               complete()    const { return _current_step >= _level->steps.size(); }
-    size_t             step()        const { return _current_step; }
-    const Level::Step&   current()     const { return _level->steps[_current_step]; }
-    bool               dynamic_lib() const { return _level->dynamic_lib; }
-    const std::string& source_file() const { return _level->source_file; }
+    bool   dynamic_lib() const { return _level->dynamic_lib; }
+    const std::string&
+           source_file() const { return _level->source_file; }
 
-    // All tokens to spawn for the current step (resolved — no choices yet)
-    // Choices are returned as multiple Text3d objects placed near each other
-    DynArray<std::string> grants_for_step() const;
+    DynArray<std::string>
+           garants_ForStep() const;
 };
diff --git a/src/systems/particle_system.hpp b/src/systems/particle_system.hpp
index 8aa7633..a7b479f 100644
--- a/src/systems/particle_system.hpp
+++ b/src/systems/particle_system.hpp
@@ -14,14 +14,15 @@ class GameObject;
 // One particle. Lives ~1-2 seconds, falls under gravity, vanishes on
 // collision or when its lifetime expires.
 struct Particle {
-    Vec3       pos{};
-    Vec3       vel{};
+    bool   alive    { true };
+    float  yaw      { 0.0f };
+    float  spin     { 0.0f };   // radians/sec
+    double age      { 0.0 };
+    double lifetime { 1.5 };
+
+    Vec3 pos{};
+    Vec3 vel{};
     SDL_FColor color { 1.0f, 1.0f, 1.0f, 1.0f };
-    double     age      { 0.0 };
-    double     lifetime { 1.5 };
-    float      yaw      { 0.0f };
-    float      spin     { 0.0f };   // radians/sec
-    bool       alive    { true };
 };
 
 
@@ -43,15 +44,10 @@ public:
     void update(double dt,
                 const DynArray<GameObject*>& solid_colliders,
                 float world_scale);
-
-    void draw(GlRenderer& gl, const Camera& cam, const Light& light) const;
-
+    void clear ();
+    bool empty () const { return _particles.empty(); }
+    void draw  (GlRenderer& gl, const Camera& cam, const Light& light) const;
     void emit_FireworkBurst(const Vec3& center,
                             SDL_FColor color,
                             int count = 80);
-
-    // Clear all particles — called when celebration ends.
-    void clear();
-
-    bool empty() const { return _particles.empty(); }
 };
diff --git a/src/systems/physics_system.hpp b/src/systems/physics_system.hpp
index 8bd2c07..f5fa74b 100644
--- a/src/systems/physics_system.hpp
+++ b/src/systems/physics_system.hpp
@@ -19,10 +19,11 @@ struct ProjectileContactEvent {
 class PhysicsSystem {
     // Uniform spatial grid scratch, reused each frame to avoid reallocation.
     struct SpatialGrid {
-        double cell = 2.0;
-        double inv  = 0.5;
-        int    nx = 0, nz = 0;
+        int    nx    = 0, nz = 0;
+        double cell  = 2.0;
+        double inv   = 0.5;
         double min_x = 0, min_z = 0;
+
         DynArray<DynArray<GameObject*>> cells;
 
         void build(const DynArray<GameObject*>& objs);
diff --git a/src/systems/pick_system.hpp b/src/systems/pick_system.hpp
index e086fd5..81a4ed0 100644
--- a/src/systems/pick_system.hpp
+++ b/src/systems/pick_system.hpp
@@ -10,16 +10,17 @@
 
 enum class PickKind : uint8_t {
     None,
-    Word,
     Scenery,
     Wall,
+    Word,
 };
 
 struct PickResult {
-    PickKind     kind        { PickKind::None };
-    size_t       word_index  { 0 };
-    size_t       wall_index  { 0 };
-    GameObject*  scenery_obj { nullptr };
+    size_t word_index { 0 };
+    size_t wall_index { 0 };
+
+    GameObject* scenery_obj { nullptr };
+    PickKind    kind        { PickKind::None };
 
     bool hit() const { return kind != PickKind::None; }
 };
diff --git a/src/systems/plate_system.cpp b/src/systems/plate_system.cpp
index c2eb291..c60f7de 100644
--- a/src/systems/plate_system.cpp
+++ b/src/systems/plate_system.cpp
@@ -34,11 +34,3 @@ PlateSystem::update(double dt,
             plate->pulse = 1.0;
     }
 }
-
-bool
-PlateSystem::any_Active(const DynArray<StepPlate>& plates) const
-{
-    for (const StepPlate& sp : plates)
-        if (sp.plate && sp.plate->active) return true;
-    return false;
-}
diff --git a/src/systems/plate_system.hpp b/src/systems/plate_system.hpp
index bfd408a..599e2ac 100644
--- a/src/systems/plate_system.hpp
+++ b/src/systems/plate_system.hpp
@@ -11,5 +11,4 @@ class PlateSystem
 public:
     void update(double dt, const Player& player,
                 const DynArray<StepPlate>& plates);
-    bool any_Active(const DynArray<StepPlate>& plates) const;
 };
diff --git a/src/systems/program_system.cpp b/src/systems/program_system.cpp
index ca58d75..22095be 100644
--- a/src/systems/program_system.cpp
+++ b/src/systems/program_system.cpp
@@ -27,7 +27,7 @@ ProgramSystem::close_Child()
     if (_out_fd >= 0) { ::close(_out_fd); _out_fd = -1; }
     if (_child > 0) {
         int status;
-        if (waitpid(_child, &status, WNOHANG) == 0) {   // still alive → kill it
+        if (waitpid(_child, &status, WNOHANG) == 0) {
             kill(_child, SIGKILL);
             waitpid(_child, &status, 0);
         }
diff --git a/src/systems/program_system.hpp b/src/systems/program_system.hpp
index 7fa4a1a..19e419a 100644
--- a/src/systems/program_system.hpp
+++ b/src/systems/program_system.hpp
@@ -1,34 +1,31 @@
 #pragma once
 #include <string>
-#include <sys/types.h>   // pid_t
+#include <sys/types.h>
 
 #include "entities/world.hpp"
 #include "utils/globals.hpp"
 #include "wall/text_wall.hpp"
 
-// ProgramSystem: runs the executable built by CompilerSystem (/tmp/game_prog)
-// and streams its stdout/stderr onto the Program wall (+X). The child is
-// spawned with BOTH a readable stdout pipe and a writable stdin pipe, so the
-// player can feed input to scanf/cin/getline by throwing words at the wall
-// (see feed_Input). Polls non-blocking so it never stalls the frame.
 struct ProgramSystem
 {
 private:
-    int         _out_fd {-1};   // read end of child's stdout+stderr
-    int         _in_fd  {-1};   // write end of child's stdin
-    pid_t       _child  {-1};
-    int         _count  {0};
+    int _out_fd {-1};
+    int _in_fd  {-1};
+    int _count  {0};
+
+    pid_t _child  {-1};
+
     std::string _pending;       // partial-line buffer for stdout
 
     void push_Line(TextWallList&, const std::string& line, float world_scale);
-    void close_Child();         // close pipes, reap (or kill) the child
+    void close_Child();
 public:
     static inline float line_height = 0.4f;
     static inline float start_y     = World::get_FloorY() + 1;
 
     void run        (TextWallList&, float world_scale);
     void update     (TextWallList&, float world_scale);
-    void feed_Input (const std::string& text);   // thrown word(s) → child stdin
+    void feed_Input (const std::string& text);
     void echo_Input (TextWallList&, const std::string& line);
     // Park already-thrown (animated) words on the Program wall as one echoed
     // input line, scrolling existing output up — terminal style.
diff --git a/src/systems/projectile_system.cpp b/src/systems/projectile_system.cpp
index 356a364..d59f0a2 100644
--- a/src/systems/projectile_system.cpp
+++ b/src/systems/projectile_system.cpp
@@ -160,6 +160,7 @@ static bool
 check_WallTextIntercept(const Projectile& p, const Vec3& centroid, double dt,
                         Wall::Position hit_pos,
                         const DynArray<WallTextHitTest>& wall_text_hits,
+                        double aim_y, bool editor_wall,
                         size_t& out_idx)
 {
     const Vec3 next_centroid {
@@ -177,6 +178,12 @@ check_WallTextIntercept(const Projectile& p, const Vec3& centroid, double dt,
     for (const WallTextHitTest& wt : wall_text_hits) {
         if (wt.wall_position != hit_pos) continue;
 
+        if (editor_wall) {
+            const double center_y = (wt.aabb_min.y + wt.aabb_max.y) * 0.5;
+            if (SDL_fabs(aim_y - center_y) > ConfigWall::line_height * 0.5)
+                continue;
+        }
+
         Vec3 box_min { wt.aabb_min.x - word_he.x,
                        wt.aabb_min.y - word_he.y,
                        wt.aabb_min.z - word_he.z };
@@ -186,10 +193,7 @@ check_WallTextIntercept(const Projectile& p, const Vec3& centroid, double dt,
 
         Collision::SweptResult sw =
             Collision::swept_RayAabb(p.prev_centroid, ray_dir, box_min, box_max);
-        if (sw.hit) {
-            out_idx = wt.index;
-            return true;
-        }
+        if (sw.hit) { out_idx = wt.index; return true; }
     }
     return false;
 }
@@ -312,6 +316,15 @@ bounce_OffWall(Projectile& p, Wall::Position hit_pos, double ws,
 }
 
 
+static double
+editor_AimY(const Projectile& p, double plane_x)
+{
+    if (SDL_fabs(p.dir.x) <= 1e-6) return Text3d::words_centroid(p.words).y;
+    const double t = (plane_x - p.cam_origin.x) / p.dir.x;
+    return p.cam_origin.y + p.dir.y * t;
+}
+
+
 static bool
 handle_WallHit(Projectile& p, const Vec3& centroid, Wall::Position hit_pos,
                double dt,double ws,
@@ -324,9 +337,15 @@ handle_WallHit(Projectile& p, const Vec3& centroid, Wall::Position hit_pos,
         return bounce_OffWall(p, hit_pos, ws, results);
     }
 
+    const bool editor = (Wall::role_of(hit_pos) == Wall::Role::Editor);
+    const double aim_y = editor
+        ? editor_AimY(p, wall_plane_coord(hit_pos, ws))
+        : 0.0;
+
     size_t hit_idx = 0;
     if (p.words.size() == 1 &&
-        check_WallTextIntercept(p, centroid, dt, hit_pos, wall_text_hits, hit_idx)) {
+            check_WallTextIntercept(p, centroid, dt, hit_pos, wall_text_hits,
+                                    aim_y, editor, hit_idx)) {
         ProjectileResult r;
         r.landing         = ProjectileLanding::WallText;
         r.wall_pos        = hit_pos;
diff --git a/src/systems/projectile_system.hpp b/src/systems/projectile_system.hpp
index 45963f1..cee9fc1 100644
--- a/src/systems/projectile_system.hpp
+++ b/src/systems/projectile_system.hpp
@@ -12,54 +12,61 @@ static constexpr uint8_t BOUNCED_X = 1 << 0;
 static constexpr uint8_t BOUNCED_Z = 1 << 1;
 
 struct Projectile {
+    Wall::Position
+         stick_wall    { Wall::Position::PosX };
+    bool sticking      = false;
+    bool collided      = false;
+    bool program_stick = false;
+    uint8_t bounce_state  = 0;
+    float  ribbon_start_yaw  {};
+    float  ribbon_target_yaw {};
+    double yaw;
+    double stick_t = 0.0;
+    double step_dt = 0.0;
+    Text3d* track_obj    = nullptr;
+
+    Vec3 vel{};
+    Vec3 dir{};
+    Vec3 prev_centroid{};
+    Vec3 launch_origin{};
+    Vec3 cam_origin   {};
+
+    Vec3 track_local{};
+    Vec3 track_last {};
+
+
+    Vec3 ribbon_origin       {};
+    Vec3 ribbon_start_origin {};
+    Vec3 ribbon_target_origin{};
+
     DynArray<Text3dPtr> words;
-    Vec3     vel{};
-    Vec3     dir{};
-    Vec3     prev_centroid{};
-    Vec3     launch_origin{};
-    Vec3     cam_origin   {}; // camera eye at launch — the crosshair ray origin
-    double   yaw;
-    uint8_t        bounce_state  = 0;
-    bool           sticking      = false;
-    bool           collided      = false;   // hit something → gravity resumes
-    bool           program_stick = false;   // sticking to the Program wall as input echo
-    double         stick_t       = 0.0;
-    Wall::Position stick_wall    { Wall::Position::PosX };
-    double         step_dt       = 0.0;
-
-    Text3d* track_obj    = nullptr;   // hard-track target; null = ballistic/free
-    Vec3    track_local  {};          // aim point in target's local frame
-    Vec3    track_last   {};          // last known world point (fallback if obj dies)
-
-
-    Vec3  ribbon_origin        {};
-    Vec3  ribbon_start_origin  {};
-    Vec3  ribbon_target_origin {};
-    float ribbon_start_yaw     {};
-    float ribbon_target_yaw    {};
 };
 
 struct WallTextHitTest {
-    Wall::Position wall_position;
-    Vec3           aabb_min;
-    Vec3           aabb_max;
-    size_t         index;
+    Wall::Position
+           wall_position;
+    size_t index;
+
+    Vec3 aabb_min;
+    Vec3 aabb_max;
 };
 
 enum class ProjectileLanding : uint8_t {
-    None,
     Floor,
+    None,
+    ProgramInput,
     Wall,
     WallText,
-    ProgramInput,
 };
 
 struct ProjectileResult {
-    ProjectileLanding   landing  = ProjectileLanding::None;
-    Wall::Position      wall_pos = Wall::Position::NegZ;
-    size_t              wall_text_index = 0;
+    Wall::Position
+            wall_pos = Wall::Position::NegZ;
+    size_t wall_text_index = 0;
+    Vec3   vel{};
+
+    ProjectileLanding   landing = ProjectileLanding::None;
     DynArray<Text3dPtr> words;
-    Vec3                vel{};
 };
 
 
diff --git a/src/systems/wall_connection.hpp b/src/systems/wall_connection.hpp
index 5735810..7a1b6d4 100644
--- a/src/systems/wall_connection.hpp
+++ b/src/systems/wall_connection.hpp
@@ -37,22 +37,27 @@ public:
     void render(GlRenderer& gl, const Camera& cam, const Light& light) const;
 
 private:
-    Target _target { Target::None };
-    Vec3   _pult_pos {};
+    unsigned _vim_seen_version { 0 };
+
+    double _row0_y{ 0.0 };
+    double _eye_y { 0.0 };
     double _blink { 0.0 };
+    double _code_left_z { 0.0 };
+
+    Target _target{ Target::None };
+
+    Text3dPtr _cmdline;
+    Text3dPtr _prog_input;
+
+    Vec3 _pult_pos{};
 
-    Text3dPtr   _prog_input;
     std::string _prog_last;
 
-    VimBuffer             _vim;
-    unsigned              _vim_seen_version { 0 };
+    VimBuffer _vim;
+
     DynArray<Text3dPtr>   _render;
     DynArray<Text3dPtr>   _linenums;
-    Text3dPtr             _cmdline;
     DynArray<std::string> _status;
-    double _row0_y      { 0.0 };
-    double _code_left_z { 0.0 };
-    double _eye_y       { 0.0 };
 
     void rebuild_Editor ();
     void rebuild_Cmdline();
diff --git a/src/systems/word_ai_system.hpp b/src/systems/word_ai_system.hpp
index 3eed820..a6de80f 100644
--- a/src/systems/word_ai_system.hpp
+++ b/src/systems/word_ai_system.hpp
@@ -11,27 +11,27 @@ class WordAiSystem {
     int   _max_active_ai = 5;
 
 
-    void update_Idle    (Text3d& w, const Player& p, int active_ai_count, double dt);
-    void update_Attack  (Text3d& w, const Player& p, CarriedLine& carried,
-                         DynArray<Text3dPtr>& all_words,
-                         DynArray<Text3dPtr>& dropped,
-                         DynArray<Text3dPtr>& released,
-                         double dt);
-    void update_Flee    (Text3d& w, const Player& p,
-                         DynArray<Text3dPtr>& released, double dt);
-    void update_Stunned (Text3d& w, DynArray<Text3dPtr>& released, double dt);
+    void update_Idle   (Text3d& w, const Player& p, int active_ai_count, double dt);
+    void update_Attack (Text3d& w, const Player& p, CarriedLine& carried,
+                        DynArray<Text3dPtr>& all_words,
+                        DynArray<Text3dPtr>& dropped,
+                        DynArray<Text3dPtr>& released,
+                        double dt);
+    void update_Flee   (Text3d& w, const Player& p,
+                        DynArray<Text3dPtr>& released, double dt);
+    void update_Stunned(Text3d& w, DynArray<Text3dPtr>& released, double dt);
     void update_StolenTrail(Text3d& w, double dt);
 
     void release_StolenWords(Text3d& w, DynArray<Text3dPtr>& out);
 
-    void enter_Attack   (Text3d& w);
-    void enter_Flee     (Text3d& w);
-    void enter_Stunned  (Text3d& w);
-    void enter_Idle     (Text3d& w);
+    void enter_Attack  (Text3d& w);
+    void enter_Flee    (Text3d& w);
+    void enter_Stunned (Text3d& w);
+    void enter_Idle    (Text3d& w);
 
     void apply_Locomotion(Text3d& w, const Vec3& target_vel, double dt);
 public:
-    void set_MaxActiveAi(int n)    { _max_active_ai = n; }
+    void set_MaxActiveAi (int n)    { _max_active_ai = n; }
     void update(DynArray<Text3dPtr>& words,
                 const Player& player,
                 CarriedLine& carried,
diff --git a/src/systems/word_anim.hpp b/src/systems/word_anim.hpp
index c3242b1..fa10fde 100644
--- a/src/systems/word_anim.hpp
+++ b/src/systems/word_anim.hpp
@@ -5,20 +5,23 @@
 #include "utils/globals.hpp"
 
 struct WordAnim_Add {
+    bool   complete{false};
+    double yaw_start;
+    double yaw_target;
+    double pitch_start  {0.0};
+    double pitch_target {0.0};
+    double t{0.0};
+
     Text3dPtr word;
-    Vec3      start_pos;
-    Vec3      target_pos;
-    double    yaw_start;
-    double    yaw_target;
-    double    pitch_start  {0.0};
-    double    pitch_target {0.0};
-    double    t{0.0};
-    bool      complete{false};
+
+    Vec3 start_pos;
+    Vec3 target_pos;
 };
 
 struct WordAnim_Drop {
+    bool complete{false};
+
     Text3dPtr word;
-    bool      complete{false};
 };
 
 class WordAnimSystem {
diff --git a/src/text_3d/text_label.hpp b/src/text_3d/text_label.hpp
index ffddd34..1f14ef1 100644
--- a/src/text_3d/text_label.hpp
+++ b/src/text_3d/text_label.hpp
@@ -15,26 +15,28 @@ class TextLabel {
         Anchor,
     };
 
+    bool  _collidable { true };
+    bool  _billboard  { true };
+
+    float _style_depth{ 0.06f };
+    float _style_scale{ 0.003f };
+    float _face_yaw   { 0.0f };
+    SDL_FColor  _style_color{ 1.0f, 1.0f, 0.0f, 1.0f };
+
+    const Vec3* _anchor_ptr   { nullptr };
+    Vec3        _centre       {};
+    Vec3        _attach_offset{};
+
+    std::string  _current_text;
+
+    PositionMode _mode { PositionMode::Absolute };
+
     DynArray<Text3dPtr> _words;
     DynArray<double>    _offsets;
     DynArray<Vec3>      _anchor_pos;
     DynArray<Vec3>      _jiggle_off;
     DynArray<Vec3>      _jiggle_vel;
 
-    PositionMode  _mode           { PositionMode::Absolute };
-    Vec3          _centre         {};
-    const Vec3*   _anchor_ptr     { nullptr };
-    Vec3          _attach_offset  {};
-
-    bool   _billboard  { true };
-    float  _face_yaw   { 0.0f };
-
-    SDL_FColor  _style_color  { 1.0f, 1.0f, 0.0f, 1.0f };
-    float       _style_depth  { 0.06f };
-    float       _style_scale  { 0.003f };
-
-    bool        _collidable   { true };
-    std::string _current_text;
 
     void rebuild(const std::string& text);
     Vec3 current_Centre() const;
@@ -42,22 +44,22 @@ class TextLabel {
 public:
     TextLabel() = default;
 
-    void set_Text(const std::string& text);
-    void set_Text(const std::string& text,
-                  SDL_FColor color, float depth, float scale);
-    void clear();
-    void set_Centre(const Vec3& p);
-    void attach_To(const Vec3* anchor, const Vec3& offset = {});
-    void detach();
-    void set_Billboard(bool on) { _billboard = on; }
-    void set_FaceYaw(float y)  { _face_yaw = y;  }
-    void set_Style(SDL_FColor color, float depth, float scale);
+    void attach_To     (const Vec3* anchor, const Vec3& offset = {});
+    void detach        ();
+
+    void set_Centre    (const Vec3& p);
+    void set_Billboard (bool on) { _billboard = on; }
+    void set_FaceYaw   (float y)  { _face_yaw = y;  }
+    void set_Style     (SDL_FColor color, float depth, float scale);
     void set_Collidable(bool on);
-    void update(double dt, const Camera& camera);
-    void draw(GlRenderer& gl, const Camera& camera, const Light& light) const;
+    void set_Text      (const std::string& text);
+    void set_Text      (const std::string& text,
+                        SDL_FColor color, float depth, float scale);
+    void clear         ();
+    void update        (double dt, const Camera& camera);
+    void draw          (GlRenderer& gl, const Camera& camera, const Light& light) const;
+    void kick          (size_t word_index, const Vec3& projectile_vel, const Vec3& normal);
+    bool empty         () const { return _words.empty(); }
     const DynArray<Text3dPtr>&
-         words() const { return _words; }
-    void kick(size_t word_index, const Vec3& projectile_vel, const Vec3& normal);
-
-    bool empty() const { return _words.empty(); }
+         words         () const { return _words; }
 };
diff --git a/src/utils/carry_math.hpp b/src/utils/carry_math.hpp
index fbeee20..2fb88c9 100644
--- a/src/utils/carry_math.hpp
+++ b/src/utils/carry_math.hpp
@@ -13,17 +13,14 @@ namespace CarryMath
     static constexpr double CARRY_DIST = 1.2;
     static constexpr double CARRY_GAP  = 0.18;
 
-    // Hold the carried line in the player's right hand: the anchor (where the
-    // most-recently-picked word sits) is pushed off the crosshair to the lower
-    // right. The rest of the line trails left from there, unchanged.
-    static constexpr double CARRY_HAND_RIGHT = 0.45;   // toward the right hand
-    static constexpr double CARRY_HAND_DOWN  = 0.35;   // below the crosshair
+    static constexpr double CARRY_HAND_RIGHT = 0.45;
+    static constexpr double CARRY_HAND_DOWN  = 0.35;
 
 
-    Vec3   line_Anchor(const Camera& camera, double carry_dist);
-
-    DynArray<Vec3> slot_Positions(const CarriedLine& carried,
-                                     const Vec3& anchor,
-                                     const Vec3& right,
-                                     double carry_gap);
+    Vec3 line_Anchor(const Camera& camera, double carry_dist);
+    DynArray<Vec3>
+         slot_Positions(const CarriedLine& carried,
+                        const Vec3& anchor,
+                        const Vec3& right,
+                        double carry_gap);
 }
diff --git a/src/utils/config.hpp b/src/utils/config.hpp
index 181ef0b..07604b0 100644
--- a/src/utils/config.hpp
+++ b/src/utils/config.hpp
@@ -129,6 +129,7 @@ static constexpr double underline_thick  = 0.005; // half-height of the underlin
 static constexpr double line_number_gap   = 1.2;    // clear space between code and the numbers
 static constexpr float  line_number_scale = 0.003f;
 static constexpr double bounce_impulse    = 2.2;  // upward kick when a number is shot
+static constexpr double merge_top_margin  = 0.20; // how much space to the top to merge with current line
 }
 
 
diff --git a/src/utils/frame_profiler.hpp b/src/utils/frame_profiler.hpp
index 9e2c598..8ef699e 100644
--- a/src/utils/frame_profiler.hpp
+++ b/src/utils/frame_profiler.hpp
@@ -46,20 +46,22 @@ inline double perf_to_ms(Uint64 ticks) {
 
 struct Zone {
     const char* name      {nullptr};
+    uint32_t    calls     {0};      // calls across the report window
     Uint64      total     {0};      // accumulated ticks across the report window
     Uint64      frame_sum {0};      // ticks this frame (so we can also track peak)
     Uint64      peak      {0};      // peak per-frame ticks in window
-    uint32_t    calls     {0};      // calls across the report window
 };
 
 struct Frame {
     static constexpr size_t MAX_ZONES = 32;
-    std::array<Zone, MAX_ZONES> zones{};
+    bool     started        {false};
     size_t   num_zones      {0};
     uint32_t frames_in_win  {0};
+
     Uint64   window_start   {0};   // perf-counter ticks
     Uint64   report_every_ns{1'000'000'000ull};  // 1 second
-    bool     started        {false};
+
+    std::array<Zone, MAX_ZONES> zones{};
 
     Zone* find_or_add(const char* name) {
         // Pointer-equality lookup — string literals dedupe naturally.
diff --git a/src/utils/math.hpp b/src/utils/math.hpp
index 0534458..4171cb9 100644
--- a/src/utils/math.hpp
+++ b/src/utils/math.hpp
@@ -26,10 +26,14 @@ struct Vec3 {
     Vec3() = default;
     Vec3(double x_, double y_, double z_) : x(x_), y(y_), z(z_) {}
 
-    Vec3& operator+=(const Vec3& other) { x += other.x; y += other.y; z += other.z; return *this; }
-    Vec3& operator-=(const Vec3& other) { x -= other.x; y -= other.y; z -= other.z; return *this; }
-    Vec3& operator*=(double s)          { x *= s;       y *= s;       z *= s;       return *this; }
-    Vec3& operator/=(double s)          { x /= s;       y /= s;       z /= s;       return *this; }
+    Vec3& operator+=(const Vec3& other)
+            { x += other.x; y += other.y; z += other.z; return *this; }
+    Vec3& operator-=(const Vec3& other)
+            { x -= other.x; y -= other.y; z -= other.z; return *this; }
+    Vec3& operator*=(double s)
+            { x *= s; y *= s; z *= s; return *this; }
+    Vec3& operator/=(double s)
+            { x /= s; y /= s; z /= s; return *this; }
 };
 
 inline Vec3 operator+(Vec3 a, const Vec3& b) { return a += b; }
diff --git a/src/wall/editor_wall.cpp b/src/wall/editor_wall.cpp
index 7bc8d03..088c156 100644
--- a/src/wall/editor_wall.cpp
+++ b/src/wall/editor_wall.cpp
@@ -30,11 +30,118 @@ EditorWall::find_NearestLine(double y)
 }
 
 
+EditorWall::Target
+EditorWall::resolve_Target(double y) const
+{
+    DynArray<size_t> order;
+    for (size_t i = 0; i < _lines.size(); ++i)
+        if (!_lines[i].words.empty()) order.push_back(i);
+
+    if (order.empty())
+        return { Target::Insert, 0, y };
+
+    std::sort(order.begin(), order.end(),
+        [&](size_t a, size_t b){ return _lines[a].y > _lines[b].y; });
+
+    auto glyph_top = [&](size_t li) {
+        return _lines[li].y
+               + (double)_lines[li].words.front()->half_extent_y
+               + ConfigWall::merge_top_margin;
+    };
+    auto underline = [&](size_t li) {
+        return _lines[li].y - ConfigWall::underline_drop;
+    };
+
+    // 1) Merge: aim within any line's glyph band [underline .. glyph top].
+    for (size_t li : order)
+        if (y <= glyph_top(li) && y >= underline(li))
+            return { Target::Merge, li, 0.0 };
+
+    const double LH = ConfigWall::line_height;
+
+    // 2) Above the top line's glyph → new row at the aim height, but never
+    //    closer than one LH to the current top (keep whatever wider gap the
+    //    shot asked for).
+    {
+        const size_t top = order.front();
+        if (y > glyph_top(top)) {
+            const double min_y = _lines[top].y + LH;      // closest allowed
+            const double new_y = (y > min_y) ? y : min_y; // keep the wide gap if aimed higher
+            return { Target::Insert, top, new_y, 0.0 };
+        }
+    }
+
+    // 3) Between two lines. Decide by where in the gap the aim falls:
+    //    - enough room and aimed in the middle → place AT the aim (no push)
+    //    - too close to the upper line → snap one row under it
+    //    - too close to the lower line → snap one row above it (push lo if needed)
+    for (size_t oi = 0; oi + 1 < order.size(); ++oi) {
+        const size_t hi = order[oi];
+        const size_t lo = order[oi + 1];
+        if (y < underline(hi) && y > glyph_top(lo)) {
+            const double hi_y = _lines[hi].y;
+            const double lo_y = _lines[lo].y;
+            const double gap  = hi_y - lo_y;
+
+            const double free_top = hi_y - LH;
+            const double free_bot = lo_y + LH;
+
+            // Wide gap, aimed in the open middle → place exactly at the aim,
+            // push nothing (there's room on both sides).
+            if (gap >= 2.0 * LH && y <= free_top && y >= free_bot)
+                return { Target::Insert, lo, y, 0.0 };
+
+            // Wide gap but aimed near an edge → snap to that edge, no push
+            // (the gap already has room, we just don't free-float there).
+            if (gap >= 2.0 * LH)
+                return { Target::Insert, lo, (y > free_top) ? free_top : free_bot, 0.0 };
+
+            // Tight gap (< 2*LH): only one placement fits. Sit one row under hi
+            // and push lo+below down by the deficit to restore LH spacing.
+            const double deficit = 2.0 * LH - gap;
+            return { Target::Insert, lo, free_top, deficit };
+        }
+    }
+
+    // 4) Below the bottom line → new row at the aim height, but never closer
+    //    than one LH below the current bottom.
+    {
+        const size_t bot = order.back();
+        const double max_y = _lines[bot].y - LH;          // closest allowed (further down = smaller y)
+        const double new_y = (y < max_y) ? y : max_y;     // keep the wide gap if aimed lower
+        return { Target::Insert, bot, new_y, 0.0 };
+    }
+}
+
+
+EditorWall::Line&
+EditorWall::apply_Target(const Target& t)
+{
+    if (t.kind == Target::Merge)
+        return _lines[t.line_index];
+
+    if (t.push_dist > 0.0 && t.line_index < _lines.size()) {
+        const double push_below_y = _lines[t.line_index].y;
+        for (Line& l : _lines) {
+            if (l.y <= push_below_y + 1e-6) {
+                l.y -= t.push_dist;
+                for (Text3d* w : l.words) if (w) w->pos.y -= t.push_dist;
+            }
+        }
+    }
+
+    Line fresh;
+    fresh.y = t.new_y;
+    _lines.push_back(std::move(fresh));
+    return _lines.back();
+}
+
+
 EditorWall::Placement
 EditorWall::place_OnLine(Line& line, double new_half_z, double impact_z)
 {
     if (line.words.empty())
-        return { impact_z, 0 };
+        return { 0, impact_z };
 
     const size_t n = line.words.size();
     auto left_edge  = [](const Text3d* w){ return w->pos.z + (double)w->half_extent; };
@@ -48,7 +155,7 @@ EditorWall::place_OnLine(Line& line, double new_half_z, double impact_z)
         const double flush_z = left_edge(leftmost) + ConfigWall::free_space_dist + new_half_z;
         // Aimed further LEFT than flush (larger z)? keep it. Else snap flush.
         const double z = (impact_z > flush_z) ? impact_z : flush_z;
-        return { z, 0 };   // front
+        return { 0, z };   // front
     }
 
     // Case B: right of the whole line — always flush.
@@ -57,7 +164,7 @@ EditorWall::place_OnLine(Line& line, double new_half_z, double impact_z)
         // Aimed further RIGHT than flush (smaller z)? keep it. Else snap flush.
         // Never allow larger-than-flush z (that would overlap the neighbour).
         const double z = (impact_z < flush_z) ? impact_z : flush_z;
-        return { z, n };
+        return { n, z };
     }
 
     // Case C: between two words.
@@ -68,14 +175,14 @@ EditorWall::place_OnLine(Line& line, double new_half_z, double impact_z)
             const double needed = 2.0 * new_half_z + 2.0 * ConfigWall::free_space_dist;
             const double avail  = gap_left - gap_right;
             if (avail >= needed)
-                return { (gap_left + gap_right) * 0.5, i + 1 };
+                return { i + 1, (gap_left + gap_right) * 0.5 };
             const double shift = needed - avail;
             for (size_t k = i + 1; k < n; ++k)
                 line.words[k]->pos.z -= shift;
-            return { gap_left - ConfigWall::free_space_dist - new_half_z, i + 1 };
+            return { i + 1, gap_left - ConfigWall::free_space_dist - new_half_z };
         }
     }
-    return { right_edge(rightmost) - ConfigWall::free_space_dist - new_half_z, n };
+    return { n, right_edge(rightmost) - ConfigWall::free_space_dist - new_half_z };
 }
 
 
@@ -84,21 +191,16 @@ EditorWall::on_WordPlaced(Text3d* word, const Vec3& impact_pos)
 {
     if (!word) return impact_pos;
 
-    Line* target = find_NearestLine(impact_pos.y);
-    if (!target) {
-        Line fresh;
-        fresh.y = impact_pos.y;
-        _lines.push_back(std::move(fresh));
-        target = &_lines.back();
-    }
+    Target  t      = resolve_Target(impact_pos.y);
+    Line&   target = apply_Target(t);
 
     const double new_half_z = word->half_extent;
-    const Placement p = place_OnLine(*target, new_half_z, impact_pos.z);
+    const Placement p = place_OnLine(target, new_half_z, impact_pos.z);
 
-    Vec3 snapped { impact_pos.x, target->y, p.z };
+    Vec3 snapped { impact_pos.x, target.y, p.z };
 
-    target->words.insert(
-        target->words.begin() + static_cast<std::ptrdiff_t>(p.insert_index),
+    target.words.insert(
+        target.words.begin() + static_cast<std::ptrdiff_t>(p.insert_index),
         word);
 
     return snapped;
@@ -125,13 +227,9 @@ EditorWall::on_LinePlaced(const DynArray<Text3d*>& words_in, const Vec3& impact)
         if (i + 1 < ws.size()) block_w += ConfigWall::free_space_dist;
     }
 
-    Line* target = find_NearestLine(impact.y);
-    if (!target) {
-        Line fresh;
-        fresh.y = impact.y;
-        _lines.push_back(std::move(fresh));
-        target = &_lines.back();
-    }
+    Target t       = resolve_Target(impact.y);
+    Line&  targetR = apply_Target(t);
+    Line*  target  = &targetR;
 
     auto left_edge  = [](const Text3d* w){ return w->pos.z + (double)w->half_extent; };
     auto right_edge = [](const Text3d* w){ return w->pos.z - (double)w->half_extent; };
@@ -341,9 +439,9 @@ EditorWall::number_Hitboxes() const
         const double hz = (double)t->half_extent   + PAD;   // along wall (z)
         const double hy = (double)t->half_extent_y + PAD;   // vertical
         const Vec3   c  = t->pos;
-        out.push_back({ Vec3{ c.x - DEPTH_X, c.y - hy, c.z - hz },
-                        Vec3{ c.x + DEPTH_X, c.y + hy, c.z + hz },
-                        HIT_BASE + i });
+        out.push_back({ HIT_BASE + i,
+                        Vec3{ c.x - DEPTH_X, c.y - hy, c.z - hz },
+                        Vec3{ c.x + DEPTH_X, c.y + hy, c.z + hz } });
     };
 
     for (size_t i = 0; i < _lines.size(); ++i) {
@@ -445,3 +543,37 @@ EditorWall::draw(GlRenderer& gl, const Camera& cam, const Light& light) const
         if (l.num_right) l.num_right->draw(gl, cam, light);
     }
 }
+
+
+double
+EditorWall::get_GridOrigin() const
+{
+    double origin = 0.0;
+    bool   found  = false;
+    for (const Line& l : _lines) {
+        if (l.words.empty()) continue;
+        if (!found || l.y > origin) { origin = l.y; found = true; }
+    }
+    return found ? origin : 0.0;
+}
+
+double
+EditorWall::snap_RowY(double y) const
+{
+    const double LH     = ConfigWall::line_height;
+    const double origin = get_GridOrigin();
+    const double row    = std::round((y - origin) / LH);
+    return origin + row * LH;
+}
+
+bool
+EditorWall::row_IsEmpty(double y) const
+{
+    const double LH     = ConfigWall::line_height;
+    const double slot_y = snap_RowY(y);
+    for (const Line& l : _lines) {
+        if (l.words.empty()) continue;
+        if (SDL_fabs(l.y - slot_y) < LH * 0.5) return false;
+    }
+    return true;
+}
diff --git a/src/wall/editor_wall.hpp b/src/wall/editor_wall.hpp
index 820b056..bb88ab1 100644
--- a/src/wall/editor_wall.hpp
+++ b/src/wall/editor_wall.hpp
@@ -10,108 +10,66 @@ class Camera;
 class GlRenderer;
 
 
-// EditorWall: tracks words placed on the editor (NegX) wall, organizes
-// them into horizontal lines with snap-to-row positioning, and renders
-// thin underline guides below each populated row.
-//
-// Layout convention: editor wall is at -X. Wall surface is the YZ plane,
-// so:
-//   pos.y  → vertical position (which row)
-//   pos.z  → horizontal position along that row
-//   pos.x  → fixed at -world_scale + small offset
-//
-// Snap rules:
-//   - Throws within ConfigWall::snap_threshold_y of an existing line's y → snap to it
-//   - Throws outside threshold → start a new line at the throw's y
-//   - Words on a line append to the right of the rightmost word with
-//     a fixed horizontal gap (ConfigWall::word_gap).
-//
-// All thresholds expressed in world units. Tunable below.
 class EditorWall
 {
     struct Line {
-        double                 y      {0.0};
-        DynArray<Text3d*>   words;            // left-to-right
+        int    shown_number {-1};
+        double y            {0.0};
+        double bounce_off   {0.0};
+        double bounce_vel   {0.0};
 
-        // Owned line-number labels, one each end. Deliberately NOT stored in
-        // wall_texts: that keeps them out of source extraction and means the
-        // shoot-down path can never make them fall off the wall.
         Text3dPtr num_left;
         Text3dPtr num_right;
-        int       shown_number {-1};          // currently rendered digit(s)
-        double    bounce_off    {0.0};         // transient vertical jiggle
-        double    bounce_vel    {0.0};
+
+        DynArray<Text3d*>   words; // left-to-right
     };
 
-    // Result of placing a word: the final Z, and the index in the line's
-    // word vector where it should be inserted (visual left→right order).
     struct Placement {
-        double z;
         size_t insert_index;
+        double z;
+    };
+
+    struct Target {
+        enum Kind { Merge, Insert } kind;
+        size_t   line_index;
+        double   new_y;
+        double   push_dist {0.0};
     };
 
     DynArray<Line> _lines;
-    Line* find_NearestLine(double y);
 
-    // Decide where the new word lands on the line and shift neighbours if a
-    // between-words insert needs room. Mutates the line's existing word
-    // positions when a shift is required.
-    Placement place_OnLine(Line& line, double new_half_z, double impact_z);
+    Line*     find_NearestLine (double y);
+    Placement place_OnLine     (Line& line, double new_half_z, double impact_z);
+    double    get_GridOrigin   () const;
+    Target    resolve_Target   (double y) const;
+    Line&     apply_Target     (const Target& t);
 public:
-    // ── World-tuning constants (public, edit to taste) ───────────────────
-    // Hit-id encoding: GameState mixes these into its wall-text hit list, and
-    // tells them apart on the way back. (size_t is 64-bit on the target.)
-    static constexpr size_t HIT_BASE = size_t(1) << 32;
+    static constexpr size_t
+                  HIT_BASE = size_t(1) << 32;
     static bool   is_LineNumberHit(size_t idx) { return idx >= HIT_BASE; }
-    static size_t line_Of          (size_t idx) { return idx - HIT_BASE; }
+    static size_t line_Of         (size_t idx) { return idx - HIT_BASE; }
 
     struct NumberHit {
+        size_t index;
         Vec3   aabb_min;
         Vec3   aabb_max;
-        size_t index;     // HIT_BASE + line id
     };
 
-    // ── State management ────────────────────────────────────────────────
-
-    // Reset everything. Call on scene teardown.
-    void clear();
-
-    // Per-frame: renumber rows top→bottom, (re)create/position the orange
-    // number labels, tilt them toward the eye, and advance bounce springs.
-    void update(double dt, const Vec3& eye_pos);
-
-    // Register a word that just hit the editor wall. May modify word->pos
-    // to snap it (current behavior: snap is computed BEFORE the word is
-    // visible; smooth animation is a later step).
-    //
-    // Returns the snapped pos so the caller can apply it.
-    Vec3 on_WordPlaced(Text3d* word, const Vec3& impact_pos);
-
-    // Place a whole shot line (several words) as ONE contiguous block at the
-    // aim point, so a multi-word line merges between two existing words
-    // instead of each word scattering to its own slot. Sets each word's pos
-    // directly (caller doesn't re-apply). Pass the words in any order and the
-    // shot line's centroid as the impact.
-    void on_LinePlaced(const DynArray<Text3d*>& words, const Vec3& impact);
-
-    // Deregister a word that's been picked up off the wall. Safe if the
-    // word wasn't tracked (e.g. it was on a non-editor wall).
-    void on_WordRemoved(Text3d* word);
-
-    // Render underlines for every populated row.
-    void draw(GlRenderer& gl, const Camera& cam, const Light& light) const;
-
-    // Diagnostics.
-    size_t line_Count() const { return _lines.size(); }
+    void clear  ();
+    void update (double dt, const Vec3& eye_pos);
+    void draw   (GlRenderer& gl, const Camera& cam, const Light& light) const;
 
     void scroll(double delta) { for (Line& l : _lines) l.y += delta; }
+    void on_LinePlaced (const DynArray<Text3d*>& words, const Vec3& impact);
+    void on_WordRemoved(Text3d* word);
+    Vec3 on_WordPlaced (Text3d* word, const Vec3& impact_pos);
 
-    // ── Line-number shooting ─────────────────────────────────────────────
-    // AABBs for every number label, tagged with an encoded hit id, for
-    // GameState to append to its projectile wall-text hit list.
-    DynArray<NumberHit> number_Hitboxes() const;
-    // World-Y shift needed to bring the given line to eye height.
+    size_t line_Count       () const { return _lines.size(); }
+    bool   row_IsEmpty(double y) const;
+    double snap_RowY  (double y) const;
     double scroll_DeltaToEye(size_t line_id, double eye_y) const;
-    // Kick a line's numbers so they bounce in place (does not move the line).
-    void   bounce(size_t line_id);
+    void   bounce           (size_t line_id);
+
+    DynArray<NumberHit>
+           number_Hitboxes() const;
 };
diff --git a/src/wall/text_wall.hpp b/src/wall/text_wall.hpp
index 2f84c38..a359ca6 100644
--- a/src/wall/text_wall.hpp
+++ b/src/wall/text_wall.hpp
@@ -13,16 +13,11 @@ struct TextWall
     Wall::Role     role;
     Text3dPtr      text3d;
 
+    void draw         (GlRenderer& gl, const Camera& camera, const Light& light) const;
     static TextWallPtr
-    from_Drop(Text3dPtr src);
-
+         from_Drop    (Text3dPtr src);
     static std::string
-    extract_Text(const TextWallList&, Wall::Role, double line_threshold = 0.3);
-
-    void
-    draw(GlRenderer& gl, const Camera& camera, const Light& light) const;
-
+         extract_Text (const TextWallList&, Wall::Role, double line_threshold = 0.3);
     static void
-    show_TokenInfo(TextWallList& texts,
-                   const std::string& token_text);
+         show_TokenInfo(TextWallList& texts, const std::string& token_text);
 };
diff --git a/src/wall/wall_label.hpp b/src/wall/wall_label.hpp
index 8d5a994..96af1bc 100644
--- a/src/wall/wall_label.hpp
+++ b/src/wall/wall_label.hpp
@@ -8,37 +8,22 @@
 class GlRenderer;
 class Camera;
 
-// WallLabel: a large name-plate hovering in front of each wall at a height
-// where the player has to look up slightly. Shooting it jiggles the words
-// via TextLabel::kick() but they spring back to their anchor position.
-//
-// Orientation: fixed yaw (faces the room, same as wall text), pitch tilts
-// up/down toward the player's eye each frame — identical to the editor
-// wall text behaviour.
 struct WallLabel
 {
-    Wall::Role  role;
-    TextLabel   label;
-    Vec3        anchor;    // world-space centre (never changes after generate())
-    float       face_yaw;  // fixed yaw matching the wall's orientation
+    Wall::Role role;
+    float      face_yaw;
+    Vec3       anchor;
+    TextLabel  label;
 
-    // Factory: creates one label per wall role, positioned at the given scale.
     static DynArray<WallLabel>
     generate_All(float world_scale);
 
-    // Call when a projectile hits word at word_index with the given velocity.
-    // The push direction is the wall's own inward normal (derived from role),
-    // so the caller doesn't need a per-hit surface normal.
-    void on_Hit(size_t word_index, const Vec3& projectile_vel);
-
-    // Per-frame: tilt label toward eye and update jiggle spring.
-    void update(double dt, const Camera& camera);
-
-    void draw(GlRenderer& gl, const Camera& camera, const Light& light) const;
+    void on_Hit (size_t word_index, const Vec3& projectile_vel);
+    void update (double dt, const Camera& camera);
+    void draw   (GlRenderer& gl, const Camera& camera, const Light& light) const;
 };
 
-// Free helper: the inward-facing surface normal for a wall position (points
-// from the wall into the room). kick() uses this as the push direction.
+
 inline Vec3 wall_InwardNormal(Wall::Position pos)
 {
     switch (pos) {
diff --git a/src/wall/wall_surfaces.hpp b/src/wall/wall_surfaces.hpp
index 6150a17..100a25d 100644
--- a/src/wall/wall_surfaces.hpp
+++ b/src/wall/wall_surfaces.hpp
@@ -11,24 +11,16 @@
 class Camera;
 
 
-// WallSurfaces: four solid colored quads, one per wall, each tinted to its
-// role's purpose. The editor wall additionally renders horizontal "paper
-// rule" lines via the shader's wall-pattern path.
-//
-// Layout per wall:
-//   Editor    (-X)  cream + horizontal blue rules     "writing surface"
-//   Program   (+X)  warm ivory                        "spec / document"
-//   Info      (+Z)  soft slate blue                   "info card"
-//   Compiler  (-Z)  near-black indigo                 "terminal"
 class WallSurfaces
 {
 public:
     struct WallQuad {
+        bool       is_editor;
         Wall::Role role;
+        SDL_FColor color;
+
         DynArray<float> verts;
         DynArray<int>   indices;
-        SDL_FColor      color;
-        bool            is_editor;
     };
 
     void generate(float world_scale);