Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / commits / 304d4e0

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

Namespace Configs

Christian Immanuel · 2026-06-03 08:24 · 304d4e0d87a4808881e41ac21673368f145ef3c4

 src/c_token/token_color.hpp        |  18 +++---
 src/entities/player.cpp            |   4 +-
 src/entities/player.hpp            |   4 --
 src/entities/pult.cpp              |   6 +-
 src/entities/pult.hpp              |   4 --
 src/levels/scene.hpp               |   2 +-
 src/states/game_state.cpp          |  38 ++++++-------
 src/systems/celebration_system.cpp |   2 +-
 src/systems/compiler_system.cpp    |   2 +-
 src/systems/hint_system.cpp        |   2 +-
 src/systems/mod_loader_system.cpp  |   2 +-
 src/systems/program_system.cpp     |   2 +-
 src/systems/projectile_system.cpp  |   2 +-
 src/systems/word_ai_system.cpp     |  38 ++++++-------
 src/systems/word_ai_system.hpp     |   9 ---
 src/systems/word_anim.cpp          |   2 +-
 src/text_3d/text_3d.hpp            |  33 +----------
 src/text_3d/text_label.cpp         |  20 +++----
 src/text_3d/text_label.hpp         |   6 --
 src/utils/config.hpp               | 110 +++++++++++++++++++++++++++++--------
 src/wall/editor_wall.cpp           |  40 +++++++-------
 src/wall/editor_wall.hpp           |  15 +----
 src/wall/text_wall.cpp             |   4 +-
 src/wall/text_wall_scroll.hpp      |  10 ++--
 src/wall/wall.hpp                  |   6 +-
 src/wall/wall_surfaces.cpp         |   2 +-
 26 files changed, 189 insertions(+), 194 deletions(-)

diff --git a/src/c_token/token_color.hpp b/src/c_token/token_color.hpp
index ff24ab2..5606a7c 100644
--- a/src/c_token/token_color.hpp
+++ b/src/c_token/token_color.hpp
@@ -10,15 +10,15 @@ inline SDL_FColor
 get(TokenType t)
 {
     switch (t) {
-        case TokenType::Keyword:     return Text3d::color_orange;   // if, for, return
-        case TokenType::Type:        return Text3d::color_green;    // int, void
-        case TokenType::Identifier:  return Text3d::color_light_gray;    // main, printf, i, x …
-        case TokenType::Literal:     return Text3d::color_red;      // 0, 1, "\n", %s
-        case TokenType::Operator:    return Text3d::color_light_gray;    // = == + - * / ++ 
-        case TokenType::Delimiter:   return Text3d::color_light_gray;    // ( ) { } ; , [ ]
-        case TokenType::Preprocessor:return Text3d::color_pink;    // #define #if
+        case TokenType::Keyword:     return ConfigColors::color_orange;   // if, for, return
+        case TokenType::Type:        return ConfigColors::color_green;    // int, void
+        case TokenType::Identifier:  return ConfigColors::color_light_gray;    // main, printf, i, x …
+        case TokenType::Literal:     return ConfigColors::color_red;      // 0, 1, "\n", %s
+        case TokenType::Operator:    return ConfigColors::color_light_gray;    // = == + - * / ++ 
+        case TokenType::Delimiter:   return ConfigColors::color_light_gray;    // ( ) { } ; , [ ]
+        case TokenType::Preprocessor:return ConfigColors::color_pink;    // #define #if
     }
-    return Text3d::color_white;
+    return ConfigColors::color_white;
 }
 
 inline void
@@ -33,7 +33,7 @@ inline SDL_FColor
 get_ByString(std::string token_text)
 {
     const Token* tok = TokenDB::find_OrSynthesize(token_text);
-    return tok ? get(tok->type) : Text3d::color_white;
+    return tok ? get(tok->type) : ConfigColors::color_white;
 }
 
 }
diff --git a/src/entities/player.cpp b/src/entities/player.cpp
index 6f198c0..1957af0 100644
--- a/src/entities/player.cpp
+++ b/src/entities/player.cpp
@@ -56,8 +56,8 @@ Player::compute_Movement(const FrameInput& input)
 
     if (n != 0) {
         double inv = 1.0 / SDL_sqrt(n);
-        _acc_x = MOVE_ACCEL * ( c*dirX + s*dirZ) * inv;
-        _acc_z = MOVE_ACCEL * (-s*dirX + c*dirZ) * inv;
+        _acc_x = ConfigPhysics::player_move_accel * ( c*dirX + s*dirZ) * inv;
+        _acc_z = ConfigPhysics::player_move_accel * (-s*dirX + c*dirZ) * inv;
     }
 }
 
diff --git a/src/entities/player.hpp b/src/entities/player.hpp
index 7dd0a3e..9ef1ff8 100644
--- a/src/entities/player.hpp
+++ b/src/entities/player.hpp
@@ -33,10 +33,6 @@ public:
     float    radius   {0.5f};
     float    height   {1.5f};
     bool     on_ground{false};
-    static constexpr double
-             JUMP_VELOCITY = 8.4375;
-    static constexpr double
-             MOVE_ACCEL    = 60.0;
 
     void   compute_Movement(const FrameInput& input);
     void   add_Look        (int dx, int dy);
diff --git a/src/entities/pult.cpp b/src/entities/pult.cpp
index 2e8e499..9f6aa96 100644
--- a/src/entities/pult.cpp
+++ b/src/entities/pult.cpp
@@ -19,7 +19,7 @@ PultButton::PultButton(const std::string& lbl, PultAction act, Vec3 world_pos)
 
 void
 PultButton::press()
-{ pressed = true; press_timer = PRESS_DUR; }
+{ pressed = true; press_timer = ConfigPult::press_dur; }
 
 
 void
@@ -67,9 +67,9 @@ Pult::Pult(const std::string& n, Vec3 world_pos,
      , name(n), half_w(half_width), half_d(half_depth)
 {
     label.set_Billboard(true);
-    label.set_Style(Text3d::color_indigo, Text3d::depth_wall, 0.0025f);
+    label.set_Style(ConfigColors::color_indigo, Text3d::depth_wall, 0.0025f);
     label.set_Collidable(true);
-    label.attach_To(&pos, Vec3{ 0.0, LABEL_HEIGHT, 0.0 });
+    label.attach_To(&pos, Vec3{ 0.0, ConfigPult::label_height, 0.0 });
     label.set_Text(n);
 }
 
diff --git a/src/entities/pult.hpp b/src/entities/pult.hpp
index 4f96c54..2692dea 100644
--- a/src/entities/pult.hpp
+++ b/src/entities/pult.hpp
@@ -29,8 +29,6 @@ struct PultButton final : public SolidBox
     bool   pressed     { false };
     bool   player_on   { false };
     double press_timer { 0.0   };
-    static constexpr double
-           PRESS_DUR   = 0.18;
 
     PultButton(const std::string& lbl, PultAction act, Vec3 world_pos);
 
@@ -54,8 +52,6 @@ struct Pult : public SolidBox
     float half_w { W };
     float half_d { D };
 
-    static constexpr double LABEL_HEIGHT = 1.1;
-
     Pult(const std::string& n, Vec3 world_pos,
          float half_width = W, float half_depth = D);
 
diff --git a/src/levels/scene.hpp b/src/levels/scene.hpp
index cfb659a..2ed69d0 100644
--- a/src/levels/scene.hpp
+++ b/src/levels/scene.hpp
@@ -14,7 +14,7 @@
 struct PlacedWord {
     std::string text;
     Vec3        pos;
-    SDL_FColor  color    = Text3d::color_floor;
+    SDL_FColor  color    = ConfigColors::color_floor;
     bool        required = false;
 };
 
diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index 4cefc80..4e7ca91 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -181,11 +181,11 @@ GameState::pult_DispatchAction(PultAction act)
     switch (act) {
         case PultAction::ScrollEditorUp:
             TextWallScroll::scroll_Up(_ctx.wall_texts, Wall::Role::Editor);
-            _editor_wall.scroll(TextWallScroll::SCROLL_STEP);
+            _editor_wall.scroll(ConfigWall::scroll_step);
             break;
         case PultAction::ScrollEditorDown:
             TextWallScroll::scroll_Down(_ctx.wall_texts, Wall::Role::Editor);
-            _editor_wall.scroll(-TextWallScroll::SCROLL_STEP);
+            _editor_wall.scroll(-ConfigWall::scroll_step);
             break;
         case PultAction::ClearEditor:
             TextWallScroll::clear_ToFloor(_ctx.wall_texts, _ctx.words, Wall::Role::Editor, floor_y);
@@ -230,7 +230,7 @@ GameState::on_Pick()
 
         if (base->type == ObjectType::Text) {
             _step_ledger.picked_up.push_back(base->text);
-            base->set_Look(Text3d::color_keep, Text3d::depth_floor);
+            base->set_Look(ConfigColors::color_keep, Text3d::depth_floor);
             TextWall::show_TokenInfo(_ctx.wall_texts, base->text, _ctx.world->scale());
         }
         carry_BeginAnim(std::move(base), /*with_pitch=*/true);
@@ -244,7 +244,7 @@ GameState::on_Pick()
         if (!word) return false;
 
         _step_ledger.picked_up.push_back(word->text);
-        word->set_Look(Text3d::color_keep, Text3d::depth_floor);
+        word->set_Look(ConfigColors::color_keep, Text3d::depth_floor);
         TextWall::show_TokenInfo(_ctx.wall_texts, word->text, _ctx.world->scale());
 
         carry_BeginAnim(std::move(word), /*with_pitch=*/false);
@@ -373,7 +373,7 @@ void
 GameState::on_Jump()
 {
     if (_ctx.player->on_ground) {
-        _ctx.player->vel.y     = Player::JUMP_VELOCITY;
+        _ctx.player->vel.y     = ConfigPhysics::player_jump_velocity;
         _ctx.player->on_ground = false;
     }
 }
@@ -476,7 +476,7 @@ GameState::projectile_LandOnFloor(ProjectileResult& result)
 {
     for (Text3dPtr& w : result.words) {
         if (!w || w->type != ObjectType::Text) continue;
-        w->set_Look(Text3d::color_keep, Text3d::depth_floor);
+        w->set_Look(ConfigColors::color_keep, Text3d::depth_floor);
         w->body.mode = PhysicsMode::Dynamic;
         w->body.vel  = result.vel;
         _ctx.words.push_back(std::move(w));
@@ -494,7 +494,7 @@ GameState::projectile_LandOnWall(ProjectileResult& result)
     for (Text3dPtr& w : result.words) {
         if (!w) continue;
         if (w->type == ObjectType::Text)
-            w->set_Look(Text3d::color_keep, Text3d::depth_wall);
+            w->set_Look(ConfigColors::color_keep, Text3d::depth_wall);
 
         w->body.mode = PhysicsMode::Static;
         w->body.vel  = Vec3{0, 0, 0};
@@ -534,7 +534,7 @@ GameState::walltext_HandleShootDown(ProjectileResult& result,
             orphan->body.vel          = { 0.0, 1.5, 0.0 };
             orphan->body.hover_height = -static_cast<double>(_ctx.world->scale()) + 1.5;
             orphan->yaw               = 0.0f;
-            orphan->set_Look(Text3d::color_keep, Text3d::depth_floor);
+            orphan->set_Look(ConfigColors::color_keep, Text3d::depth_floor);
             _ctx.words.push_back(std::move(orphan));
             to_erase.push_back(result.wall_text_index);
         }
@@ -543,7 +543,7 @@ GameState::walltext_HandleShootDown(ProjectileResult& result,
     for (Text3dPtr& w : result.words) {
         if (!w) continue;
         if (w->type == ObjectType::Text) {
-            w->set_Look(Text3d::color_keep, Text3d::depth_floor);
+            w->set_Look(ConfigColors::color_keep, Text3d::depth_floor);
         }
         w->body.mode = PhysicsMode::Dynamic;
         w->body.vel  = result.vel;
@@ -804,16 +804,16 @@ GameState::infowall_StepdetailsShow()
     DynArray<std::string> lines = wrap_Text(details, MAX_INFO_LINE_CHARS);
 
     const float bottom_y = -world_scale + 1.0f;
-    float y = bottom_y + static_cast<float>(lines.size() - 1) * Wall::TEXT_LINE_SPACING;
+    float y = bottom_y + static_cast<float>(lines.size() - 1) * ConfigWall::text_line_spacing;
 
     for (const std::string& line : lines) {
         Vec3 pos = wall_transform.position;
         pos.y = y;
-        y -= Wall::TEXT_LINE_SPACING;
+        y -= ConfigWall::text_line_spacing;
         if (line.empty()) continue;
 
         Text3dPtr t = make_Text3d(line, Text3d::depth_wall, 0.0025f,
-                                  pos,  Text3d::color_white);
+                                  pos,  ConfigColors::color_white);
         t->yaw = wall_transform.yaw;
 
         _ctx.wall_texts.push_back(cpputils::make_unique<TextWall>(
@@ -850,7 +850,7 @@ GameState::scene_Setup(const Scene& scene)
 
     for (const PlacedAi& a : scene.ai_seeds) {
         Text3dPtr t = make_Text3d(a.text, Text3d::depth_floor, 0.003f,
-                                  a.pos, Text3d::color_floor);
+                                  a.pos, ConfigColors::color_floor);
         t->body.hover_height = a.pos.y;
         t->ai.aggressiveness = a.aggressiveness;
 
@@ -858,14 +858,14 @@ GameState::scene_Setup(const Scene& scene)
             case Text3dAI::State::Attack:
                 t->ai.state  = Text3dAI::State::Attack;
                 t->body.mode = PhysicsMode::AiHover;
-                t->set_Color(Text3d::color_attack);
+                t->set_Color(ConfigColors::color_attack);
                 t->ai.state_timer = 0.0;
                 t->ai.phase_timer = 0.5;
                 break;
             case Text3dAI::State::Flee:
                 t->ai.state  = Text3dAI::State::Flee;
                 t->body.mode = PhysicsMode::AiHover;
-                t->set_Color(Text3d::color_flee);
+                t->set_Color(ConfigColors::color_flee);
                 break;
             case Text3dAI::State::Idle:
             case Text3dAI::State::Stunned:
@@ -1023,7 +1023,7 @@ GameState::substep(double dt)
                 word->ai.state       = Text3dAI::State::Stunned;
                 word->ai.state_timer = 0.0;
                 word->body.vel       = Vec3{0, 0, 0};
-                word->set_Color(Text3d::color_stunned);
+                word->set_Color(ConfigColors::color_stunned);
             }
         }
     }
@@ -1209,7 +1209,7 @@ GameState::sandbox_LoadFileToWall()
         double z = z_start;
         for (const std::string& tk : toks) {
             Text3dPtr t = make_Text3d(tk, Text3d::depth_wall, 0.003f,
-                                      Vec3{ x_wall, y, z }, Text3d::color_wall);
+                                      Vec3{ x_wall, y, z }, ConfigColors::color_wall);
             t->yaw = tf.yaw;
 
             // Snap into the editor wall's line tracking, like a thrown word.
@@ -1224,7 +1224,7 @@ GameState::sandbox_LoadFileToWall()
             // it; nudge our cursor past where it landed.
             z = snapped.z - 0.5;   // coarse step; place_OnLine handles exact gaps
         }
-        y -= EditorWall::LINE_HEIGHT;
+        y -= ConfigWall::line_height;
     }
 }
 
@@ -1247,7 +1247,7 @@ GameState::typing_RebuildPreview(const std::string& s)
     std::string tok;
     while (in >> tok) {
         Text3dPtr t = make_Text3d(tok, Text3d::depth_floor, 0.003f,
-                                  anchor, Text3d::color_floor);
+                                  anchor, ConfigColors::color_floor);
         t->yaw = static_cast<float>(glyph_yaw);
         t->body.mode = PhysicsMode::Carried;
         _typing_words.push_back(t.get());   // track raw ptr for later removal
diff --git a/src/systems/celebration_system.cpp b/src/systems/celebration_system.cpp
index 8e64754..88aab08 100644
--- a/src/systems/celebration_system.cpp
+++ b/src/systems/celebration_system.cpp
@@ -21,7 +21,7 @@ CelebrationSystem::start(const Camera& cam, double& glyph_yaw)
     _banner = make_Text3d("Success",
                          Text3d::depth_floor * 2.0f,
                          0.012f, banner_pos,
-                         Text3d::color_white);
+                         ConfigColors::color_white);
     _banner->yaw       = static_cast<float>(glyph_yaw);
     _banner->body.mode = PhysicsMode::Static;
 }
diff --git a/src/systems/compiler_system.cpp b/src/systems/compiler_system.cpp
index 68ee01b..b49865d 100644
--- a/src/systems/compiler_system.cpp
+++ b/src/systems/compiler_system.cpp
@@ -84,7 +84,7 @@ CompilerSystem::push_Line(TextWallList& texts, const std::string& line, float wo
     Vec3 pos = tf.position;
     pos.y = start_y;
 
-    Text3dPtr t = make_Text3d(clean, Text3d::depth_compiler, 0.003f, pos, Text3d::color_compiler);
+    Text3dPtr t = make_Text3d(clean, Text3d::depth_compiler, 0.003f, pos, ConfigColors::color_compiler);
     t->yaw = tf.yaw;
 
     TextWallPtr wt = cpputils::make_unique<TextWall>(TextWall{
diff --git a/src/systems/hint_system.cpp b/src/systems/hint_system.cpp
index 53fa313..cd57906 100644
--- a/src/systems/hint_system.cpp
+++ b/src/systems/hint_system.cpp
@@ -5,7 +5,7 @@ HintSystem::HintSystem()
 {
     _label.set_Centre(Vec3{ 0.0, static_cast<double>(-ConfigGame::world_box_scale) + 2.5, 0.0 });
     _label.set_Billboard(true);
-    _label.set_Style(Text3d::color_yellow, Text3d::depth_wall, 0.003f);
+    _label.set_Style(ConfigColors::color_yellow, Text3d::depth_wall, 0.003f);
     _label.set_Collidable(true);
 }
 
diff --git a/src/systems/mod_loader_system.cpp b/src/systems/mod_loader_system.cpp
index 335dd8b..872302c 100644
--- a/src/systems/mod_loader_system.cpp
+++ b/src/systems/mod_loader_system.cpp
@@ -154,7 +154,7 @@ ModLoader::push_Line(TextWallList& texts, const std::string& line, float world_s
 
     std::string clean = ascii_Sanitize(line);
     Text3dPtr t = make_Text3d(clean, Text3d::depth_compiler, 0.003f,
-                              pos, Text3d::color_compiler);
+                              pos, ConfigColors::color_compiler);
     t->yaw = tf.yaw;
 
     texts.push_back(cpputils::make_unique<TextWall>(TextWall{
diff --git a/src/systems/program_system.cpp b/src/systems/program_system.cpp
index 4bb5699..5c7bf68 100644
--- a/src/systems/program_system.cpp
+++ b/src/systems/program_system.cpp
@@ -82,7 +82,7 @@ ProgramSystem::push_Line(TextWallList& texts, const std::string& line, float wor
 
     // Program wall is light ivory → use a dark text colour for contrast.
     Text3dPtr t = make_Text3d(clean, Text3d::depth_wall, 0.003f,
-                              pos, Text3d::color_dark_gray);
+                              pos, ConfigColors::color_dark_gray);
     t->yaw = tf.yaw;
 
     texts.push_back(cpputils::make_unique<TextWall>(TextWall{
diff --git a/src/systems/projectile_system.cpp b/src/systems/projectile_system.cpp
index 8de0c7e..2e5bc7a 100644
--- a/src/systems/projectile_system.cpp
+++ b/src/systems/projectile_system.cpp
@@ -352,7 +352,7 @@ ProjectileSystem::launch(DynArray<Projectile>&   out,
     for (Text3dPtr& w : words) {
         if (!w) continue;
         if (w->type == ObjectType::Text)
-            w->set_Look(Text3d::color_keep, Text3d::depth_floor);
+            w->set_Look(ConfigColors::color_keep, Text3d::depth_floor);
 
         w->yaw = static_cast<float>(yaw);
     }
diff --git a/src/systems/word_ai_system.cpp b/src/systems/word_ai_system.cpp
index 2827931..23a0b79 100644
--- a/src/systems/word_ai_system.cpp
+++ b/src/systems/word_ai_system.cpp
@@ -57,12 +57,12 @@ WordAiSystem::update(DynArray<Text3dPtr>& words,
                 if (!s) continue;
                 s->body.mode = PhysicsMode::Dynamic;
                 s->body.vel  = w->body.vel * 0.5;
-                s->set_Color(s->token_info ? TokenColor::get(s->token_info->type) : Text3d::color_floor);
+                s->set_Color(s->token_info ? TokenColor::get(s->token_info->type) : ConfigColors::color_floor);
                 released.push_back(std::move(s));   // deferred — not words.push_back
             }
             w->ai.stolen.clear();
             w->ai.state = Text3dAI::State::Idle;
-            w->set_Color(w->token_info ? TokenColor::get(w->token_info->type) : Text3d::color_floor);
+            w->set_Color(w->token_info ? TokenColor::get(w->token_info->type) : ConfigColors::color_floor);
             continue;
         }
 
@@ -98,12 +98,12 @@ WordAiSystem::update_Idle(Text3d& w, const Player& p, int active_ai_count, doubl
         to_player.y*to_player.y +
         to_player.z*to_player.z;
 
-    if (dist_sq > TRIGGER_RANGE * TRIGGER_RANGE) return;
+    if (dist_sq > ConfigAi::trigger_range * ConfigAi::trigger_range) return;
 
-    double roll_threshold = IDLE_AGGRO_ROLL * w.ai.aggressiveness * dt;
+    double roll_threshold = ConfigAi::idle_aggro_roll * w.ai.aggressiveness * dt;
     if (random_unit() > roll_threshold) return;
 
-    if (random_unit() < FLEE_BIAS) enter_Flee(w);
+    if (random_unit() < ConfigAi::flee_bias) enter_Flee(w);
     else                            enter_Attack(w);
 }
 
@@ -153,7 +153,7 @@ void WordAiSystem::update_Attack(Text3d& w, const Player& p, CarriedLine& carrie
 
         w.ai.wiggle_phase += w.ai.wiggle_freq * dt;
         Vec3 perp = perpendicular_horizontal(dir);
-        double wig = SDL_sin(w.ai.wiggle_phase) * WIGGLE_AMPLITUDE;
+        double wig = SDL_sin(w.ai.wiggle_phase) * ConfigAi::wiggle_amplitude;
         if (!has_carry) wig *= 0.4;   // gentler wobble while orbiting
         target_vel.x += perp.x * wig;
         target_vel.z += perp.z * wig;
@@ -171,10 +171,10 @@ void WordAiSystem::update_Attack(Text3d& w, const Player& p, CarriedLine& carrie
                 if (!node || !node->word) continue;
                 if (!Collision::aabb_Overlap(w, *node->word)) continue;
 
-                if (random_unit() < STEAL_PROBABILITY) {
+                if (random_unit() < ConfigAi::steal_probability) {
                     Text3dPtr stolen = std::move(node->word);
                     stolen->body.mode = PhysicsMode::AiHover;
-                    stolen->set_Color(Text3d::color_attack);
+                    stolen->set_Color(ConfigColors::color_attack);
                     w.ai.stolen.push_back(std::move(stolen));
                     enter_Flee(w);   // run away with the loot
                     carried.compact();
@@ -216,7 +216,7 @@ void WordAiSystem::update_Attack(Text3d& w, const Player& p, CarriedLine& carrie
 
     // ── Give up if player is too far ─────────────────────────────────
     Vec3 to_player = p.pos - w.pos;
-    if (length(to_player) > TRIGGER_RANGE * 3.0) {
+    if (length(to_player) > ConfigAi::trigger_range * 3.0) {
         release_StolenWords(w, released);
         enter_Stunned(w);
     }
@@ -246,7 +246,7 @@ WordAiSystem::update_Flee(Text3d& w, const Player& p, DynArray<Text3dPtr>& relea
 
         w.ai.wiggle_phase += w.ai.wiggle_freq * dt;
         Vec3 perp = perpendicular_horizontal(dir);
-        double wig = SDL_sin(w.ai.wiggle_phase) * WIGGLE_AMPLITUDE * 0.6;
+        double wig = SDL_sin(w.ai.wiggle_phase) * ConfigAi::wiggle_amplitude * 0.6;
         target_vel.x += perp.x * wig;
         target_vel.z += perp.z * wig;
     }
@@ -254,8 +254,8 @@ WordAiSystem::update_Flee(Text3d& w, const Player& p, DynArray<Text3dPtr>& relea
     apply_Locomotion(w, target_vel, dt);
 
     // Escape condition: far enough, for long enough.
-    if (dist > FLEE_ESCAPE_DIST) {
-        if (w.ai.state_timer > FLEE_ESCAPE_TIME) {
+    if (dist > ConfigAi::flee_escape_dist) {
+        if (w.ai.state_timer > ConfigAi::flee_escape_time) {
             // Got away. If carrying loot, roll for drop-vs-keep.
             if (!w.ai.stolen.empty()) {
                 if (random_unit() < ConfigAi::drop_loot_probability) {
@@ -277,7 +277,7 @@ WordAiSystem::update_Stunned(Text3d& w, DynArray<Text3dPtr>& released, double dt
 {
     apply_Locomotion(w, Vec3{}, dt);
 
-    if (w.ai.state_timer > STUNNED_DURATION) {
+    if (w.ai.state_timer > ConfigAi::stunned_duration) {
         release_StolenWords(w, released);
         enter_Idle(w);
     }
@@ -290,7 +290,7 @@ WordAiSystem::release_StolenWords(Text3d& w, DynArray<Text3dPtr>& out)
         if (!s) continue;
         s->body.mode = PhysicsMode::Dynamic;
         s->body.vel  = { w.body.vel.x * 0.5, 2.0, w.body.vel.z * 0.5 };
-        s->set_Color(s->token_info ? TokenColor::get(s->token_info->type) : Text3d::color_floor);
+        s->set_Color(s->token_info ? TokenColor::get(s->token_info->type) : ConfigColors::color_floor);
         out.push_back(std::move(s));
     }
     w.ai.stolen.clear();
@@ -307,7 +307,7 @@ WordAiSystem::enter_Attack(Text3d& w)
     w.ai.has_stolen        = false;
     w.ai.hover_height_saved = w.body.hover_height;
     w.body.mode            = PhysicsMode::AiHover;
-    w.set_Color(Text3d::color_attack);
+    w.set_Color(ConfigColors::color_attack);
 
     // Pick an orbit direction and starting angle for the no-carry wander.
     w.ai.orbit_angle = random_unit() * SDL_PI_D * 2.0;
@@ -324,7 +324,7 @@ WordAiSystem::enter_Flee(Text3d& w)
     w.ai.in_pause          = false;
     w.ai.hover_height_saved = w.body.hover_height;
     w.body.mode            = PhysicsMode::AiHover;
-    w.set_Color(Text3d::color_flee);
+    w.set_Color(ConfigColors::color_flee);
 }
 
 
@@ -334,7 +334,7 @@ WordAiSystem::enter_Stunned(Text3d& w)
     w.ai.state       = Text3dAI::State::Stunned;
     w.ai.state_timer = 0.0;
     w.body.vel       = Vec3{};
-    w.set_Color(Text3d::color_stunned);
+    w.set_Color(ConfigColors::color_stunned);
 }
 
 
@@ -345,14 +345,14 @@ WordAiSystem::enter_Idle(Text3d& w)
     w.ai.state_timer = 0.0;
     w.body.mode      = PhysicsMode::Hover;
     w.body.hover_height = w.ai.hover_height_saved;
-    w.set_Color(w.token_info ? TokenColor::get(w.token_info->type) : Text3d::color_floor);
+    w.set_Color(w.token_info ? TokenColor::get(w.token_info->type) : ConfigColors::color_floor);
 }
 
 
 void
 WordAiSystem::apply_Locomotion(Text3d& w, const Vec3& target_vel, double dt)
 {
-    double k = 1.0 - SDL_exp(-VELOCITY_LERP_RATE * dt);
+    double k = 1.0 - SDL_exp(-ConfigAi::velocity_lerp_rate * dt);
     w.body.vel.x += (target_vel.x - w.body.vel.x) * k;
     w.body.vel.y += (target_vel.y - w.body.vel.y) * k;
     w.body.vel.z += (target_vel.z - w.body.vel.z) * k;
diff --git a/src/systems/word_ai_system.hpp b/src/systems/word_ai_system.hpp
index c4cea5e..0003aa2 100644
--- a/src/systems/word_ai_system.hpp
+++ b/src/systems/word_ai_system.hpp
@@ -7,15 +7,6 @@ struct Player;
 struct CarriedLine;
 
 class WordAiSystem {
-    static constexpr double TRIGGER_RANGE     =  6.0;
-    static constexpr double IDLE_AGGRO_ROLL   =  0.4;   // per-second chance × aggressiveness
-    static constexpr double FLEE_BIAS         =  0.35;  // fraction of state changes that are Flee vs Attack
-    static constexpr double STUNNED_DURATION  =  1.2;
-    static constexpr double FLEE_ESCAPE_DIST  = 10.0;
-    static constexpr double FLEE_ESCAPE_TIME  =  2.0;
-    static constexpr double STEAL_PROBABILITY =  0.5;   // chance per contact
-    static constexpr double VELOCITY_LERP_RATE = 6.0;
-    static constexpr double WIGGLE_AMPLITUDE   = 0.8;
 
     float _world_scale;
     int   _max_active_ai = 5;
diff --git a/src/systems/word_anim.cpp b/src/systems/word_anim.cpp
index ef0a165..7257298 100644
--- a/src/systems/word_anim.cpp
+++ b/src/systems/word_anim.cpp
@@ -70,7 +70,7 @@ WordAnimSystem::update(DynArray<WordAnim_Add>&    anims_add,
             drop.word->body.vel.y  = 0.0;
             drop.word->body.mode   = PhysicsMode::Dynamic;
             if (drop.word->type == ObjectType::Text)
-                drop.word->set_Look(Text3d::color_keep, Text3d::depth_floor);
+                drop.word->set_Look(ConfigColors::color_keep, Text3d::depth_floor);
             words.push_back(std::move(drop.word));
         }
     }
diff --git a/src/text_3d/text_3d.hpp b/src/text_3d/text_3d.hpp
index 8a3f81b..d409049 100644
--- a/src/text_3d/text_3d.hpp
+++ b/src/text_3d/text_3d.hpp
@@ -7,6 +7,7 @@
 #include "cpputils/unique_ptr.hpp"
 #include "entities/game_object.hpp"
 #include "text_3d/text_mesh.hpp"
+#include "utils/config.hpp"
 #include "utils/globals.hpp"
 #include "utils/light.hpp"
 
@@ -51,36 +52,6 @@ struct Text3d final : public GameObject {
     bool   glow_enabled  { false };
     float  half_extent_y { 0.2f };
 
-    static inline constexpr SDL_FColor color_keep    = {0.0f, -1.0f, 0.0f,  1.0f};
-    static inline constexpr SDL_FColor color_attack  = {1.0f, 0.35f, 0.2f,  1.0f};
-    static inline constexpr SDL_FColor color_flee    = {1.0f, 0.95f, 0.4f,  1.0f};
-    static inline constexpr SDL_FColor color_stunned = {0.45f, 0.45f, 0.5f, 1.0f};
-
-    static inline constexpr SDL_FColor color_white   = {1.0f, 1.0f, 1.0f, 1.0f};
-    static inline constexpr SDL_FColor color_red     = {1.0f, 0.0f, 0.0f, 1.0f};
-    static inline constexpr SDL_FColor color_green   = {0.30f, 0.70f, 0.40f, 1.0f};
-    static inline constexpr SDL_FColor color_blue    = {0.0f, 0.86f, 1.0f, 1.0f};
-    static inline constexpr SDL_FColor color_yellow  = {1.0f, 1.0f, 0.0f, 1.0f};
-    static inline constexpr SDL_FColor color_cyan    = {0.0f, 1.0f, 1.0f, 1.0f};
-    static inline constexpr SDL_FColor color_magenta = {1.0f, 0.0f, 1.0f, 1.0f};
-    static inline constexpr SDL_FColor color_black   = {0.0f, 0.0f, 0.0f, 1.0f};
-    static inline constexpr SDL_FColor color_gray    = {0.5f, 0.5f, 0.5f, 1.0f};
-    static inline constexpr SDL_FColor color_orange     = {1.0f,  0.55f, 0.10f, 1.0f};
-    static inline constexpr SDL_FColor color_amber      = {1.0f,  0.75f, 0.20f, 1.0f};
-    static inline constexpr SDL_FColor color_lime       = {0.65f, 0.95f, 0.25f, 1.0f};
-    static inline constexpr SDL_FColor color_teal       = {0.0f,  0.65f, 0.60f, 1.0f};
-    static inline constexpr SDL_FColor color_sky        = {0.40f, 0.70f, 1.0f,  1.0f};
-    static inline constexpr SDL_FColor color_indigo     = {0.30f, 0.30f, 0.80f, 1.0f};
-    static inline constexpr SDL_FColor color_purple     = {0.60f, 0.30f, 0.85f, 1.0f};
-    static inline constexpr SDL_FColor color_pink       = {1.0f,  0.34f, 0.87f, 1.0f};
-    static inline constexpr SDL_FColor color_brown      = {0.55f, 0.35f, 0.20f, 1.0f};
-    static inline constexpr SDL_FColor color_dark_gray  = {0.25f, 0.25f, 0.25f, 1.0f};
-    static inline constexpr SDL_FColor color_light_gray = {0.82f, 0.82f, 0.82f, 1.0f};
-
-    static inline constexpr SDL_FColor color_compiler = color_white;
-    static inline constexpr SDL_FColor color_floor    = color_blue;
-    static inline constexpr SDL_FColor color_wall     = color_green;
-
     static inline constexpr float depth_wall     = 0.06f;
     static inline constexpr float depth_floor    = 0.14f;
     static inline constexpr float depth_compiler = 0.02f;
@@ -124,7 +95,7 @@ struct Text3d final : public GameObject {
     Text3d(const std::string& txt,
            float depth, float scale,
            Vec3 position,
-           SDL_FColor colr = color_blue);
+           SDL_FColor colr = ConfigColors::color_blue);
     ~Text3d() override;
 
     Vec3 get_Distance(Vec3 position);
diff --git a/src/text_3d/text_label.cpp b/src/text_3d/text_label.cpp
index f1df0e7..46aed0b 100644
--- a/src/text_3d/text_label.cpp
+++ b/src/text_3d/text_label.cpp
@@ -116,8 +116,8 @@ TextLabel::rebuild(const std::string& text)
     }
 
     float total_w = 0.0f;
-    for (auto& t : tmp) total_w += t.obj->half_extent * 2.0f + WORD_GAP;
-    total_w -= WORD_GAP;
+    for (auto& t : tmp) total_w += t.obj->half_extent * 2.0f + ConfigText3d::word_gap;
+    total_w -= ConfigText3d::word_gap;
 
     float x_cur = -total_w * 0.5f;
     for (auto& t : tmp) {
@@ -136,7 +136,7 @@ TextLabel::rebuild(const std::string& text)
         _jiggle_off.push_back(Vec3{});
         _jiggle_vel.push_back(Vec3{});
 
-        x_cur += he * 2.0f + WORD_GAP;
+        x_cur += he * 2.0f + ConfigText3d::word_gap;
     }
 }
 
@@ -229,9 +229,9 @@ TextLabel::update(double dt, const Camera& camera)
         Vec3& vel3 = _jiggle_vel[i];
 
         Vec3 acc{
-            -JIGGLE_STIFF * off3.x - JIGGLE_DAMP * vel3.x,
-            -JIGGLE_STIFF * off3.y - JIGGLE_DAMP * vel3.y,
-            -JIGGLE_STIFF * off3.z - JIGGLE_DAMP * vel3.z
+            -ConfigText3d::jiggle_stiff * off3.x - ConfigText3d::jiggle_damp * vel3.x,
+            -ConfigText3d::jiggle_stiff * off3.y - ConfigText3d::jiggle_damp * vel3.y,
+            -ConfigText3d::jiggle_stiff * off3.z - ConfigText3d::jiggle_damp * vel3.z
         };
         vel3.x += acc.x * dt;
         vel3.y += acc.y * dt;
@@ -243,9 +243,9 @@ TextLabel::update(double dt, const Camera& camera)
         auto clamp = [](double v, double lo, double hi) {
             return v < lo ? lo : (v > hi ? hi : v);
         };
-        off3.x = clamp(off3.x, -JIGGLE_MAX_OFF, JIGGLE_MAX_OFF);
-        off3.y = clamp(off3.y, -JIGGLE_MAX_OFF, JIGGLE_MAX_OFF);
-        off3.z = clamp(off3.z, -JIGGLE_MAX_OFF, JIGGLE_MAX_OFF);
+        off3.x = clamp(off3.x, -ConfigText3d::jiggle_max_off, ConfigText3d::jiggle_max_off);
+        off3.y = clamp(off3.y, -ConfigText3d::jiggle_max_off, ConfigText3d::jiggle_max_off);
+        off3.z = clamp(off3.z, -ConfigText3d::jiggle_max_off, ConfigText3d::jiggle_max_off);
 
         t.pos.x = anchor.x + off3.x;
         t.pos.y = anchor.y + off3.y;
@@ -276,7 +276,7 @@ TextLabel::kick(size_t word_index, const Vec3& projectile_vel, const Vec3& norma
                       projectile_vel.z * normal.z);
     if (v_into < 0.0) v_into = 0.0;
 
-    double mag = v_into * KICK_STRENGTH;
+    double mag = v_into * ConfigText3d::kick_strength;
 
     Vec3& vel3 = _jiggle_vel[word_index];
     vel3.x += -normal.x * mag;
diff --git a/src/text_3d/text_label.hpp b/src/text_3d/text_label.hpp
index 0e768fb..ffddd34 100644
--- a/src/text_3d/text_label.hpp
+++ b/src/text_3d/text_label.hpp
@@ -40,12 +40,6 @@ class TextLabel {
     Vec3 current_Centre() const;
 
 public:
-    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;
-
     TextLabel() = default;
 
     void set_Text(const std::string& text);
diff --git a/src/utils/config.hpp b/src/utils/config.hpp
index 2009b06..43705b3 100644
--- a/src/utils/config.hpp
+++ b/src/utils/config.hpp
@@ -4,52 +4,114 @@
 
 namespace ConfigGame
 {
-static constexpr int
-max_substeps    = 4;
-static constexpr float 
-world_box_scale = 16.f;
+static constexpr int   max_substeps    = 4;
+static constexpr float world_box_scale = 16.f;
 }
 
 
 namespace ConfigPhysics
 {
-static constexpr double
-binrad    = SDL_PI_D / 2147483648.0;
-static constexpr double
-proj_grav = 15.0;
-static constexpr double
-air_drag  = 0.993;
+static constexpr double binrad    = SDL_PI_D / 2147483648.0;
+static constexpr double proj_grav = 15.0;
+static constexpr double air_drag  = 0.993;
 
-static constexpr double
-shoot_speed = 34.0;
-static constexpr double
-stick_dist  =  0.2;
+static constexpr double shoot_speed = 34.0;
+static constexpr double stick_dist  =  0.2;
+
+static constexpr double player_jump_velocity = 8.4375;
+static constexpr double player_move_accel    = 60.0;
+}
+
+
+namespace ConfigColors
+{
+    static inline constexpr SDL_FColor color_keep    = {0.0f, -1.0f, 0.0f,  1.0f};
+    static inline constexpr SDL_FColor color_attack  = {1.0f, 0.35f, 0.2f,  1.0f};
+    static inline constexpr SDL_FColor color_flee    = {1.0f, 0.95f, 0.4f,  1.0f};
+    static inline constexpr SDL_FColor color_stunned = {0.45f, 0.45f, 0.5f, 1.0f};
+
+    static inline constexpr SDL_FColor color_white   = {1.0f, 1.0f, 1.0f, 1.0f};
+    static inline constexpr SDL_FColor color_red     = {1.0f, 0.0f, 0.0f, 1.0f};
+    static inline constexpr SDL_FColor color_green   = {0.30f, 0.70f, 0.40f, 1.0f};
+    static inline constexpr SDL_FColor color_blue    = {0.0f, 0.86f, 1.0f, 1.0f};
+    static inline constexpr SDL_FColor color_yellow  = {1.0f, 1.0f, 0.0f, 1.0f};
+    static inline constexpr SDL_FColor color_cyan    = {0.0f, 1.0f, 1.0f, 1.0f};
+    static inline constexpr SDL_FColor color_magenta = {1.0f, 0.0f, 1.0f, 1.0f};
+    static inline constexpr SDL_FColor color_black   = {0.0f, 0.0f, 0.0f, 1.0f};
+    static inline constexpr SDL_FColor color_gray    = {0.5f, 0.5f, 0.5f, 1.0f};
+    static inline constexpr SDL_FColor color_orange     = {1.0f,  0.55f, 0.10f, 1.0f};
+    static inline constexpr SDL_FColor color_amber      = {1.0f,  0.75f, 0.20f, 1.0f};
+    static inline constexpr SDL_FColor color_lime       = {0.65f, 0.95f, 0.25f, 1.0f};
+    static inline constexpr SDL_FColor color_teal       = {0.0f,  0.65f, 0.60f, 1.0f};
+    static inline constexpr SDL_FColor color_sky        = {0.40f, 0.70f, 1.0f,  1.0f};
+    static inline constexpr SDL_FColor color_indigo     = {0.30f, 0.30f, 0.80f, 1.0f};
+    static inline constexpr SDL_FColor color_purple     = {0.60f, 0.30f, 0.85f, 1.0f};
+    static inline constexpr SDL_FColor color_pink       = {1.0f,  0.34f, 0.87f, 1.0f};
+    static inline constexpr SDL_FColor color_brown      = {0.55f, 0.35f, 0.20f, 1.0f};
+    static inline constexpr SDL_FColor color_dark_gray  = {0.25f, 0.25f, 0.25f, 1.0f};
+    static inline constexpr SDL_FColor color_light_gray = {0.82f, 0.82f, 0.82f, 1.0f};
+
+    static inline constexpr SDL_FColor color_compiler = color_white;
+    static inline constexpr SDL_FColor color_floor    = color_blue;
+    static inline constexpr SDL_FColor color_wall     = color_green;
 }
 
 
 namespace ConfigText3d
 {
-static constexpr double
-hover_amp   = 0.3;
-static constexpr double
-hover_speed = 1.5;
+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;
 }
 
 
 namespace ConfigAi
 {
-static constexpr double
-drop_loot_probability = 0.35;
+static constexpr double drop_loot_probability = 0.35;
+static constexpr double trigger_range      =  6.0;
+static constexpr double idle_aggro_roll    =  0.4; // per-second chance × aggressiveness
+static constexpr double flee_bias          =  0.35;// fraction of state changes that are flee vs attack
+static constexpr double stunned_duration   =  1.2;
+static constexpr double flee_escape_dist   = 10.0;
+static constexpr double flee_escape_time   =  2.0;
+static constexpr double steal_probability  =  0.5; // chance per contact
+static constexpr double velocity_lerp_rate =  6.0;
+static constexpr double wiggle_amplitude   =  0.8;
 }
 
 namespace ConfigLevel
 {
-static constexpr double
-settle_time_for_step = 0.25;   // 250ms grace period
+static constexpr double settle_time_for_step = 0.25;// 250ms grace period
 }
 
 namespace ConfigWall
 {
-static constexpr float
-text_offset = 0.1f;   // distance from wall surface
+static constexpr float text_offset       = 0.1f;   // distance from wall surface
+static constexpr float max_tilt          = 0.90f;
+static constexpr float text_line_spacing = 0.45f;
+
+static constexpr float scroll_step       = 0.5f;
+
+static constexpr double line_height      = 0.4 + static_cast<double>(text_line_spacing);
+static constexpr double snap_threshold_y = line_height*0.3;  // 30% of line height
+static constexpr double word_gap         = 0.12;  // horizontal gap between snapped words
+static constexpr double free_space_dist  = 0.18;
+static constexpr double underline_drop   = 0.04;  // small gap below letter baseline
+static constexpr double underline_thick  = 0.005; // half-height of the underline strip
+
+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
+}
+
+
+namespace ConfigPult
+{
+static constexpr double press_dur    = 0.18;
+static constexpr double label_height = 1.1;
 }
diff --git a/src/wall/editor_wall.cpp b/src/wall/editor_wall.cpp
index 70bc2e0..00de647 100644
--- a/src/wall/editor_wall.cpp
+++ b/src/wall/editor_wall.cpp
@@ -19,7 +19,7 @@ EditorWall::Line*
 EditorWall::find_NearestLine(double y)
 {
     Line*  best    = nullptr;
-    double best_dy = SNAP_THRESHOLD_Y;
+    double best_dy = ConfigWall::snap_threshold_y;
     for (Line& l : _lines) {
         if (l.words.empty()) continue;
         const double center_y = l.words.front()->pos.y;   // any word on the row; they share y
@@ -45,7 +45,7 @@ EditorWall::place_OnLine(Line& line, double new_half_z, double impact_z)
 
     // Case A: left of the whole line — always flush, no impact.
     if (impact_z > left_edge(leftmost)) {
-        const double flush_z = left_edge(leftmost) + FREE_SPACE_DIST + new_half_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
@@ -53,7 +53,7 @@ EditorWall::place_OnLine(Line& line, double new_half_z, double impact_z)
 
     // Case B: right of the whole line — always flush.
     if (impact_z < right_edge(rightmost)) {
-        const double flush_z = right_edge(rightmost) - FREE_SPACE_DIST - new_half_z;
+        const double flush_z = right_edge(rightmost) - ConfigWall::free_space_dist - new_half_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;
@@ -65,17 +65,17 @@ EditorWall::place_OnLine(Line& line, double new_half_z, double impact_z)
         const double gap_left  = right_edge(line.words[i]);
         const double gap_right = left_edge(line.words[i + 1]);
         if (impact_z <= gap_left && impact_z >= gap_right) {
-            const double needed = 2.0 * new_half_z + 2.0 * FREE_SPACE_DIST;
+            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 };
             const double shift = needed - avail;
             for (size_t k = i + 1; k < n; ++k)
                 line.words[k]->pos.z -= shift;
-            return { gap_left - FREE_SPACE_DIST - new_half_z, i + 1 };
+            return { gap_left - ConfigWall::free_space_dist - new_half_z, i + 1 };
         }
     }
-    return { right_edge(rightmost) - FREE_SPACE_DIST - new_half_z, n };
+    return { right_edge(rightmost) - ConfigWall::free_space_dist - new_half_z, n };
 }
 
 
@@ -122,7 +122,7 @@ EditorWall::on_LinePlaced(const DynArray<Text3d*>& words_in, const Vec3& impact)
     double block_w = 0.0;
     for (size_t i = 0; i < ws.size(); ++i) {
         block_w += 2.0 * (double)ws[i]->half_extent;
-        if (i + 1 < ws.size()) block_w += FREE_SPACE_DIST;
+        if (i + 1 < ws.size()) block_w += ConfigWall::free_space_dist;
     }
 
     Line* target = find_NearestLine(impact.y);
@@ -151,14 +151,14 @@ EditorWall::on_LinePlaced(const DynArray<Text3d*>& words_in, const Vec3& impact)
     // Don't overlap the word to the LEFT of the slot: pull the block right (−z)
     // if centring would push its left edge into that word.
     if (insert_index > 0) {
-        const double max_left = right_edge(target->words[insert_index - 1]) - FREE_SPACE_DIST;
+        const double max_left = right_edge(target->words[insert_index - 1]) - ConfigWall::free_space_dist;
         if (block_left_z > max_left) block_left_z = max_left;
     }
     // Make room on the RIGHT: if the block would overlap the word to its right,
     // shove that word and everything past it further right (−z).
     if (insert_index < n) {
         const double block_right = block_left_z - block_w;
-        const double min_right   = left_edge(target->words[insert_index]) + FREE_SPACE_DIST;
+        const double min_right   = left_edge(target->words[insert_index]) + ConfigWall::free_space_dist;
         if (block_right < min_right) {
             const double shift = min_right - block_right;
             for (size_t k = insert_index; k < n; ++k)
@@ -174,7 +174,7 @@ EditorWall::on_LinePlaced(const DynArray<Text3d*>& words_in, const Vec3& impact)
         const double center_z = cursor - hw;
         w->pos.y = target->y;
         w->pos.z = center_z;
-        cursor   = center_z - hw - FREE_SPACE_DIST;
+        cursor   = center_z - hw - ConfigWall::free_space_dist;
     }
 
     // Insert the run into the line in visual order at the chosen slot.
@@ -218,10 +218,10 @@ make_Number(const std::string& s, const Vec3& pos, float yaw)
     // The Text3d constructor colours by token lookup (e.g. "1" is a Literal →
     // red), so force orange afterwards.
     Text3dPtr t = make_Text3d(s, Text3d::depth_wall,
-                              EditorWall::LINE_NUMBER_SCALE, pos,
-                              Text3d::color_orange);
+                              ConfigWall::line_number_scale, pos,
+                              ConfigColors::color_orange);
     t->yaw = yaw;
-    t->set_Color(Text3d::color_orange);
+    t->set_Color(ConfigColors::color_orange);
     return t;
 }
 
@@ -231,7 +231,7 @@ set_NumberText(Text3d* t, const std::string& s)
     if (t->text == s) return;
     t->text = s;
     t->rebuild_Mesh();
-    t->set_Color(Text3d::color_orange);   // rebuild can reset colour; keep orange
+    t->set_Color(ConfigColors::color_orange);   // rebuild can reset colour; keep orange
 }
 
 } // namespace
@@ -299,8 +299,8 @@ EditorWall::update(double dt, const Vec3& eye_pos)
     // a right number occupies [right_col_z - 2*rh, right_col_z] (outer = -z).
     const double ws   = ConfigGame::world_box_scale;
     const double EDGE = 0.3;
-    double left_col_z  = code_left  + LINE_NUMBER_GAP;
-    double right_col_z = code_right - LINE_NUMBER_GAP;
+    double left_col_z  = code_left  + ConfigWall::line_number_gap;
+    double right_col_z = code_right - ConfigWall::line_number_gap;
     left_col_z  = std::min(left_col_z,   ws - EDGE - 2.0 * max_lh);
     right_col_z = std::max(right_col_z, -ws + EDGE + 2.0 * max_rh);
 
@@ -367,7 +367,7 @@ void
 EditorWall::bounce(size_t line_id)
 {
     if (line_id < _lines.size())
-        _lines[line_id].bounce_vel += BOUNCE_IMPULSE;
+        _lines[line_id].bounce_vel += ConfigWall::bounce_impulse;
 }
 
 
@@ -391,9 +391,9 @@ EditorWall::draw(GlRenderer& gl, const Camera& cam, const Light& light) const
     for (const Line& l : _lines) {
         if (l.words.empty()) continue;
 
-        const float y_mid = static_cast<float>(l.y - UNDERLINE_DROP);
-        const float y_lo  = y_mid - static_cast<float>(UNDERLINE_THICK);
-        const float y_hi  = y_mid + static_cast<float>(UNDERLINE_THICK);
+        const float y_mid = static_cast<float>(l.y - ConfigWall::underline_drop);
+        const float y_lo  = y_mid - static_cast<float>(ConfigWall::underline_thick);
+        const float y_hi  = y_mid + static_cast<float>(ConfigWall::underline_thick);
 
         float pts[4][3] = {
             { x_front, y_lo, -ws },   // BL
diff --git a/src/wall/editor_wall.hpp b/src/wall/editor_wall.hpp
index 37339b8..820b056 100644
--- a/src/wall/editor_wall.hpp
+++ b/src/wall/editor_wall.hpp
@@ -21,10 +21,10 @@ class GlRenderer;
 //   pos.x  → fixed at -world_scale + small offset
 //
 // Snap rules:
-//   - Throws within SNAP_THRESHOLD_Y of an existing line's y → snap to it
+//   - 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 (WORD_GAP).
+//     a fixed horizontal gap (ConfigWall::word_gap).
 //
 // All thresholds expressed in world units. Tunable below.
 class EditorWall
@@ -59,17 +59,6 @@ class EditorWall
     Placement place_OnLine(Line& line, double new_half_z, double impact_z);
 public:
     // ── World-tuning constants (public, edit to taste) ───────────────────
-    static constexpr double LINE_HEIGHT      = 0.4 + static_cast<double>(Wall::TEXT_LINE_SPACING);
-    static constexpr double SNAP_THRESHOLD_Y = LINE_HEIGHT*0.3;  // 30% of line height
-    static constexpr double WORD_GAP         = 0.12;  // horizontal gap between snapped words
-    static constexpr double FREE_SPACE_DIST  = 0.18;
-    static constexpr double UNDERLINE_DROP   = 0.04;  // small gap below letter baseline
-    static constexpr double UNDERLINE_THICK  = 0.005; // half-height of the underline strip
-
-    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
-
     // 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;
diff --git a/src/wall/text_wall.cpp b/src/wall/text_wall.cpp
index 6c5cbb0..69fcf69 100644
--- a/src/wall/text_wall.cpp
+++ b/src/wall/text_wall.cpp
@@ -156,7 +156,7 @@ TextWall::show_TokenInfo(TextWallList& texts,
         pos.y = y;
 
         Text3dPtr t = make_Text3d(line, Text3d::depth_wall, 0.003f,
-                             pos, Text3d::color_white);
+                             pos, ConfigColors::color_white);
         t->yaw = tf.yaw;
 
         texts.push_back(cpputils::make_unique<TextWall>(TextWall{
@@ -165,6 +165,6 @@ TextWall::show_TokenInfo(TextWallList& texts,
             std::move(t)
         }));
 
-        y -= Wall::TEXT_LINE_SPACING;
+        y -= ConfigWall::text_line_spacing;
     }
 }
diff --git a/src/wall/text_wall_scroll.hpp b/src/wall/text_wall_scroll.hpp
index 60b1f8b..e9a12b0 100644
--- a/src/wall/text_wall_scroll.hpp
+++ b/src/wall/text_wall_scroll.hpp
@@ -7,13 +7,11 @@
 // ─────────────────────────────────────────────────────────────────────────────
 
 #include "text_wall.hpp"
+#include "utils/config.hpp"
 #include "wall/wall.hpp"
 
 namespace TextWallScroll
 {
-    // Amount (in world units) to shift each word up or down per scroll event
-    static constexpr float SCROLL_STEP = 0.5f;
-
     // Scroll all words on the given wall role up (+) or down (-)
     inline void
     scroll(TextWallList& texts, Wall::Role role, float delta)
@@ -27,11 +25,11 @@ namespace TextWallScroll
 
     inline void
     scroll_Up  (TextWallList& texts, Wall::Role role)
-    { scroll(texts, role,  SCROLL_STEP); }
+    { scroll(texts, role,  ConfigWall::scroll_step); }
 
     inline void
     scroll_Down(TextWallList& texts, Wall::Role role)
-    { scroll(texts, role, -SCROLL_STEP); }
+    { scroll(texts, role, -ConfigWall::scroll_step); }
 
     // Drop all words of the given wall role back to the floor as dynamic objects.
     // They are moved out of wall_texts and into floor_objects with Dynamic physics.
@@ -48,7 +46,7 @@ namespace TextWallScroll
             word->body.mode    = PhysicsMode::Dynamic;
             word->body.vel     = { 0.0, 1.5, 0.0 };   // small upward nudge
             word->body.hover_height = floor_y + 1.5;
-            word->set_Look(Text3d::color_floor, Text3d::depth_floor);
+            word->set_Look(ConfigColors::color_floor, Text3d::depth_floor);
 
             words.push_back(std::move(word));
         }
diff --git a/src/wall/wall.hpp b/src/wall/wall.hpp
index 95d86c5..ff7b6bc 100644
--- a/src/wall/wall.hpp
+++ b/src/wall/wall.hpp
@@ -4,8 +4,6 @@
 #include "utils/config.hpp"
 
 namespace Wall {
-    static constexpr float MAX_TILT = 0.90f;
-    static constexpr float TEXT_LINE_SPACING = 0.45f;
 
     enum class Position : uint8_t { PosX,    NegX,   PosZ,    NegZ     };
     enum class Role     : uint8_t { Program, Editor, Info,    Compiler };
@@ -92,8 +90,8 @@ namespace Wall {
         // Sign flipped: we want positive pitch when player is BELOW the text.
         double pitch = -SDL_atan2(dy, horiz);
 
-        if (pitch >  static_cast<double>(MAX_TILT)) pitch =  MAX_TILT;
-        if (pitch < -static_cast<double>(MAX_TILT)) pitch = -MAX_TILT;
+        if (pitch >  static_cast<double>(ConfigWall::max_tilt)) pitch =  ConfigWall::max_tilt;
+        if (pitch < -static_cast<double>(ConfigWall::max_tilt)) pitch = -ConfigWall::max_tilt;
         return static_cast<float>(pitch);
     }
 }
diff --git a/src/wall/wall_surfaces.cpp b/src/wall/wall_surfaces.cpp
index 18b7bf4..13374d9 100644
--- a/src/wall/wall_surfaces.cpp
+++ b/src/wall/wall_surfaces.cpp
@@ -145,7 +145,7 @@ WallSurfaces::draw(GlRenderer& gl, const Camera& cam, const Light& light) const
     gl.set_MVP(model, mvp);
     gl.set_NormalMat(model);
     gl.set_Light(light);
-    gl.set_TextLineSpacing(Wall::TEXT_LINE_SPACING);
+    gl.set_TextLineSpacing(ConfigWall::text_line_spacing);
 
     for (const WallQuad& w : _walls) {
         gl.set_Color(w.color.r, w.color.g, w.color.b);