Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / commits / 3ce4d2b

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

Reorder member vars

Christian Immanuel · 2026-06-12 11:09 · 3ce4d2b784273ded5a07fef42d6d9bf289779a86

 assets/sandbox.c                     |   4 +-
 assets/sandbox.c.last_ok             |   4 +-
 src/core/game.hpp                    |   2 +-
 src/entities/camera.hpp              |  11 ++-
 src/entities/game_object.hpp         |  24 ++---
 src/entities/plate.hpp               |   8 +-
 src/entities/player.cpp              |   8 +-
 src/entities/player.hpp              |  13 +--
 src/entities/pult.hpp                |  14 +--
 src/entities/world.hpp               |   4 +-
 src/levels/director.hpp              |  12 ++-
 src/levels/level.hpp                 |  23 ++---
 src/levels/level/char_copy.cpp       | 146 ++++++++++++++--------------
 src/levels/level/sandbox.cpp         |  14 +--
 src/levels/level/tutorial.cpp        | 182 +++++++++++++++++------------------
 src/levels/scene.hpp                 |  53 +++++-----
 src/rendering/floor_surface.hpp      |   8 +-
 src/rendering/gl_renderer.hpp        |  28 +++---
 src/states/game_context.hpp          |   8 +-
 src/states/game_state.cpp            |  16 +--
 src/states/game_state.hpp            |  54 +++++------
 src/states/menu_state.hpp            |  16 +--
 src/systems/aim.hpp                  |   4 +-
 src/systems/celebration_system.hpp   |   4 +-
 src/systems/collision.hpp            |  18 ++--
 src/systems/compiler_system.hpp      |   8 +-
 src/systems/cpp_highlight_system.hpp |  14 +--
 src/systems/dyn_loader_system.hpp    |   9 +-
 src/systems/freelook_system.hpp      |   2 +-
 src/systems/hint_system.hpp          |   2 +-
 src/systems/input_system.hpp         |  32 +++---
 src/systems/particle_system.hpp      |  12 +--
 src/systems/physics_system.cpp       |  14 ++-
 src/systems/physics_system.hpp       |   9 +-
 src/systems/pick_system.hpp          |   9 +-
 src/systems/program_system.hpp       |  10 +-
 src/systems/projectile_system.hpp    |  56 ++++++-----
 src/systems/pult_system.hpp          |   4 +-
 src/systems/wall_connection.hpp      |  15 +--
 src/systems/word_anim_system.hpp     |  11 ++-
 src/text/handler.hpp                 |  11 ++-
 src/text/mesh.hpp                    |   2 +-
 src/text/text.hpp                    |  11 ++-
 src/text/vim_buffer.hpp              |  36 +++----
 src/utils/config.hpp                 |   3 +-
 src/utils/light.hpp                  |  15 ++-
 src/wall/label.hpp                   |   6 +-
 src/wall/wall.hpp                    |   7 +-
 48 files changed, 500 insertions(+), 476 deletions(-)

diff --git a/assets/sandbox.c b/assets/sandbox.c
index acc21b5..ac2a96b 100644
--- a/assets/sandbox.c
+++ b/assets/sandbox.c
@@ -25,7 +25,7 @@ int game_build ( DynObject * out , int max ) {
 o = out ; n = 0 ;
 text ( 0 , - 15 , 5 , "hi" ) ;
 box ( 0 , - 16 , 5 , 2 , 1 , 2 , 0 ) ;
-for ( int i = 0 ; i < 10 ; i ++ )
-box ( 2 * i , - 16 , 5 , 2 , 0 + i , 2 , 0 ) ;
+for ( int i = 0 ; i < 100 ; i ++ )
+box ( 0 + i , - 16 , 5 + i % 4 , 1 , 1 + i * 0.5 , 1 , 0 ) ;
 return n ;
 }
diff --git a/assets/sandbox.c.last_ok b/assets/sandbox.c.last_ok
index acc21b5..ac2a96b 100644
--- a/assets/sandbox.c.last_ok
+++ b/assets/sandbox.c.last_ok
@@ -25,7 +25,7 @@ int game_build ( DynObject * out , int max ) {
 o = out ; n = 0 ;
 text ( 0 , - 15 , 5 , "hi" ) ;
 box ( 0 , - 16 , 5 , 2 , 1 , 2 , 0 ) ;
-for ( int i = 0 ; i < 10 ; i ++ )
-box ( 2 * i , - 16 , 5 , 2 , 0 + i , 2 , 0 ) ;
+for ( int i = 0 ; i < 100 ; i ++ )
+box ( 0 + i , - 16 , 5 + i % 4 , 1 , 1 + i * 0.5 , 1 , 0 ) ;
 return n ;
 }
diff --git a/src/core/game.hpp b/src/core/game.hpp
index 949de2b..9112de0 100644
--- a/src/core/game.hpp
+++ b/src/core/game.hpp
@@ -11,10 +11,10 @@
 
 class Game
 {
-    SDL_Window*   _window     {};
     GlRendererPtr _gl_renderer{};
     cpputils::unique_ptr<IState>
                   _state;
+    SDL_Window*   _window     {};
 public:
     ~Game();
 
diff --git a/src/entities/camera.hpp b/src/entities/camera.hpp
index 94be3be..ac1030b 100644
--- a/src/entities/camera.hpp
+++ b/src/entities/camera.hpp
@@ -8,15 +8,16 @@
 
 struct Camera
 {
-    int32_t  pitch  {};
-    uint32_t yaw    {};
-    double   yaw_r  {};
-    double   pitch_r{};
-
     Vec3 pos  {};
     Vec3 fwd  {};
     Vec3 right{};
 
+    double   yaw_r  {};
+    double   pitch_r{};
+
+    int32_t  pitch  {};
+    uint32_t yaw    {};
+
     void sync_FromPlayer(const Player& player)
     {
         pos   = player.prev_pos;
diff --git a/src/entities/game_object.hpp b/src/entities/game_object.hpp
index e3a1888..53c6da8 100644
--- a/src/entities/game_object.hpp
+++ b/src/entities/game_object.hpp
@@ -35,14 +35,14 @@ struct CollisionShape
 
 struct RigidBody
 {
-    bool        hover_disturbed{false};
+    Vec3        vel  {};
+    Vec3        force{};
     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{};
+    bool        hover_disturbed{false};
     PhysicsMode mode {PhysicsMode::Hover};
 
 
@@ -88,23 +88,23 @@ struct RigidBody
 
 struct GameObject
 {
-    bool  carriable    {true};
-    bool  interactable {true};
-    bool  solid        {true};
+    CollisionShape shape;
+    RigidBody      body;
+    ObjectType     type{ObjectType::Text};
 
-    float yaw {0.0f};
     Vec3  pos {};
 
-    ObjectType     type{ObjectType::Text};
-    CollisionShape shape;
-    RigidBody      body;
+    float yaw {0.0f};
+
+    bool  carriable    {true};
+    bool  interactable {true};
+    bool  solid        {true};
 
     GameObject() {}
 
     GameObject(Vec3 p, ObjectType t, CollisionShape sh, RigidBody b,
                bool c = true, bool i = true, bool s = true)
-        : carriable(c), interactable(i), solid(s),
-          pos(p), type(t), shape(sh), body(b) {}
+        : shape(sh), body(b), type(t), pos(p), carriable(c), interactable(i), solid(s) {}
 
     virtual
          ~GameObject() = default;
diff --git a/src/entities/plate.hpp b/src/entities/plate.hpp
index 47834d2..69b3b2e 100644
--- a/src/entities/plate.hpp
+++ b/src/entities/plate.hpp
@@ -15,11 +15,13 @@ struct Plate final : public SolidBox
     static constexpr float _HALF_H   = 0.05f;
     static constexpr float _HALF_D   = 0.6f;
 
-    bool   active    { false };
-    bool   was_active{ false };
+    Vec3   base_pos  {};
+
     double bob_phase { 0.0 };
     double pulse     { 0.0 };
-    Vec3   base_pos  {};
+
+    bool   active    { false };
+    bool   was_active{ false };
 
     Plate(Vec3 world_pos)
         : SolidBox(
diff --git a/src/entities/player.cpp b/src/entities/player.cpp
index e8f70a0..a50acca 100644
--- a/src/entities/player.cpp
+++ b/src/entities/player.cpp
@@ -3,12 +3,12 @@
 
 
 Player::Player()
-       : pitch (-0x08000000),
-         yaw   (0x20000000),
+       : pos   {8.0, 0.0, 8.0},
+         vel   {0.0, 0.0, 0.0},
          radius(0.5f),
          height(1.5f),
-         pos   {8.0, 0.0, 8.0},
-         vel   {0.0, 0.0, 0.0}
+         pitch (-0x08000000),
+         yaw   (0x20000000)
 {}
 
 void
diff --git a/src/entities/player.hpp b/src/entities/player.hpp
index 02baa0b..1c4fec1 100644
--- a/src/entities/player.hpp
+++ b/src/entities/player.hpp
@@ -23,16 +23,17 @@ struct Player
 public:
     Player();
 
-    bool     on_ground{false};
-    int32_t  pitch    {};
-    uint32_t yaw      {};
-    float    radius   {0.5f};
-    float    height   {1.5f};
-
     Vec3     pos      {};
     Vec3     prev_pos {};
     Vec3     vel      {};
 
+    float    radius   {0.5f};
+    float    height   {1.5f};
+
+    int32_t  pitch    {};
+    uint32_t yaw      {};
+    bool     on_ground{false};
+
     void   compute_Movement(const FrameInput& input);
     void   add_Look        (int dx, int dy);
     void   apply_Collision (const PlayerCollision& col);
diff --git a/src/entities/pult.hpp b/src/entities/pult.hpp
index 53c93fc..63fc6a8 100644
--- a/src/entities/pult.hpp
+++ b/src/entities/pult.hpp
@@ -25,11 +25,13 @@ enum class PultAction : uint8_t {
 struct PultButton final : public SolidBox
 {
     std::string label;
+
     PultAction  action;
 
+    double press_timer { 0.0   };
+
     bool   pressed     { false };
     bool   player_on   { false };
-    double press_timer { 0.0   };
 
     PultButton(const std::string& lbl, PultAction act, Vec3 world_pos);
 
@@ -41,15 +43,15 @@ struct PultButton final : public SolidBox
 
 struct Pult : public SolidBox
 {
-    std::string name;
-    Text::Handler   label;
-    DynArray<PultButton*>
-                buttons;
-
     static constexpr float W = 0.50f;
     static constexpr float H = 0.28f;
     static constexpr float D = 0.40f;
 
+    Text::Handler   label;
+    DynArray<PultButton*>
+                buttons;
+    std::string name;
+
     float half_w { W };
     float half_d { D };
 
diff --git a/src/entities/world.hpp b/src/entities/world.hpp
index 40b2181..8d27f6b 100644
--- a/src/entities/world.hpp
+++ b/src/entities/world.hpp
@@ -11,11 +11,11 @@
 
 struct SweepResult
 {
-    Vec3 pos;
-    Vec3 vel;
     bool hit_x{};
     bool hit_y{};
     bool hit_z{};
+    Vec3 pos;
+    Vec3 vel;
 };
 
 
diff --git a/src/levels/director.hpp b/src/levels/director.hpp
index 5ae42ee..3cc7c43 100644
--- a/src/levels/director.hpp
+++ b/src/levels/director.hpp
@@ -51,14 +51,16 @@ struct StepPlate
 
 class Director
 {
-    cpputils::unique_ptr<Level::Progression>
-           _runner;
-    double _step_age { 0.0 };
-
-    StepLedger             _ledger;
     DynArray<StepPlate>    _step_plates;
     DynArray<GameObject*>  _step_scenery;
 
+    StepLedger             _ledger;
+
+    cpputils::unique_ptr<Level::Progression>
+                           _runner;
+
+    double _step_age { 0.0 };
+
     mutable const Level::Step*       _detail_built_for { nullptr };
     mutable DynArray<Text::TokenPtr> _detail_tokens;
 
diff --git a/src/levels/level.hpp b/src/levels/level.hpp
index 0adf036..c0cb580 100644
--- a/src/levels/level.hpp
+++ b/src/levels/level.hpp
@@ -30,28 +30,29 @@ namespace Level
 
     struct Step
     {
-        bool reset_carry  = false;
-        int  max_ai_words = 5;
-
-        Scene::Data scene;
+        DynArray<TokenGrant>
+                    grants{};
+        Scene::Data scene{};
         Goal::Type  goal = Goal::AllOf{};
 
-        std::string hint;
-        std::string details;
+        std::string hint{};
+        std::string details{};
+
+        bool reset_carry  = false;
+        int  max_ai_words = 5;
 
-        DynArray<TokenGrant>  grants;
     };
 
 
     struct Data
     {
-        bool dynamic_lib = false;
+        DynArray<Step> steps {};
 
-        std::string name;
-        std::string desc;
+        std::string name{};
+        std::string desc{};
         std::string source_file{};
 
-        DynArray<Step> steps;
+        bool dynamic_lib = false;
     };
 
     extern Data build_CharCopy();
diff --git a/src/levels/level/char_copy.cpp b/src/levels/level/char_copy.cpp
index 4e579de..d6ba282 100644
--- a/src/levels/level/char_copy.cpp
+++ b/src/levels/level/char_copy.cpp
@@ -6,14 +6,21 @@ 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,
+                .grants = {
+                    Level::TokenGrant::give("int"),
+                    Level::TokenGrant::give("main"),
+                    Level::TokenGrant::give("("),
+                    Level::TokenGrant::choice({ "void", "int", "char" }),
+                    Level::TokenGrant::give(")"),
+                    Level::TokenGrant::give("{"),
+                    Level::TokenGrant::give("return"),
+                    Level::TokenGrant::give("0"),
+                    Level::TokenGrant::give(";"),
+                    Level::TokenGrant::give("}"),
+                },
                 .scene        = {},
                 .goal         = Goal::CompilePattern{ "int main ( void ) { return 0 ; }" },
                 .hint    = "Build a working main()",
@@ -28,23 +35,28 @@ Goal:
   }
 
 Grab tokens and arrange them on the editor wall.)",
+                .reset_carry  = false,
+                .max_ai_words = 3,
+            },
+            // ── Step 1: copy_str signature ────────────────────────────────
+            {
                 .grants = {
-                    Level::TokenGrant::give("int"),
-                    Level::TokenGrant::give("main"),
+                    Level::TokenGrant::give("void"),
+                    Level::TokenGrant::give("copy_str"),
                     Level::TokenGrant::give("("),
-                    Level::TokenGrant::choice({ "void", "int", "char" }),
+                    Level::TokenGrant::choice({ "char", "int", "float" }),
+                    Level::TokenGrant::give("src"),
+                    Level::TokenGrant::give("["),
+                    Level::TokenGrant::give("]"),
+                    Level::TokenGrant::give(","),
+                    Level::TokenGrant::give("char"),
+                    Level::TokenGrant::give("dst"),
+                    Level::TokenGrant::give("["),
+                    Level::TokenGrant::give("]"),
                     Level::TokenGrant::give(")"),
                     Level::TokenGrant::give("{"),
-                    Level::TokenGrant::give("return"),
-                    Level::TokenGrant::give("0"),
-                    Level::TokenGrant::give(";"),
                     Level::TokenGrant::give("}"),
                 },
-            },
-            // ── Step 1: copy_str signature ────────────────────────────────
-            {
-                .reset_carry  = false,
-                .max_ai_words = 3,
                 .scene        = {},
                 .goal         = Goal::CompilePattern{ "void copy_str ( char src [ ] , char dst [ ] ) { }" },
                 .hint    = "Declare the copy_str function",
@@ -62,28 +74,16 @@ Goal:
   int main(void) { ... }
 
 Place this BEFORE main.)",
-                .grants = {
-                    Level::TokenGrant::give("void"),
-                    Level::TokenGrant::give("copy_str"),
-                    Level::TokenGrant::give("("),
-                    Level::TokenGrant::choice({ "char", "int", "float" }),
-                    Level::TokenGrant::give("src"),
-                    Level::TokenGrant::give("["),
-                    Level::TokenGrant::give("]"),
-                    Level::TokenGrant::give(","),
-                    Level::TokenGrant::give("char"),
-                    Level::TokenGrant::give("dst"),
-                    Level::TokenGrant::give("["),
-                    Level::TokenGrant::give("]"),
-                    Level::TokenGrant::give(")"),
-                    Level::TokenGrant::give("{"),
-                    Level::TokenGrant::give("}"),
-                },
+                .reset_carry  = false,
+                .max_ai_words = 3,
             },
             // ── Step 2: loop counter ──────────────────────────────────────
             {
-                .reset_carry  = false,
-                .max_ai_words = 3,
+                .grants = {
+                    Level::TokenGrant::choice({ "int", "float", "char" }),
+                    Level::TokenGrant::give("i"),
+                    Level::TokenGrant::give(";"),
+                },
                 .scene        = {},
                 .goal         = Goal::CompilePattern{ "void copy_str ( char src [ ] , char dst [ ] ) { int i ; }" },
                 .hint    = "Add a loop counter inside copy_str",
@@ -95,16 +95,19 @@ Goal (inside copy_str):
 
 You only need to add ONE line. Place it between the { and }
 of copy_str.)",
-                .grants = {
-                    Level::TokenGrant::choice({ "int", "float", "char" }),
-                    Level::TokenGrant::give("i"),
-                    Level::TokenGrant::give(";"),
-                },
+                .reset_carry  = false,
+                .max_ai_words = 3,
             },
             // ── Step 3: for loop scaffold ─────────────────────────────────
             {
-                .reset_carry  = false,
-                .max_ai_words = 3,
+                .grants = {
+                    Level::TokenGrant::give("for"), Level::TokenGrant::give("("),
+                    Level::TokenGrant::give("i"), Level::TokenGrant::give("="), Level::TokenGrant::give("0"), Level::TokenGrant::give(";"),
+                    Level::TokenGrant::give("i"), Level::TokenGrant::give("<"), Level::TokenGrant::give("strlen"),
+                    Level::TokenGrant::give("("), Level::TokenGrant::give("src"), Level::TokenGrant::give(")"), Level::TokenGrant::give(";"),
+                    Level::TokenGrant::give("i"), Level::TokenGrant::give("++"), Level::TokenGrant::give(")"),
+                    Level::TokenGrant::give("{"), Level::TokenGrant::give("}"),
+                },
                 .scene        = {},
                 .goal         = Goal::CompilePattern{ "for ( i = 0 ; i < strlen ( src ) ; i ++ ) { }" },
                 .hint    = "Loop through src",
@@ -118,19 +121,17 @@ Goal:
   }
 
 This is the loop scaffold only — the body comes next.)",
-                .grants = {
-                    Level::TokenGrant::give("for"), Level::TokenGrant::give("("),
-                    Level::TokenGrant::give("i"), Level::TokenGrant::give("="), Level::TokenGrant::give("0"), Level::TokenGrant::give(";"),
-                    Level::TokenGrant::give("i"), Level::TokenGrant::give("<"), Level::TokenGrant::give("strlen"),
-                    Level::TokenGrant::give("("), Level::TokenGrant::give("src"), Level::TokenGrant::give(")"), Level::TokenGrant::give(";"),
-                    Level::TokenGrant::give("i"), Level::TokenGrant::give("++"), Level::TokenGrant::give(")"),
-                    Level::TokenGrant::give("{"), Level::TokenGrant::give("}"),
-                },
+                .reset_carry  = false,
+                .max_ai_words = 3,
             },
             // ── Step 4: copy the character ────────────────────────────────
             {
-                .reset_carry  = false,
-                .max_ai_words = 3,
+                .grants = {
+                    Level::TokenGrant::give("dst"), Level::TokenGrant::give("["), Level::TokenGrant::give("i"), Level::TokenGrant::give("]"),
+                    Level::TokenGrant::give("="),
+                    Level::TokenGrant::give("src"), Level::TokenGrant::give("["), Level::TokenGrant::give("i"), Level::TokenGrant::give("]"),
+                    Level::TokenGrant::give(";"),
+                },
                 .scene        = {},
                 .goal         = Goal::CompilePattern{ "dst [ i ] = src [ i ] ;" },
                 .hint    = "Copy one character per iteration",
@@ -141,30 +142,11 @@ copies the i-th character on each pass through the loop.
 
 Goal (inside the for loop):
   dst[i] = src[i];)",
-                .grants = {
-                    Level::TokenGrant::give("dst"), Level::TokenGrant::give("["), Level::TokenGrant::give("i"), Level::TokenGrant::give("]"),
-                    Level::TokenGrant::give("="),
-                    Level::TokenGrant::give("src"), Level::TokenGrant::give("["), Level::TokenGrant::give("i"), Level::TokenGrant::give("]"),
-                    Level::TokenGrant::give(";"),
-                },
+                .reset_carry  = false,
+                .max_ai_words = 3,
             },
             // ── Step 5: call copy_str and print ──────────────────────────
             {
-                .reset_carry  = false,
-                .max_ai_words = 3,
-                .scene        = {},
-                .goal         = 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;':
-
-1. Declare a buffer:    char dst[100];
-2. Call copy_str:       copy_str("hello", dst);
-3. Print the result:    printf("%s", dst);
-
-The buffer holds the copied characters.
-copy_str fills it. printf displays it.
-
-Place all three lines BEFORE the existing return.)",
                 .grants = {
                     Level::TokenGrant::give("char"), Level::TokenGrant::give("dst"), Level::TokenGrant::give("["),
                     Level::TokenGrant::give("100"),  Level::TokenGrant::give("]"),   Level::TokenGrant::give(";"),
@@ -179,7 +161,25 @@ Place all three lines BEFORE the existing return.)",
                     Level::TokenGrant::give(","), Level::TokenGrant::give("dst"),
                     Level::TokenGrant::give(")"), Level::TokenGrant::give(";"),
                 },
+                .scene        = {},
+                .goal         = 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;':
+
+1. Declare a buffer:    char dst[100];
+2. Call copy_str:       copy_str("hello", dst);
+3. Print the result:    printf("%s", dst);
+
+The buffer holds the copied characters.
+copy_str fills it. printf displays it.
+
+Place all three lines BEFORE the existing return.)",
+                .reset_carry  = false,
+                .max_ai_words = 3,
             },
         },
+        .name = "Char Copy",
+        .desc = "Implement a function that copies a char array and prints the result.",
+        .dynamic_lib = false,
     };
 }
diff --git a/src/levels/level/sandbox.cpp b/src/levels/level/sandbox.cpp
index fdcd169..821c922 100644
--- a/src/levels/level/sandbox.cpp
+++ b/src/levels/level/sandbox.cpp
@@ -6,14 +6,9 @@ Level::Data
 Level::build_Sandbox()
 {
     return {
-        .dynamic_lib = true,
-        .name = "Sandbox",
-        .desc = "Build the world yourself: compile a library that spawns objects.",
-        .source_file = "assets/sandbox.c",
         .steps = {
             {
-                .reset_carry  = true,
-                .max_ai_words = 0,
+                .grants = { },
                 .scene        = {},
                 .goal         = Goal::AllOf{},
                 .hint    = "Spawn a box with box(x,y,z, hx,hy,hz);",
@@ -30,8 +25,13 @@ Build something like:
   box(0, 5, -3,  1, 1, 1, 0);
 
 Then press the Compile pult. Edit and recompile freely.)",
-                .grants = { },
+                .reset_carry  = true,
+                .max_ai_words = 0,
             },
         },
+        .name = "Sandbox",
+        .desc = "Build the world yourself: compile a library that spawns objects.",
+        .source_file = "assets/sandbox.c",
+        .dynamic_lib = true,
     };
 }
diff --git a/src/levels/level/tutorial.cpp b/src/levels/level/tutorial.cpp
index 33394eb..af4bab0 100644
--- a/src/levels/level/tutorial.cpp
+++ b/src/levels/level/tutorial.cpp
@@ -7,17 +7,14 @@ Level::Data
 Level::build_Tutorial()
 {
     return {
-        .name = "Tutorial",
-        .desc = "Learn the controls and the code-building loop.",
         .steps = {
 
             {
-                .reset_carry  = true,
-                .max_ai_words = 0,
+                .grants  = {},
                 .scene   = Scene::Data{
                     .ai_seeds  = {},
                     .plates = {
-                        { .pos = { 8.6, World::get_FloorY<double>() + 2.2, 0.0 }, .tag = "" },
+                        { .tag = "", .pos = { 8.6, World::get_FloorY<double>() + 2.2, 0.0 } },
                     },
                     .platforms = {
                         { .pos = { 5.0, World::get_FloorY<double>() + 0.4, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
@@ -36,16 +33,16 @@ Move the mouse to turn.
 Jump with SPACE.
 
 Jump on the plate on top of the platform. )",
-                .grants  = {},
+                .reset_carry  = true,
+                .max_ai_words = 0,
             },
 
             {
-                .reset_carry  = true,
-                .max_ai_words = 0,
+                .grants  = {},
                 .scene   = Scene::Data{
                     .ai_seeds  = {},
                     .plates = {
-                        { .pos = { 0.0, World::get_FloorY<double>() + 1.2, 6.0 }, .tag = "" },
+                        { .tag = "", .pos = { 0.0, World::get_FloorY<double>() + 1.2, 6.0 } },
                     },
                     .platforms = {
                         { .pos = { 0.0, World::get_FloorY<double>() + 0.6, 6.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
@@ -62,18 +59,18 @@ Four walls surround you:
   - Program:  shows your program's output
   - Info:     this wall, with hints
 )",
-                .grants  = {},
+                .reset_carry  = true,
+                .max_ai_words = 0,
             },
 
             {
-                .reset_carry  = true,
-                .max_ai_words = 0,
+                .grants  = {},
                 .scene   = Scene::Data{
                     .ai_seeds  = {},
                     .plates    = {},
                     .platforms = {},
                     .words = {
-                        { .pos = { 5.0, World::get_FloorY<double>() + 1.5, 0.0 }, .text = "hello" },
+                        { .text = "hello", .pos = { 5.0, World::get_FloorY<double>() + 1.5, 0.0 } },
                     },
                 },
                 .goal    = Goal::CollectWord{ "hello" },
@@ -85,20 +82,20 @@ Picked words trail in front of you as you move.
 
 > hold SHIFT to free-look without turning your body.")
 )",
-                .grants  = {},
+                .reset_carry  = true,
+                .max_ai_words = 0,
             },
 
             {
-                .reset_carry  = false,
-                .max_ai_words = 0,
+                .grants  = {},
                 .scene   = Scene::Data{
                     .ai_seeds  = {},
                     .plates    = {},
                     .platforms = {},
                     .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", },
+                        { .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 } },
                     },
                 },
                 .goal = Goal::AllOf{{
@@ -111,12 +108,12 @@ Picked words trail in front of you as you move.
 
 Collect "press", "any" and "key" with 'e'.
 They queue up in the order you grab them.)",
-                .grants  = {},
+                .reset_carry  = false,
+                .max_ai_words = 0,
             },
 
             {
-                .reset_carry  = false,
-                .max_ai_words = 0,
+                .grants  = {},
                 .scene   = {},
                 .goal    = Goal::DroppedWord{},
                 .hint    = "Press 'q' to drop a word",
@@ -124,18 +121,18 @@ They queue up in the order you grab them.)",
 
 The dropped word falls to the floor — handy for
 putting back something you grabbed by mistake.)",
-                .grants  = {},
+                .reset_carry  = false,
+                .max_ai_words = 0,
             },
 
             {
-                .reset_carry  = false,
-                .max_ai_words = 0,
+                .grants  = {},
                 .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"  },
+                        {  .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 }, },
                     },
                 },
                 .goal    = Goal::ShotWord{},
@@ -145,45 +142,45 @@ putting back something you grabbed by mistake.)",
 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,
+            },
+            {
+                .grants  = {},
                 .scene   = {
                     .words = {
-                        { .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",},
+                        { .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 }, },
                     },
                 },
                 .goal    = Goal::ShotWord{},
                 .hint    = "Press 'f' to flush all carried words",
                 .details = R"(Bla blub
 ...)",
-                .grants  = {},
+                .reset_carry  = false,
+                .max_ai_words = 0,
             },
 
             {
-                .reset_carry = true,
-                .max_ai_words = 0,
+                .grants  = {},
                 .scene   = Scene::Data{
                     .ai_seeds  = {
                         {
-                            .aggressiveness = 1.0,
-                            .pos            = { -6.0, World::get_FloorY<double>() + 2.0, -6.0 },
                             .text           = "danger",
+                            .pos            = { -6.0, World::get_FloorY<double>() + 2.0, -6.0 },
                             .initial_state  = Text::Token::Ai::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", },
+                        { .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 }, },
                     },
                 },
                 .goal    = Goal::AllOf{{ Goal::CollectWord{ "ammo" }, Goal::AiWordDefeated{ "danger" } }},
@@ -194,12 +191,23 @@ 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,
             },
 
             {
-                .reset_carry  = true,
-                .max_ai_words = 0,
+                .grants = {
+                    Level::TokenGrant::give("int"),
+                    Level::TokenGrant::give("main"),
+                    Level::TokenGrant::give("("),
+                    Level::TokenGrant::give("void"),
+                    Level::TokenGrant::give(")"),
+                    Level::TokenGrant::give("{"),
+                    Level::TokenGrant::give("return"),
+                    Level::TokenGrant::give("0"),
+                    Level::TokenGrant::give(";"),
+                    Level::TokenGrant::give("}"),
+                },
                 .scene   = {},
                 .goal    = Goal::CompilePattern{ "int main ( void ) { return 0 ; }" },
                 .hint    = "Build int main(void) { return 0; } and press BUILD",
@@ -219,23 +227,20 @@ Build exactly:
 
 Then walk to the Compile pult (labelled BUILD) and
 press it (or press 'c' to compile).)",
+                .reset_carry  = true,
+                .max_ai_words = 0,
+            },
+
+            {
                 .grants = {
-                    Level::TokenGrant::give("int"),
-                    Level::TokenGrant::give("main"),
+                    Level::TokenGrant::give("#include"),
+                    Level::TokenGrant::give("<stdio.h>"),
+                    Level::TokenGrant::give("printf"),
                     Level::TokenGrant::give("("),
-                    Level::TokenGrant::give("void"),
+                    Level::TokenGrant::give("\"hello\""),
                     Level::TokenGrant::give(")"),
-                    Level::TokenGrant::give("{"),
-                    Level::TokenGrant::give("return"),
-                    Level::TokenGrant::give("0"),
                     Level::TokenGrant::give(";"),
-                    Level::TokenGrant::give("}"),
                 },
-            },
-
-            {
-                .reset_carry  = false,
-                .max_ai_words = 0,
                 .scene = {},
                 .goal  = Goal::AllOf{{
                     Goal::CompilePattern{ "int main ( void ) { printf ( \"hello\" ) ; return 0 ; }" },
@@ -253,24 +258,30 @@ So the whole thing reads:
 Press BUILD to compile, then walk to the RUN pult and
 press it. Your program runs and its output appears on
 the Program wall — and that's what finishes this step.)",
-                .grants = {
-                    Level::TokenGrant::give("#include"),
-                    Level::TokenGrant::give("<stdio.h>"),
-                    Level::TokenGrant::give("printf"),
-                    Level::TokenGrant::give("("),
-                    Level::TokenGrant::give("\"hello\""),
-                    Level::TokenGrant::give(")"),
-                    Level::TokenGrant::give(";"),
-                },
+                .reset_carry  = false,
+                .max_ai_words = 0,
             },
 
             {
-                .reset_carry  = false,
-                .max_ai_words = 0,
+                .grants  = {
+                    Level::TokenGrant::give("scanf(\"%d\", &x)"),
+                    Level::TokenGrant::give("int"),
+                    Level::TokenGrant::give("x"),
+                    Level::TokenGrant::give("="),
+                    Level::TokenGrant::give("0"),
+                    Level::TokenGrant::give(";"),
+                    Level::TokenGrant::give(";"),
+                    Level::TokenGrant::give(";"),
+                    Level::TokenGrant::give("\"Your Number is : %d\""),
+                    Level::TokenGrant::give(","),
+                    Level::TokenGrant::give("x"),
+                    Level::TokenGrant::give("42"),
+                    Level::TokenGrant::give("printf(\"Enter a Number:\\n\\t\")"),
+                },
                 .scene   = Scene::Data{
                     .ai_seeds  = {},
                     .plates = {
-                        { .pos = { 0.0, World::get_FloorY<double>() + 1.2, -6.0 }, .tag = "" },
+                        { .tag = "", .pos = { 0.0, World::get_FloorY<double>() + 1.2, -6.0 }, },
                     },
                     .platforms = {
                         { .pos = { 0.0, World::get_FloorY<double>() + 0.6, -6.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
@@ -288,30 +299,16 @@ the Program wall — and that's what finishes this step.)",
   - Press E on a placed word to pull it back off.
 
 Have a play, then jump onto the plate to continue.)",
-                .grants  = {
-                    Level::TokenGrant::give("scanf(\"%d\", &x)"),
-                    Level::TokenGrant::give("int"),
-                    Level::TokenGrant::give("x"),
-                    Level::TokenGrant::give("="),
-                    Level::TokenGrant::give("0"),
-                    Level::TokenGrant::give(";"),
-                    Level::TokenGrant::give(";"),
-                    Level::TokenGrant::give(";"),
-                    Level::TokenGrant::give("\"Your Number is : %d\""),
-                    Level::TokenGrant::give(","),
-                    Level::TokenGrant::give("x"),
-                    Level::TokenGrant::give("42"),
-                    Level::TokenGrant::give("printf(\"Enter a Number:\\n\\t\")"),
-                },
+                .reset_carry  = false,
+                .max_ai_words = 0,
             },
 
             {
-                .reset_carry  = true,
-                .max_ai_words = 0,
+                .grants  = {},
                 .scene   = Scene::Data{
                     .ai_seeds = {},
                     .plates = {
-                        { .pos = { 0.0, World::get_FloorY<double>() + 1.2, 0.0 }, .tag = "" },
+                        { .tag = "", .pos = { 0.0, World::get_FloorY<double>() + 1.2, 0.0 }, },
                     },
                     .platforms = {
                         { .pos = { 0.0, World::get_FloorY<double>() + 0.6, 0.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
@@ -324,8 +321,11 @@ Have a play, then jump onto the plate to continue.)",
 
 Jump onto the plate to start the first real level:
 "Char Copy".)",
-                .grants  = {},
+                .reset_carry  = true,
+                .max_ai_words = 0,
             },
         },
+        .name = "Tutorial",
+        .desc = "Learn the controls and the code-building loop.",
     };
 }
diff --git a/src/levels/scene.hpp b/src/levels/scene.hpp
index 4dd66fd..6987cfb 100644
--- a/src/levels/scene.hpp
+++ b/src/levels/scene.hpp
@@ -12,43 +12,44 @@ namespace Scene
 
     namespace Placed
     {
-    struct Word
-    {
-        bool required = false;
-        Vec3 pos {};
+        struct Word
+        {
+            std::string text;
 
-        SDL_FColor  color = ConfigColor::floor;
-        std::string text;
-    };
+            Vec3 pos {};
+            SDL_FColor color = ConfigColor::floor;
+            bool required = false;
+        };
 
 
-    struct Plate
-    {
-        Vec3 pos;
+        struct Plate
+        {
+            std::string tag;
 
-        std::string tag;
-    };
+            Vec3 pos;
+        };
 
 
-    struct Platform
-    {
-        Vec3 pos;
-        Vec3 half_extents { 1.0, 0.2, 1.0 };
+        struct Platform
+        {
+            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 };
-    };
+            SDL_FColor fill_color { 0.45f, 0.45f, 0.65f, 1.0f };
+            SDL_FColor wire_color { 0.70f, 0.70f, 0.90f, 1.0f };
+        };
 
 
-    struct Ai
-    {
-        double aggressiveness = 1.0;
-        Vec3   pos;
+        struct Ai
+        {
+            std::string text;
+
+            Vec3   pos;
 
-        std::string text;
+            Text::Token::Ai::State initial_state = Text::Token::Ai::State::Attack;
 
-        Text::Token::Ai::State initial_state = Text::Token::Ai::State::Attack;
-    };
+            double aggressiveness = 1.0;
+        };
     } // namespace Placed::
 
 
diff --git a/src/rendering/floor_surface.hpp b/src/rendering/floor_surface.hpp
index 1176fbc..aed221c 100644
--- a/src/rendering/floor_surface.hpp
+++ b/src/rendering/floor_surface.hpp
@@ -20,10 +20,10 @@ protected:
     DynArray<float> _verts;
 public:
     virtual void generate(float  world_scale,
-                  float  floor_y,
-                  double center_x = -0.5,
-                  double center_y =  0.0,
-                  double width    =  3.0) = 0;
+                          float  floor_y,
+                          double center_x = -0.5,
+                          double center_y =  0.0,
+                          double width    =  3.0) = 0;
 
     virtual void draw(GlRenderer& gl, const Camera& cam, const Light& light) const = 0;
     virtual ~Surface() = default;
diff --git a/src/rendering/gl_renderer.hpp b/src/rendering/gl_renderer.hpp
index 4e57bee..f67d8bd 100644
--- a/src/rendering/gl_renderer.hpp
+++ b/src/rendering/gl_renderer.hpp
@@ -23,20 +23,6 @@ Mat4 buildModelMat4(const Vec3& pos, float yaw, float pitch);
 
 class GlRenderer
 {
-    bool  _crosshair_enabled {true};
-    int   _view_w         {1};
-    int   _view_h         {1};
-    int   _fbo_w          {0};
-    int   _fbo_h          {0};
-    int   _msaa_samples   {4};
-    int   _blur_w         {0};
-    int   _blur_h         {0};
-    float _bloom_strength { 0.6f };
-    float _aspect         {1.0f};
-
-
-    Mat4  _proj;
-    SDL_GLContext _ctx{};
     GLuint        _prog{};
 
     GLint _uMVP       {-1},
@@ -118,6 +104,20 @@ class GlRenderer
     GLint  _uEmissiveColor    {-1};
     GLint  _uEmissiveStrength {-1};
 
+    float _bloom_strength { 0.6f };
+    float _aspect         {1.0f};
+    int   _view_w         {1};
+    int   _view_h         {1};
+    int   _fbo_w          {0};
+    int   _fbo_h          {0};
+    int   _msaa_samples   {4};
+    int   _blur_w         {0};
+    int   _blur_h         {0};
+    bool  _crosshair_enabled {true};
+
+    Mat4  _proj;
+    SDL_GLContext _ctx{};
+
     void create_FBO_             (int w, int h);
     void resize_FBO_             (int w, int h);
     void destroy_FBO_            ();
diff --git a/src/states/game_context.hpp b/src/states/game_context.hpp
index c774522..7d39d22 100644
--- a/src/states/game_context.hpp
+++ b/src/states/game_context.hpp
@@ -19,6 +19,10 @@ namespace Text { class Handler; }
 
 struct GameContext
 {
+    DynArray<Text::TokenPtr>words;
+    DynArray<GameObjectPtr> scenery;
+    DynArray<PultButton*>   buttons;
+
     cpputils::unique_ptr<Player> player;
     cpputils::unique_ptr<Camera> camera;
     cpputils::unique_ptr<World>  world;
@@ -28,9 +32,5 @@ struct GameContext
     Text::Handler* editor_wall { nullptr };
     Text::Handler* info_wall   { nullptr };
 
-    DynArray<Text::TokenPtr>words;
-    DynArray<GameObjectPtr> scenery;
-    DynArray<PultButton*>   buttons;
-
     void drop_WordToFloor(Text::TokenPtr word, const Vec3& vel);
 };
diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index 2ec1aa0..7e104ff 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -940,28 +940,32 @@ GameState::update_Physics(double dt)
                 for (size_t ti = 0; ti < toks.size(); ++ti) {
                     if (!toks[ti]) continue;
                     wall_text_hits.push_back({
-                            Wall::Position::NegX,
-                            WALL_TOK_BASE | (h << 28) | (li << 14) | ti,
                             toks[ti]->aabb_Min(),
                             toks[ti]->aabb_Max(),
+                            WALL_TOK_BASE | (h << 28) | (li << 14) | ti,
+                            Wall::Position::NegX,
                         });
                 }
             }
         }
 
         for (const Text::Handler::NumberHit& nh : _editor.number_Hitboxes())
-            wall_text_hits.push_back({ Wall::Position::NegX, nh.index,
-                                       nh.aabb_Min, nh.aabb_Max });
+            wall_text_hits.push_back({
+                    nh.aabb_Min,
+                    nh.aabb_Max,
+                    nh.index,
+                    Wall::Position::NegX,
+                });
 
         for (size_t li = 0; li < _wall_labels.size(); ++li) {
             const DynArray<Text::TokenPtr>& words = _wall_labels[li].label.get_Words();
             for (size_t wi = 0; wi < words.size(); ++wi) {
                 if (!words[wi]) continue;
                 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,
+                        Wall::position_Of(_wall_labels[li].role),
                         });
             }
         }
diff --git a/src/states/game_state.hpp b/src/states/game_state.hpp
index 5ce9931..25baeaa 100644
--- a/src/states/game_state.hpp
+++ b/src/states/game_state.hpp
@@ -33,6 +33,26 @@
 
 class GameState : public IState
 {
+    Text::Handler      _editor;
+    Text::Handler      _program;
+    Text::Handler      _compiler;
+    Text::Handler      _info;
+    Wall::Connection   _wall_connection;
+    CarriedLine        _carried;
+    FrameInput         _frame_input;
+    GameContext        _ctx;
+
+    DynArray<GameObject*>    _dyn_scenery;
+    DynArray<Text::Token::Data*>        _typing_words;
+    DynArray<Vec3>           _freelook_return_from;
+    DynArray<Projectile>     _projectiles;
+    DynArray<Wall::Label>    _wall_labels;
+    DynArray<WordAnim_Add>   _anims_add;
+    DynArray<WordAnim_Drop>  _anims_drop;
+    DynArray<Text::Handler*> _text_walls;
+
+    std::string        _typing_last;
+
     cpputils::unique_ptr<InputSystem>        _input_sys;
     cpputils::unique_ptr<FreelookSystem>     _freelook_sys;
 
@@ -54,47 +74,25 @@ class GameState : public IState
     cpputils::unique_ptr<WordAnimSystem>     _word_anim_sys;
     cpputils::unique_ptr<WordAiSystem>       _word_ai_sys;
 
-
     size_t _carried_last_size { 0 };
     double _pick_cooldown     { 0.0 };
     double _shoot_cooldown    { 0.0 };
 
+    // editor-token projectile-hit id range (line numbers use Text::Handler::HIT_BASE)
+    static constexpr size_t WALL_TOK_BASE  = size_t(1) << 40;
+    static constexpr size_t LABEL_HIT_BASE = size_t(1) << 48;
+
     // focus wall jump START
-    bool   _focus_active  { false };
-    double _focus_t       { 0.0 };
     Vec3   _focus_center  {};        // look-at point on the wall plane (panned by scroll)
     Vec3   _focus_normal  {};        // inward normal of the focused wall
+    double _focus_t       { 0.0 };
     double _focus_dist    { 4.0 };   // camera distance from the wall plane (dolly via W/S)
     double _focus_yaw_r   { 0.0 };
     float  _fov_y         { 0.0f };
     float  _aspect        { 0.0f };
+    bool   _focus_active  { false };
     // focus wall jump END
 
-
-    std::string        _typing_last;
-
-    CarriedLine        _carried;
-    Text::Handler      _editor;
-    Text::Handler      _program;
-    Text::Handler      _compiler;
-    Text::Handler      _info;
-    Wall::Connection   _wall_connection;
-    FrameInput         _frame_input;
-    GameContext        _ctx;
-
-    DynArray<GameObject*>    _dyn_scenery;
-    DynArray<Text::Token::Data*>        _typing_words;
-    DynArray<Vec3>           _freelook_return_from;
-    DynArray<Projectile>     _projectiles;
-    DynArray<Wall::Label>    _wall_labels;
-    DynArray<WordAnim_Add>   _anims_add;
-    DynArray<WordAnim_Drop>  _anims_drop;
-    DynArray<Text::Handler*> _text_walls;
-
-    // editor-token projectile-hit id range (line numbers use Text::Handler::HIT_BASE)
-    static constexpr size_t WALL_TOK_BASE  = size_t(1) << 40;
-    static constexpr size_t LABEL_HIT_BASE = size_t(1) << 48;
-
     void wallconnect_BeginEditor ();
     void wallconnect_BeginProgram();
 
diff --git a/src/states/menu_state.hpp b/src/states/menu_state.hpp
index 70ca567..68f13b1 100644
--- a/src/states/menu_state.hpp
+++ b/src/states/menu_state.hpp
@@ -29,21 +29,23 @@ class MenuState : public IState
         std::string label;
     };
 
+    DynArray<MenuItem> _items;
+
     cpputils::unique_ptr<World>  _world;
     cpputils::unique_ptr<Camera> _camera;
     cpputils::unique_ptr<Light>  _light;
-    Text::TokenPtr                    _title;
-    size_t _selected      {0};
+    Text::TokenPtr               _title;
+
     double _scroll_current{0.0};
     double _scroll_target {0.0};
     double _t             {0.0};
 
-    DynArray<MenuItem> _items;
+    size_t _selected      {0};
 
-    void   build_Items ();
-    void   activate    (Game& game);
-    bool   is_LevelItem(size_t idx) const;
-    size_t pick_FromCrosshair() const;
+    void   build_Items       ();
+    void   activate          (Game& game);
+    bool   is_LevelItem      (size_t idx) const;
+    size_t pick_FromCrosshair()           const;
 
     static uint32_t yaw_FromRadians(double rad);
     static double   yaw_ToRadians  (uint32_t yaw);
diff --git a/src/systems/aim.hpp b/src/systems/aim.hpp
index a7adcf2..3cef6bb 100644
--- a/src/systems/aim.hpp
+++ b/src/systems/aim.hpp
@@ -17,10 +17,10 @@ namespace Aim
 
 struct Result
 {
-    Text::Token::Data* track { nullptr };
-
     Vec3 point {};
     Vec3 local {};
+
+    Text::Token::Data* track { nullptr };
 };
 
 
diff --git a/src/systems/celebration_system.hpp b/src/systems/celebration_system.hpp
index 99f2f72..c805705 100644
--- a/src/systems/celebration_system.hpp
+++ b/src/systems/celebration_system.hpp
@@ -11,11 +11,11 @@ struct Light;
 
 class CelebrationSystem
 {
-    bool      _active     = false;
+    Text::TokenPtr _banner;
     double    _t          = 0,
               _duration   = 3.5,
               _next_burst = 0;
-    Text::TokenPtr _banner;
+    bool      _active     = false;
 public:
     void   start   (const Camera& cam, double& glyph_yaw);
     void   tick    (double dt, ParticleSystem& particle_sys);
diff --git a/src/systems/collision.hpp b/src/systems/collision.hpp
index 1bb04d6..d0acfaf 100644
--- a/src/systems/collision.hpp
+++ b/src/systems/collision.hpp
@@ -8,22 +8,22 @@ namespace Collision
 {
     struct Manifold
     {
-        bool   hit     = false;
-        double overlap {};
         Vec3   normal  {};
+        double overlap {};
+        bool   hit     = false;
     };
 
     struct SweptResult
     {
-        bool   hit    = false;
-        double t      = 0.0;
         Vec3   normal {};
+        double t      = 0.0;
+        bool   hit    = false;
     };
 
     void        resolve_Objects(GameObject& a, GameObject& b);
-    bool        aabb_Overlap (const GameObject& a, const GameObject& b);
-    Manifold    aabb_Manifold(const GameObject& a, const GameObject& b);
-    Vec3        reflect      (const Vec3& vel, const Vec3& normal, double restitution);
-    SweptResult swept_RayAabb(const Vec3& origin, const Vec3& dir,
-                              const Vec3& box_min, const Vec3& box_max);
+    bool        aabb_Overlap   (const GameObject& a, const GameObject& b);
+    Manifold    aabb_Manifold  (const GameObject& a, const GameObject& b);
+    Vec3        reflect        (const Vec3& vel, const Vec3& normal, double restitution);
+    SweptResult swept_RayAabb  (const Vec3& origin, const Vec3& dir,
+                                const Vec3& box_min, const Vec3& box_max);
 }
diff --git a/src/systems/compiler_system.hpp b/src/systems/compiler_system.hpp
index 0f1df7a..20564ae 100644
--- a/src/systems/compiler_system.hpp
+++ b/src/systems/compiler_system.hpp
@@ -14,11 +14,11 @@
 struct CompilerSystem
 {
 private:
-    int                   _count  {0};
-    std::atomic<bool>     _running{false};
-    std::mutex            _mtx;
-    std::thread           _worker;
     DynArray<std::string> _pending;
+    std::thread           _worker;
+    std::mutex            _mtx;
+    std::atomic<bool>     _running{false};
+    int                   _count  {0};
 
     void        push_Line  (Text::Handler& out, const std::string& line);
     static void run_Compile(CompilerSystem* self, std::string src);
diff --git a/src/systems/cpp_highlight_system.hpp b/src/systems/cpp_highlight_system.hpp
index 934cadf..5b1fe7d 100644
--- a/src/systems/cpp_highlight_system.hpp
+++ b/src/systems/cpp_highlight_system.hpp
@@ -45,15 +45,15 @@ public:
     }
 
 private:
-    bool              _redo    { false };
-    std::atomic<bool> _running { false };
-    std::atomic<bool> _done    { false };
-    uint64_t          _result_gen { 0 };
-    uint64_t          _last_gen_pending { 0 };
+    DynArray<Text::HiSpan>  _pending;
+    std::string       _last_requested;
     std::thread       _worker;
     std::mutex        _mtx;
-    std::string       _last_requested;
-    DynArray<Text::HiSpan>  _pending;
+    uint64_t          _result_gen { 0 };
+    uint64_t          _last_gen_pending { 0 };
+    std::atomic<bool> _running { false };
+    std::atomic<bool> _done    { false };
+    bool              _redo    { false };
 
     void spawn(const std::string& source, uint64_t gen)
     {
diff --git a/src/systems/dyn_loader_system.hpp b/src/systems/dyn_loader_system.hpp
index c4adf16..2da2b8c 100644
--- a/src/systems/dyn_loader_system.hpp
+++ b/src/systems/dyn_loader_system.hpp
@@ -9,15 +9,14 @@
 
 class DynLoaderSystem
 {
-    FILE* _pipe   { nullptr };
-    void* _handle { nullptr };
+    std::string _pending_so;
+    std::string _last_so_path;
 
     int _count        { 0 };
     int _build_serial { 0 };
 
-    std::string _pending_so;
-    std::string _last_so_path;
-
+    FILE* _pipe   { nullptr };
+    void* _handle { nullptr };
 
     void push_Line  (Text::Handler& out, const std::string& line);
     static std::string
diff --git a/src/systems/freelook_system.hpp b/src/systems/freelook_system.hpp
index de6a668..c620935 100644
--- a/src/systems/freelook_system.hpp
+++ b/src/systems/freelook_system.hpp
@@ -5,9 +5,9 @@
 
 struct FreelookState
 {
+    double lerp_t {};
     bool   active {};
     bool   lerping{};
-    double lerp_t {};
 };
 
 
diff --git a/src/systems/hint_system.hpp b/src/systems/hint_system.hpp
index c19ece1..8ff6920 100644
--- a/src/systems/hint_system.hpp
+++ b/src/systems/hint_system.hpp
@@ -11,8 +11,8 @@ class GlRenderer;
 
 class HintSystem
 {
-    std::string _current_hint;
     Text::Handler   _label;
+    std::string _current_hint;
 
 public:
     HintSystem   ();
diff --git a/src/systems/input_system.hpp b/src/systems/input_system.hpp
index 228c72b..5116d03 100644
--- a/src/systems/input_system.hpp
+++ b/src/systems/input_system.hpp
@@ -29,12 +29,23 @@ enum class InputCommand : uint8_t
 
 struct FrameInput
 {
-    bool forward  = false;
-    bool backward = false;
-    bool left     = false;
-    bool right    = false;
-    bool pick     = false;
-    bool shoot    = false;
+    DynArray<int>          vim_keys;
+    DynArray<InputCommand> commands;
+
+    std::string            typing_buffer;
+
+    double scroll_x = 0.0;
+    double scroll_y = 0.0;
+
+    int    look_x   = 0;
+    int    look_y   = 0;
+
+    bool forward    = false;
+    bool backward   = false;
+    bool left       = false;
+    bool right      = false;
+    bool pick       = false;
+    bool shoot      = false;
     bool focus_wall = false;
 
     bool normal_keys    = false;
@@ -45,15 +56,6 @@ struct FrameInput
     bool typing_mode   = false;
     bool typing_commit = false;
     bool typing_cancel = false;
-
-    int    look_x   = 0;
-    int    look_y   = 0;
-    double scroll_x = 0.0;
-    double scroll_y = 0.0;
-
-    std::string            typing_buffer;
-    DynArray<int>          vim_keys;
-    DynArray<InputCommand> commands;
 };
 
 
diff --git a/src/systems/particle_system.hpp b/src/systems/particle_system.hpp
index 066e125..1d2239f 100644
--- a/src/systems/particle_system.hpp
+++ b/src/systems/particle_system.hpp
@@ -13,16 +13,16 @@ class GameObject;
 
 struct Particle
 {
-    bool   alive    { true };
-    float  yaw      { 0.0f };
-    float  spin     { 0.0f };
-    double age      { 0.0 };
-    double lifetime { 1.5 };
+    SDL_FColor color { 1.0f, 1.0f, 1.0f, 1.0f };
 
     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 };
+    bool   alive    { true };
 };
 
 
diff --git a/src/systems/physics_system.cpp b/src/systems/physics_system.cpp
index 2179d59..5017957 100644
--- a/src/systems/physics_system.cpp
+++ b/src/systems/physics_system.cpp
@@ -412,8 +412,11 @@ PhysicsSystem::resolve_Projectiles(DynArray<Projectile>& projectiles,
 
                         if (contacts) {
                             contacts->push_back(ProjectileContactEvent{
-                                                    .projectile = &proj, .other = obj,
-                                                    .point = cur, .normal = sw.normal });
+                                                .point = cur,
+                                                .normal = sw.normal,
+                                                .other = obj,
+                                                .projectile = &proj,
+                                                });
                             }
                     }
                     continue;
@@ -426,8 +429,11 @@ PhysicsSystem::resolve_Projectiles(DynArray<Projectile>& projectiles,
                     proj.collided = true;
                     if (contacts) {
                         contacts->push_back(ProjectileContactEvent{
-                                            .projectile = &proj, .other = obj,
-                                            .point = cur, .normal = sw.normal });
+                                            .point = cur, 
+                                            .normal = sw.normal,
+                                            .other = obj,
+                                            .projectile = &proj,
+                                            });
                     }
                 }
             }
diff --git a/src/systems/physics_system.hpp b/src/systems/physics_system.hpp
index ccdd35f..8ba9ac9 100644
--- a/src/systems/physics_system.hpp
+++ b/src/systems/physics_system.hpp
@@ -13,22 +13,21 @@ struct Projectile;
 
 struct ProjectileContactEvent
 {
-    Projectile* projectile = nullptr;
-    GameObject* other      = nullptr;
     Vec3        point{};
     Vec3        normal{};
+    GameObject* other      = nullptr;
+    Projectile* projectile = nullptr;
 };
 
 
 class PhysicsSystem
 {
     struct SpatialGrid {
-        int    nx    = 0, nz = 0;
+        DynArray<DynArray<GameObject*>> cells;
         double cell  = 2.0;
         double inv   = 0.5;
         double min_x = 0, min_z = 0;
-
-        DynArray<DynArray<GameObject*>> cells;
+        int    nx    = 0, nz = 0;
 
         void build(const DynArray<GameObject*>& objs);
         int  index(int cx, int cz) const { return cz * nx + cx; }
diff --git a/src/systems/pick_system.hpp b/src/systems/pick_system.hpp
index f857185..a1e215a 100644
--- a/src/systems/pick_system.hpp
+++ b/src/systems/pick_system.hpp
@@ -13,10 +13,11 @@ enum class PickKind : uint8_t { None, Scenery, Word, WallWord };
 struct PickResult
 {
     size_t word_index { 0 };
-    GameObject*    scenery_obj  { nullptr };
-    Text::Handler* wall_handler { nullptr };
-    Text::Token::Data*   wall_token   { nullptr };
-    PickKind       kind         { PickKind::None };
+    GameObject*        scenery_obj  { nullptr };
+    Text::Handler*     wall_handler { nullptr };
+    Text::Token::Data* wall_token   { nullptr };
+    PickKind           kind         { PickKind::None };
+
     bool hit() const { return kind != PickKind::None; }
 };
 
diff --git a/src/systems/program_system.hpp b/src/systems/program_system.hpp
index ad40e26..5a63777 100644
--- a/src/systems/program_system.hpp
+++ b/src/systems/program_system.hpp
@@ -11,13 +11,13 @@
 struct ProgramSystem
 {
 private:
-    int _out_fd {-1};
-    int _in_fd  {-1};
-    int _count  {0};
+    std::string _pending;
 
-    pid_t _child  {-1};
+    pid_t _child {-1};
 
-    std::string _pending;
+    int _out_fd  {-1};
+    int _in_fd   {-1};
+    int _count   {0};
 
     void push_Line  (Text::Handler& out, const std::string& line);
     void close_Child();
diff --git a/src/systems/projectile_system.hpp b/src/systems/projectile_system.hpp
index 4351c95..77201d6 100644
--- a/src/systems/projectile_system.hpp
+++ b/src/systems/projectile_system.hpp
@@ -22,19 +22,7 @@ enum class ProjectileLanding : uint8_t
 
 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;
-
-    Text::Token::Data* track_obj = nullptr;
+    DynArray<Text::TokenPtr> words;
 
     Vec3 vel{};
     Vec3 dir{};
@@ -42,38 +30,54 @@ struct Projectile
     Vec3 launch_origin{};
     Vec3 cam_origin   {};
 
-    Vec3 track_local{};
-    Vec3 track_last {};
-
+    Vec3 track_local  {};
+    Vec3 track_last   {};
 
     Vec3 ribbon_origin       {};
     Vec3 ribbon_start_origin {};
     Vec3 ribbon_target_origin{};
 
-    DynArray<Text::TokenPtr> words;
+    double yaw;
+    double stick_t = 0.0;
+    double step_dt = 0.0;
+
+    float  ribbon_start_yaw  {};
+    float  ribbon_target_yaw {};
+
+    Text::Token::Data* track_obj = nullptr;
+
+    bool sticking      = false;
+    bool collided      = false;
+    bool program_stick = false;
+    uint8_t bounce_state = 0;
+
+    Wall::Position
+         stick_wall    { Wall::Position::PosX };
 };
 
 
 struct WallTextHitTest
 {
-    Wall::Position
-           wall_position;
-    size_t index;
-
     Vec3 aabb_Min;
     Vec3 aabb_Max;
+
+    size_t index;
+
+    Wall::Position
+           wall_position;
 };
 
 
 struct ProjectileResult
 {
-    Wall::Position
-           wall_pos        = Wall::Position::NegZ;
-    size_t wall_text_index = 0;
+    DynArray<Text::TokenPtr> words;
+    ProjectileLanding   landing = ProjectileLanding::None;
+
     Vec3   vel             {};
+    size_t wall_text_index = 0;
 
-    ProjectileLanding   landing = ProjectileLanding::None;
-    DynArray<Text::TokenPtr> words;
+    Wall::Position
+           wall_pos        = Wall::Position::NegZ;
 };
 
 
diff --git a/src/systems/pult_system.hpp b/src/systems/pult_system.hpp
index 17d299c..3f7501e 100644
--- a/src/systems/pult_system.hpp
+++ b/src/systems/pult_system.hpp
@@ -17,10 +17,10 @@ namespace Text { class Handler; }
 
 class PultSystem
 {
+    DynArray<Pult*> _pults;
+
     Pult* _connect_program { nullptr };
     Pult* _connect_editor  { nullptr };
-
-    DynArray<Pult*> _pults;
 public:
     void init        (DynArray<GameObjectPtr>& scenery,
                       DynArray<PultButton*>&   buttons_out,
diff --git a/src/systems/wall_connection.hpp b/src/systems/wall_connection.hpp
index 075c434..d3df49b 100644
--- a/src/systems/wall_connection.hpp
+++ b/src/systems/wall_connection.hpp
@@ -34,9 +34,11 @@ public:
 
 
 private:
-    bool   _cmd_pos_init { false };
-    float  _cmd_yaw      { 0.0f };
-    double _blink        { 0.0 };
+    std::string _cmd_last;
+    std::string _prog_last;
+
+    Vec3 _cmd_pos {};
+    Vec3 _pult_pos{};
 
     Target _target { Target::None };
 
@@ -45,11 +47,10 @@ private:
     Text::TokenPtr _cmdline;
     Text::TokenPtr _prog_input;
 
-    Vec3 _cmd_pos {};
-    Vec3 _pult_pos{};
+    float  _cmd_yaw      { 0.0f };
+    double _blink        { 0.0 };
 
-    std::string _cmd_last;
-    std::string _prog_last;
+    bool   _cmd_pos_init { false };
 
     void rebuild_Cmdline       (const std::string& text);
     void rebuild_ProgramPreview(const std::string& buffer);
diff --git a/src/systems/word_anim_system.hpp b/src/systems/word_anim_system.hpp
index 733261e..02b8ef1 100644
--- a/src/systems/word_anim_system.hpp
+++ b/src/systems/word_anim_system.hpp
@@ -7,7 +7,9 @@
 
 struct WordAnim_Add
 {
-    bool   complete     {false};
+    Vec3 start_pos;
+    Vec3 target_pos;
+
     double yaw_start;
     double yaw_target;
     double pitch_start  {0.0};
@@ -16,16 +18,15 @@ struct WordAnim_Add
 
     Text::TokenPtr word;
 
-    Vec3 start_pos;
-    Vec3 target_pos;
+    bool   complete     {false};
 };
 
 
 struct WordAnim_Drop
 {
-    bool complete{false};
-
     Text::TokenPtr word;
+
+    bool complete{false};
 };
 
 
diff --git a/src/text/handler.hpp b/src/text/handler.hpp
index 708968a..c481f18 100644
--- a/src/text/handler.hpp
+++ b/src/text/handler.hpp
@@ -102,11 +102,12 @@ public:
     EditReq feed_Keys  (DynArray<int>& keys, double eye_y);
     void    mark_Saved ();
 
-    int cursor_Row      () const;
-    int cursor_Col      () const;
-    int cursor_VisualCol() const;
-    bool cursor_ToToken (Token::Data* t);   
-    bool cursor_AtImpact(const Vec3& impact); 
+    int  cursor_Row      () const;
+    int  cursor_Col      () const;
+    int  cursor_VisualCol() const;
+    bool cursor_ToToken  (Token::Data* t);   
+    bool cursor_AtImpact (const Vec3& impact); 
+
     const std::string& cmdline_Text() const;
     std::string        modeline    () const;
     VimBuffer::Mode    cursor_Mode () const;
diff --git a/src/text/mesh.hpp b/src/text/mesh.hpp
index e8d3663..60180ae 100644
--- a/src/text/mesh.hpp
+++ b/src/text/mesh.hpp
@@ -19,8 +19,8 @@ struct MeshTri
 struct TextMesh
 {
     DynArray<Vec3>    verts;
-    DynArray<MeshTri> tris;
     DynArray<Vec3>    vertex_normals;
+    DynArray<MeshTri> tris;
     DynArray<float>   is_front;
 
     bool build(
diff --git a/src/text/text.hpp b/src/text/text.hpp
index ba6bc5a..39ef26f 100644
--- a/src/text/text.hpp
+++ b/src/text/text.hpp
@@ -64,21 +64,22 @@ Text
     {
         DynArray<TokenPtr> tokens;
 
+        double bounce_off   { 0.0 };
+        double bounce_vel   { 0.0 };
         double y            { 0.0 };
         double line_height  { 0.0 };
         double word_spacing { 0.0 };
 
-        int      number      { -1 };
-        bool     show_number { false };
-        double   bounce_off  { 0.0 };
-        double   bounce_vel  { 0.0 };
         TokenPtr num_left;
         TokenPtr num_right;
 
+        int  number      { -1 };
+        bool show_number { false };
+
         bool   is_Empty() const { return tokens.empty(); }
         size_t count   () const { return tokens.size(); }
 
-        double width(double gap) const;
+        double width   (double gap) const;
 
         Token::Data* token_At_Byte(size_t off) const;
 
diff --git a/src/text/vim_buffer.hpp b/src/text/vim_buffer.hpp
index 232eced..be93dba 100644
--- a/src/text/vim_buffer.hpp
+++ b/src/text/vim_buffer.hpp
@@ -60,34 +60,28 @@ public:
     }
 
 private:
-    bool     _dirty = false;
-    unsigned _version = 0;
-    int      _row = 0, _col = 0;
-    int      _count = 0;
+    struct Snapshot { std::vector<std::string> lines; int row; int col; };
+
+    std::vector<std::string> _saved;
+    std::vector<std::string> _lines{ "" };
+    std::vector<Snapshot>    _undo;
+    std::vector<Snapshot>    _redo;
+
+    Mode   _mode = Mode::Normal;
+    Signal _sig  = Signal::None;
 
     std::string _pending;
     std::string _cmd;
     std::string _status;
-
-    // Yank/delete register (vim's unnamed register). Linewise yanks (yy/dd/cc)
-    // set _reg_linewise so paste creates new lines; charwise yanks (yw/dw/cw)
-    // clear it so paste splices inline. Multi-line linewise content is stored
-    // '\n'-joined.
     std::string _register;
-    bool        _reg_linewise = false;
 
-    // Undo/redo: full-state snapshots (lines + cursor). Grouped vim-style — one
-    // snapshot per normal-mode edit, and one per insert SESSION (taken when
-    // insert begins, so all typing until Esc undoes as a unit). Any new edit
-    // clears the redo stack.
-    struct Snapshot { std::vector<std::string> lines; int row; int col; };
-    std::vector<Snapshot> _undo;
-    std::vector<Snapshot> _redo;
+    unsigned _version = 0;
+    int      _row     = 0,
+             _col     = 0;
+    int      _count   = 0;
 
-    Mode     _mode = Mode::Normal;
-    Signal   _sig = Signal::None;
-    std::vector<std::string> _saved;
-    std::vector<std::string> _lines{ "" };
+    bool     _dirty   = false;
+    bool     _reg_linewise = false;
 
     void touch           ();
     void clamp_Col       ();
diff --git a/src/utils/config.hpp b/src/utils/config.hpp
index b06462e..7ff57a8 100644
--- a/src/utils/config.hpp
+++ b/src/utils/config.hpp
@@ -93,11 +93,12 @@ namespace ConfigText3d
 static constexpr double hover_amp   = 0.3;
 static constexpr double hover_speed = 1.5;
 
-static constexpr float  word_gap        = 0.25f;
 static constexpr double jiggle_stiff    = 80.0;
 static constexpr double jiggle_damp     = 9.0;
 static constexpr double jiggle_max_off  = 0.35;
 static constexpr double kick_strength   = 0.18;
+
+static constexpr float  word_gap        = 0.25f;
 }
 
 
diff --git a/src/utils/light.hpp b/src/utils/light.hpp
index 570513b..934a47d 100644
--- a/src/utils/light.hpp
+++ b/src/utils/light.hpp
@@ -3,15 +3,14 @@
 #include "math.hpp"
 
 struct Light {
-    Vec3  sun_direction { 0.3, -0.9, 0.2 };
-    Vec3  sun_color     { 1.00, 0.95, 0.85 };
-    float sun_intensity { 0.0f }; // off for now
+    Vec3  sun_direction    { 0.3, -0.9, 0.2 };
+    Vec3  sun_color        { 1.00, 0.95, 0.85 };
+    Vec3  flashlight_pos   { 0.0, 0.0, 0.0 };
+    Vec3  flashlight_dir   { 0.0, 0.0, -1.0 };
+    Vec3  flashlight_color { 1.00, 0.95, 0.80 };
+    Vec3  ambient          { 0.25, 0.27, 0.30 };
 
-    Vec3  ambient { 0.25, 0.27, 0.30 };
-
-    Vec3  flashlight_pos       { 0.0, 0.0, 0.0 };
-    Vec3  flashlight_dir       { 0.0, 0.0, -1.0 };
-    Vec3  flashlight_color     { 1.00, 0.95, 0.80 };
+    float sun_intensity        { 0.0f }; // off for now
     float flashlight_range     { 40.0f };
     float flashlight_inner_cos { 0.85f };  // ≈ 32° full cone
     float flashlight_outer_cos { 0.50f };  // ≈ 60° cutoff
diff --git a/src/wall/label.hpp b/src/wall/label.hpp
index 95f186f..061790d 100644
--- a/src/wall/label.hpp
+++ b/src/wall/label.hpp
@@ -15,10 +15,10 @@ namespace Wall
 {
     struct Label
     {
-        Wall::Role    role;
-        float         face_yaw;
-        Vec3          anchor;
         Text::Handler label;
+        Vec3          anchor;
+        float         face_yaw;
+        Wall::Role    role;
 
         static DynArray<Label> generate_All(float world_scale);
 
diff --git a/src/wall/wall.hpp b/src/wall/wall.hpp
index 4ca0c64..c020ee9 100644
--- a/src/wall/wall.hpp
+++ b/src/wall/wall.hpp
@@ -44,12 +44,13 @@ namespace Wall
     {
     public:
         struct Quad {
-            bool       is_editor;
+            DynArray<float> verts;
+            DynArray<int>   indices;
+
             Role       role;
             SDL_FColor color;
+            bool       is_editor;
 
-            DynArray<float> verts;
-            DynArray<int>   indices;
         };
 
         void generate(float world_scale);