From d6f5a7795360e3c2a6fc2d424904b6daa1f2accd Mon Sep 17 00:00:00 2001
From: Benoit Germain <benoit.germain@ubisoft.com>
Date: Tue, 16 Apr 2024 12:46:14 +0200
Subject: C++ migration: more conversion to InterCopyContext. debugspew
 indentation is managed by a scope object

---
 docs/index.html        |   2 +-
 src/deep.h             |   2 +-
 src/keeper.cpp         |  59 ++++++++-------
 src/lanes.cpp          |  61 ++++++++--------
 src/macros_and_utils.h |   7 +-
 src/state.cpp          |  44 ++++++------
 src/tools.cpp          | 192 +++++++++++++++++++++++++++----------------------
 src/tools.h            |  12 ++--
 src/universe.h         |  28 ++++++++
 9 files changed, 231 insertions(+), 176 deletions(-)

diff --git a/docs/index.html b/docs/index.html
index 67eccd5..9b3e5e7 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1706,7 +1706,7 @@ static MyDeepFactory g_MyDeepFactory;
 		Take a look at <tt>LindaFactory</tt> in <tt>linda.cpp</tt> or <tt>MyDeepFactory</tt> in <tt>deep_test.cpp</tt>.
 	</li>
 	<li>Include <tt>"deep.h"</tt> and either link against Lanes or statically compile <tt>compat.cpp deep.cpp tools.cpp universe.cpp</tt> into your module if you want to avoid a runtime dependency for users that will use your module without Lanes.
-	<li>Instanciate your userdata using <tt>yourFactoryObject.pushDeepUserdata()()</tt>, instead of the regular <tt>lua_newuserdata()</tt>. Given a <tt>factory</tt>, it sets up the support structures and returns a state-specific proxy userdata for accessing your data. This proxy can also be copied over to other lanes.</li>
+	<li>Instanciate your userdata using <tt>yourFactoryObject.pushDeepUserdata()</tt>, instead of the regular <tt>lua_newuserdata()</tt>. Given a <tt>factory</tt>, it sets up the support structures and returns a state-specific proxy userdata for accessing your data. This proxy can also be copied over to other lanes.</li>
 	<li>Accessing the deep userdata from your C code, use <tt>yourFactoryObject.toDeep()</tt> instead of the regular <tt>lua_touserdata()</tt>.</li>
 </ol>
 
diff --git a/src/deep.h b/src/deep.h
index 7c0aa6d..27aed8f 100644
--- a/src/deep.h
+++ b/src/deep.h
@@ -23,7 +23,7 @@ class Universe;
 
 enum class LookupMode
 {
-    LaneBody, // send the lane body directly from the source to the destination lane
+    LaneBody, // send the lane body directly from the source to the destination lane. keep this one first so that it's the value we get when we default-construct
     ToKeeper, // send a function from a lane to a keeper state
     FromKeeper // send a function from a keeper state to a lane
 };
diff --git a/src/keeper.cpp b/src/keeper.cpp
index 650789b..3e26d5e 100644
--- a/src/keeper.cpp
+++ b/src/keeper.cpp
@@ -226,18 +226,19 @@ int keeper_push_linda_storage(Universe* U, Dest L, void* ptr_, uintptr_t magic_)
         return 0;
     }
     // move data from keeper to destination state
-    lua_pushnil(KL);                                                        // storage nil
     STACK_GROW(L, 5);
     STACK_CHECK_START_REL(L, 0);
-    lua_newtable(L);                                                                           // out
+    lua_newtable(L);                                                                                     // out
+    InterCopyContext c{ U, L, KL, {}, {}, {}, LookupMode::FromKeeper, {} };
+    lua_pushnil(KL);                                                        // storage nil
     while (lua_next(KL, -2))                                                // storage key fifo
     {
         keeper_fifo* fifo = prepare_fifo_access(KL, -1);                    // storage key fifotbl
         lua_pushvalue(KL, -2);                                              // storage key fifotbl key
-        std::ignore = luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key fifotbl       // out key
+        std::ignore = c.inter_move(1);                                      // storage key fifotbl       // out key
         STACK_CHECK(L, 2);
         lua_newtable(L);                                                                                 // out key keyout
-        std::ignore = luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key               // out key keyout fifotbl
+        std::ignore = c.inter_move(1);                                      // storage key               // out key keyout fifotbl
         lua_pushinteger(L, fifo->first);                                                                 // out key keyout fifotbl first
         STACK_CHECK(L, 5);
         lua_setfield(L, -3, "first");                                                                    // out key keyout fifotbl
@@ -646,23 +647,24 @@ void close_keepers(Universe* U)
  * Note: Any problems would be design flaws; the created Lua state is left
  *       unclosed, because it does not really matter. In production code, this
  *       function never fails.
- * settings table is at position 1 on the stack
+ * settings table is expected at position 1 on the stack
  */
 void init_keepers(Universe* U, lua_State* L)
 {
+    ASSERT_L(lua_gettop(L) == 1 && lua_istable(L, 1));
     STACK_CHECK_START_REL(L, 0);                                   // L                            K
-    lua_getfield(L, 1, "nb_keepers");                              // nb_keepers
+    lua_getfield(L, 1, "nb_keepers");                              // settings nb_keepers
     int const nb_keepers{ static_cast<int>(lua_tointeger(L, -1)) };
-    lua_pop(L, 1);                                                 //
+    lua_pop(L, 1);                                                 // settings
     if (nb_keepers < 1)
     {
         luaL_error(L, "Bad number of keepers (%d)", nb_keepers); // doesn't return
     }
     STACK_CHECK(L, 0);
 
-    lua_getfield(L, 1, "keepers_gc_threshold");                    // keepers_gc_threshold
+    lua_getfield(L, 1, "keepers_gc_threshold");                    // settings keepers_gc_threshold
     int const keepers_gc_threshold{ static_cast<int>(lua_tointeger(L, -1)) };
-    lua_pop(L, 1);                                                 //
+    lua_pop(L, 1);                                                 // settings
     STACK_CHECK(L, 0);
 
     // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states
@@ -682,7 +684,7 @@ void init_keepers(Universe* U, lua_State* L)
             U->keepers->keeper_array[i].Keeper::Keeper();
         }
     }
-    for (int i = 0; i < nb_keepers; ++i)                           // keepersUD
+    for (int i = 0; i < nb_keepers; ++i)                           // settings
     {
         // note that we will leak K if we raise an error later
         lua_State* const K{ create_state(U, L) };
@@ -706,26 +708,28 @@ void init_keepers(Universe* U, lua_State* L)
 
         // make sure 'package' is initialized in keeper states, so that we have require()
         // this because this is needed when transferring deep userdata object
-        luaL_requiref(K, "package", luaopen_package, 1);           //                              package
-        lua_pop(K, 1);                                             //
+        luaL_requiref(K, "package", luaopen_package, 1);           // settings                     package
+        lua_pop(K, 1);                                             // settings
         STACK_CHECK(K, 0);
         serialize_require(DEBUGSPEW_PARAM_COMMA(U) K);
         STACK_CHECK(K, 0);
 
-        // copy package.path and package.cpath from the source state
-        lua_getglobal(L, "package");                               // "..." keepersUD package
+        // copy package.path and package.cpath from the source state (TODO: use _R._LOADED.package instead of _G.package)
+        lua_getglobal(L, "package");                               // settings package
         if (!lua_isnil(L, -1))
         {
             // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately
-            if (luaG_inter_copy_package(U, Source{ L }, Dest{ K }, -1, LookupMode::ToKeeper) != InterCopyResult::Success)
+            InterCopyContext c{ U, Dest{ K }, Source{ L }, {}, SourceIndex{ lua_absindex(L, -1) }, {}, LookupMode::ToKeeper, {} };
+            if (c.inter_copy_package() != InterCopyResult::Success)
             {
                 // if something went wrong, the error message is at the top of the stack
-                lua_remove(L, -2);                                 // error_msg
+                lua_remove(L, -2);                                 // settings error_msg
                 raise_lua_error(L);
             }
         }
-        lua_pop(L, 1);                                             //
+        lua_pop(L, 1);                                             // settings
         STACK_CHECK(L, 0);
+        STACK_CHECK(K, 0);
 
         // attempt to call on_state_create(), if we have one and it is a C function
         // (only support a C function because we can't transfer executable Lua code in keepers)
@@ -837,20 +841,23 @@ KeeperCallResult keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_
     ASSERT_L(lua_gettop(K) == 0);
 
     STACK_GROW(K, 2);
-
-    PUSH_KEEPER_FUNC(K, func_);                                                                                            // func_
-
-    lua_pushlightuserdata(K, linda);                                                                                       // func_ linda
-
-    if ((args == 0) || luaG_inter_copy(U, Source{ L }, Dest{ K }, args, LookupMode::ToKeeper) == InterCopyResult::Success) // func_ linda args...
-    {
-        lua_call(K, 1 + args, LUA_MULTRET);                                                                                // result...
+    PUSH_KEEPER_FUNC(K, func_);                                                                                       // func_
+    lua_pushlightuserdata(K, linda);                                                                                  // func_ linda
+    if (
+        (args == 0) ||
+        (InterCopyContext{ U, Dest{ K }, Source{ L }, {}, {}, {}, LookupMode::ToKeeper, {} }.inter_copy(args) == InterCopyResult::Success)
+    )
+    {                                                                                                                 // func_ linda args...
+        lua_call(K, 1 + args, LUA_MULTRET);                                                                           // result...
         int const retvals{ lua_gettop(K) - top_K };
         // note that this can raise a luaL_error while the keeper state (and its mutex) is acquired
         // this may interrupt a lane, causing the destruction of the underlying OS thread
         // after this, another lane making use of this keeper can get an error code from the mutex-locking function
         // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread)
-        if ((retvals == 0) || (luaG_inter_move(U, Source{ K }, Dest{ L }, retvals, LookupMode::FromKeeper) == InterCopyResult::Success)) // K->L
+        if (
+            (retvals == 0) ||
+            (InterCopyContext{ U, Dest{ L }, Source{ K }, {}, {}, {}, LookupMode::FromKeeper, {} }.inter_move(retvals) == InterCopyResult::Success)
+        ) // K->L
         {
             result.emplace(retvals);
         }
diff --git a/src/lanes.cpp b/src/lanes.cpp
index 8b4410a..17d4f67 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -946,13 +946,12 @@ LUAG_FUNC(require)
     DEBUGSPEW_CODE(Universe* U = universe_get(L));
     STACK_CHECK_START_REL(L, 0);
     DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lanes.require %s BEGIN\n" INDENT_END, name));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
     lua_pushvalue(L, lua_upvalueindex(1)); // "name" require
     lua_insert(L, 1); // require "name"
     lua_call(L, nargs, 1); // module
     populate_func_lookup_table(L, -1, name);
     DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lanes.require %s END\n" INDENT_END, name));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
     STACK_CHECK(L, 0);
     return 1;
 }
@@ -972,10 +971,9 @@ LUAG_FUNC(register)
     DEBUGSPEW_CODE(Universe* U = universe_get(L));
     STACK_CHECK_START_REL(L, 0); // "name" mod_table
     DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lanes.register %s BEGIN\n" INDENT_END, name));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
     populate_func_lookup_table(L, -1, name);
     DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lanes.register %s END\n" INDENT_END, name));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
     STACK_CHECK(L, 0);
     return 0;
 }
@@ -1022,7 +1020,6 @@ LUAG_FUNC(lane_new)
 
     /* --- Create and prepare the sub state --- */
     DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: setup\n" INDENT_END));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
 
     // populate with selected libraries at the same time
     lua_State* const L2{ luaG_newstate(U, Source{ L }, libs_str) };         // L                                                                    // L2
@@ -1041,7 +1038,8 @@ LUAG_FUNC(lane_new)
         lua_State* const m_L;
         Lane* m_lane{ nullptr };
         int const m_gc_cb_idx;
-        DEBUGSPEW_CODE(Universe* const U); // for DEBUGSPEW only (hence the absence of m_ prefix)
+        DEBUGSPEW_CODE(Universe* const U);
+        DEBUGSPEW_CODE(DebugSpewIndentScope m_scope);
 
         public:
 
@@ -1050,7 +1048,9 @@ LUAG_FUNC(lane_new)
         , m_lane{ lane_ }
         , m_gc_cb_idx{ gc_cb_idx_ }
         DEBUGSPEW_COMMA_PARAM(U{ U_ })
-        {}
+        DEBUGSPEW_COMMA_PARAM(m_scope{ U_ })
+        {
+        }
 
         ~OnExit()
         {
@@ -1132,7 +1132,8 @@ LUAG_FUNC(lane_new)
     {
         DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: update 'package'\n" INDENT_END));
         // when copying with mode LookupMode::LaneBody, should raise an error in case of problem, not leave it one the stack
-        [[maybe_unused]] InterCopyResult const ret{ luaG_inter_copy_package(U, Source{ L }, Dest{ L2 }, package_idx, LookupMode::LaneBody) };
+        InterCopyContext c{ U, Dest{ L2 }, Source{ L }, {}, SourceIndex{ package_idx }, {}, {}, {} };
+        [[maybe_unused]] InterCopyResult const ret{ c.inter_copy_package() };
         ASSERT_L(ret == InterCopyResult::Success); // either all went well, or we should not even get here
     }
 
@@ -1141,7 +1142,7 @@ LUAG_FUNC(lane_new)
     {
         int nbRequired = 1;
         DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: require 'required' list\n" INDENT_END));
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+        DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
         // should not happen, was checked in lanes.lua before calling lane_new()
         if (lua_type(L, required_idx) != LUA_TTABLE)
         {
@@ -1175,10 +1176,8 @@ LUAG_FUNC(lane_new)
                     if (lua_pcall( L2, 1, 1, 0) != LUA_OK)                                                                                          // ret/errcode
                     {
                         // propagate error to main state if any
-                        std::ignore = luaG_inter_move(U
-                            , Source{ L2 }, Dest{ L }
-                            , 1, LookupMode::LaneBody
-                        );                                                  // func libs priority globals package required gc_cb [... args ...] n "modname" error
+                        InterCopyContext c{ U, Dest{ L }, Source{ L2 }, {}, {}, {}, {}, {} };
+                        std::ignore = c.inter_move(1);                                                                                              // func libs priority globals package required gc_cb [... args ...] n "modname" error
                         raise_lua_error(L);
                     }
                     // after requiring the module, register the functions it exported in our name<->function database
@@ -1189,7 +1188,6 @@ LUAG_FUNC(lane_new)
             lua_pop(L, 1);                                                  // func libs priority globals package required gc_cb [... args ...] n
             ++ nbRequired;
         }                                                                   // func libs priority globals package required gc_cb [... args ...]
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
     }
     STACK_CHECK(L, 0);
     STACK_CHECK(L2, 0);                                                                                                                             //
@@ -1205,20 +1203,19 @@ LUAG_FUNC(lane_new)
             luaL_error(L, "Expected table, got %s", luaL_typename(L, globals_idx)); // doesn't return
         }
 
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+        DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
         lua_pushnil(L);                                                     // func libs priority globals package required gc_cb [... args ...] nil
         // Lua 5.2 wants us to push the globals table on the stack
-        lua_pushglobaltable(L2);                                                                                                                    // _G
+        InterCopyContext c{ U, Dest{ L2 }, Source{ L }, {}, {}, {}, {}, {} };
+        lua_pushglobaltable(L2); // _G
         while( lua_next(L, globals_idx))                                    // func libs priority globals package required gc_cb [... args ...] k v
         {
-            std::ignore = luaG_inter_copy(U, Source{ L }, Dest{ L2 }, 2, LookupMode::LaneBody);                                                     // _G k v
+            std::ignore = c.inter_copy(2);                                                                                                          // _G k v
             // assign it in L2's globals table
             lua_rawset(L2, -3);                                                                                                                     // _G
             lua_pop(L, 1);                                                  // func libs priority globals package required gc_cb [... args ...] k
         }                                                                   // func libs priority globals package required gc_cb [... args ...]
         lua_pop( L2, 1);                                                                                                                            //
-
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
     }
     STACK_CHECK(L, 0);
     STACK_CHECK(L2, 0);
@@ -1228,10 +1225,10 @@ LUAG_FUNC(lane_new)
     if (func_type == LuaType::FUNCTION)
     {
         DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END));
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+        DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
         lua_pushvalue(L, 1);                                                // func libs priority globals package required gc_cb [... args ...] func
-        InterCopyResult const res{ luaG_inter_move(U, Source{ L }, Dest{ L2 }, 1, LookupMode::LaneBody) }; // func libs priority globals package required gc_cb [... args ...]     // func
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
+        InterCopyContext c{ U, Dest{ L2 }, Source{ L }, {}, {}, {}, {}, {} };
+        InterCopyResult const res{ c.inter_move(1) };                       // func libs priority globals package required gc_cb [... args ...]     // func
         if (res != InterCopyResult::Success)
         {
             luaL_error(L, "tried to copy unsupported types"); // doesn't return
@@ -1258,9 +1255,9 @@ LUAG_FUNC(lane_new)
     if (nargs > 0)
     {
         DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END));
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
-        InterCopyResult const res{ luaG_inter_move(U, Source{ L }, Dest{ L2 }, nargs, LookupMode::LaneBody) }; // func libs priority globals package required gc_cb                // func [... args ...]
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
+        DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
+        InterCopyContext c{ U, Dest{ L2 }, Source{ L }, {}, {}, {}, {}, {} };
+        InterCopyResult const res{ c.inter_move(nargs) };                   // func libs priority globals package required gc_cb                    // func [... args ...]
         if (res != InterCopyResult::Success)
         {
             luaL_error(L, "tried to copy unsupported types"); // doesn't return
@@ -1278,7 +1275,6 @@ LUAG_FUNC(lane_new)
     onExit.success();
     // we should have the lane userdata on top of the stack
     STACK_CHECK(L, 1);
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
     return 1;
 }
 
@@ -1423,7 +1419,10 @@ LUAG_FUNC(thread_join)
         case Lane::Done:
         {
             int const n{ lua_gettop(L2) }; // whole L2 stack
-            if ((n > 0) && (luaG_inter_move(U, Source{ L2 }, Dest{ L }, n, LookupMode::LaneBody) != InterCopyResult::Success))
+            if (
+                (n > 0) &&
+                (InterCopyContext{ U, Dest{ L }, Source{ L2 }, {}, {}, {}, {}, {} }.inter_move(n) != InterCopyResult::Success)
+            )
             {
                 luaL_error(L, "tried to copy unsupported types"); // doesn't return
             }
@@ -1437,7 +1436,8 @@ LUAG_FUNC(thread_join)
             STACK_GROW(L, 3);
             lua_pushnil(L);
             // even when ERROR_FULL_STACK, if the error is not LUA_ERRRUN, the handler wasn't called, and we only have 1 error message on the stack ...
-            if (luaG_inter_move(U, Source{ L2 }, Dest{ L }, n, LookupMode::LaneBody) != InterCopyResult::Success) // nil "err" [trace]
+            InterCopyContext c{ U, Dest{ L }, Source{ L2 }, {}, {}, {}, {}, {} };
+            if (c.inter_move(n) != InterCopyResult::Success) // nil "err" [trace]
             {
                 luaL_error(L, "tried to copy unsupported types: %s", lua_tostring(L, -n)); // doesn't return
             }
@@ -1772,12 +1772,12 @@ LUAG_FUNC(configure)
     STACK_CHECK_START_ABS(L, 1);                                                          // settings
 
     DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "%p: lanes.configure() BEGIN\n" INDENT_END, L));
-    DEBUGSPEW_CODE(if (U) U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
 
     if (U == nullptr)
     {
         U = universe_create(L);                                                           // settings universe
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+        DEBUGSPEW_CODE(DebugSpewIndentScope scope2{ U });
         lua_newtable( L);                                                                 // settings universe mt
         lua_getfield(L, 1, "shutdown_timeout");                                           // settings universe mt shutdown_timeout
         lua_getfield(L, 1, "shutdown_mode");                                              // settings universe mt shutdown_timeout shutdown_mode
@@ -1920,7 +1920,6 @@ LUAG_FUNC(configure)
     CONFIG_REGKEY.setValue(L, [](lua_State* L) { lua_pushvalue(L, -2); });
     STACK_CHECK(L, 1);
     DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "%p: lanes.configure() END\n" INDENT_END, L));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
     // Return the settings table
     return 1;
 }
diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h
index 77bcfe2..073e940 100644
--- a/src/macros_and_utils.h
+++ b/src/macros_and_utils.h
@@ -19,9 +19,8 @@ using namespace std::chrono_literals;
 
 #define USE_DEBUG_SPEW() 0
 #if USE_DEBUG_SPEW()
-extern char const* debugspew_indent;
 #define INDENT_BEGIN "%.*s "
-#define INDENT_END , (U ? U->debugspew_indent_depth.load(std::memory_order_relaxed) : 0), debugspew_indent
+#define INDENT_END , (U ? U->debugspew_indent_depth.load(std::memory_order_relaxed) : 0), DebugSpewIndentScope::debugspew_indent
 #define DEBUGSPEW_CODE(_code) _code
 #define DEBUGSPEW_OR_NOT(a_, b_) a_
 #define DEBUGSPEW_PARAM_COMMA(param_) param_,
@@ -114,8 +113,8 @@ class StackChecker
     }
 };
 
-#define STACK_CHECK_START_REL(L, offset_) StackChecker stackChecker_##L(L, StackChecker::Relative{ offset_ }, __FILE__, __LINE__)
-#define STACK_CHECK_START_ABS(L, offset_) StackChecker stackChecker_##L(L, StackChecker::Absolute{ offset_ }, __FILE__, __LINE__)
+#define STACK_CHECK_START_REL(L, offset_) StackChecker stackChecker_##L{L, StackChecker::Relative{ offset_ }, __FILE__, __LINE__}
+#define STACK_CHECK_START_ABS(L, offset_) StackChecker stackChecker_##L{L, StackChecker::Absolute{ offset_ }, __FILE__, __LINE__}
 #define STACK_CHECK_RESET_REL(L, offset_) stackChecker_##L = StackChecker{L, StackChecker::Relative{ offset_ }, __FILE__, __LINE__}
 #define STACK_CHECK_RESET_ABS(L, offset_) stackChecker_##L = StackChecker{L, StackChecker::Absolute{ offset_ }, __FILE__, __LINE__}
 #define STACK_CHECK(L, offset_) stackChecker_##L.check(offset_, __FILE__, __LINE__)
diff --git a/src/state.cpp b/src/state.cpp
index 4a5f995..1ba5a77 100644
--- a/src/state.cpp
+++ b/src/state.cpp
@@ -191,26 +191,27 @@ static void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U) lua_State* L, char const
 // #################################################################################################
 
 // just like lua_xmove, args are (from, to)
-static void copy_one_time_settings(Universe* U, Source L, Dest L2)
+static void copy_one_time_settings(Universe* U, Source L1, Dest L2)
 {
-    STACK_GROW(L, 2);
-    STACK_CHECK_START_REL(L, 0);
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
+
+    STACK_GROW(L1, 2);
+    STACK_CHECK_START_REL(L1, 0);
     STACK_CHECK_START_REL(L2, 0);
 
     DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "copy_one_time_settings()\n" INDENT_END));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
 
-    CONFIG_REGKEY.pushValue(L);                                                         // config
+    CONFIG_REGKEY.pushValue(L1);                                                        // config
     // copy settings from from source to destination registry
-    if (luaG_inter_move(U, L, L2, 1, LookupMode::LaneBody) != InterCopyResult::Success) //                           // config
+    InterCopyContext c{ U, L2, L1, {}, {}, {}, {}, {} };
+    if (c.inter_move(1) != InterCopyResult::Success)                                    //                           // config
     {
-        luaL_error( L, "failed to copy settings when loading lanes.core"); // doesn't return
+        luaL_error(L1, "failed to copy settings when loading lanes.core"); // doesn't return
     }
     // set L2:_R[CONFIG_REGKEY] = settings
     CONFIG_REGKEY.setValue(L2, [](lua_State* L) { lua_insert(L, -2); });                                             // config
     STACK_CHECK(L2, 0);
-    STACK_CHECK(L, 0);
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
+    STACK_CHECK(L1, 0);
 }
 
 // #################################################################################################
@@ -355,7 +356,7 @@ lua_State* luaG_newstate(Universe* U, Source from_, char const* libs_)
     }
 
     DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "luaG_newstate()\n" INDENT_END));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
 
     // copy settings (for example because it may contain a Lua on_state_create function)
     copy_one_time_settings( U, from_, L);
@@ -426,24 +427,23 @@ lua_State* luaG_newstate(Universe* U, Source from_, char const* libs_)
     STACK_CHECK(L, 1);
     populate_func_lookup_table(L, -1, nullptr);
 
-#if 0 && USE_DEBUG_SPEW()
+#if 1 && USE_DEBUG_SPEW()
     // dump the lookup database contents
-    lua_getfield(L, LUA_REGISTRYINDEX, LOOKUP_REGKEY);                                                    // {}
-    lua_pushnil(L);                                                                                       // {} nil
-    while (lua_next(L, -2))                                                                               // {} k v
+    LOOKUP_REGKEY.pushValue(L);                                                                                                // {}
+    lua_pushnil(L);                                                                                                            // {} nil
+    while (lua_next(L, -2))                                                                                                    // {} k v
     {
-        lua_getglobal(L, "print");                                                                        // {} k v print
-        lua_pushlstring(L, debugspew_indent, U->debugspew_indent_depth.load(std::memory_order_relaxed));  // {} k v print " "
-        lua_pushvalue(L, -4);                                                                             // {} k v print " " k
-        lua_pushvalue(L, -4);                                                                             // {} k v print " " k v
-        lua_call(L, 3, 0);                                                                                // {} k v
-        lua_pop(L, 1);                                                                                    // {} k
+        lua_getglobal(L, "print");                                                                                             // {} k v print
+        lua_pushlstring(L, DebugSpewIndentScope::debugspew_indent, U->debugspew_indent_depth.load(std::memory_order_relaxed)); // {} k v print " "
+        lua_pushvalue(L, -4);                                                                                                  // {} k v print " " k
+        lua_pushvalue(L, -4);                                                                                                  // {} k v print " " k v
+        lua_call(L, 3, 0);                                                                                                     // {} k v
+        lua_pop(L, 1);                                                                                                         // {} k
     }
-    lua_pop(L, 1);                                                                                        // {}
+    lua_pop(L, 1);                                                                                                             // {}
 #endif // USE_DEBUG_SPEW()
 
     lua_pop(L, 1);
     STACK_CHECK(L, 0);
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
     return L;
 }
diff --git a/src/tools.cpp b/src/tools.cpp
index c995fdd..ad706be 100644
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -38,7 +38,7 @@ THE SOFTWARE.
 // functions implemented in deep.c
 extern void push_registry_subtable( lua_State* L, UniqueKey key_);
 
-DEBUGSPEW_CODE( char const* debugspew_indent = "----+----!----+----!----+----!----+----!----+----!----+----!----+----!----+");
+DEBUGSPEW_CODE(char const* const DebugSpewIndentScope::debugspew_indent = "----+----!----+----!----+----!----+----!----+----!----+----!----+----!----+");
 
 
 // ################################################################################################
@@ -348,7 +348,7 @@ static void update_lookup_entry(DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L
     char const* prevName;
     DEBUGSPEW_CODE(char const *newName);
     DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "update_lookup_entry()\n" INDENT_END));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
 
     STACK_CHECK_START_REL(L, 0);
     // first, raise an error if the function is already known
@@ -408,7 +408,6 @@ static void update_lookup_entry(DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L
     }
     -- _depth;
     STACK_CHECK(L, -1);
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
 }
 
 // #################################################################################################
@@ -423,7 +422,7 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U)
     // we need to remember subtables to process them after functions encountered at the current depth (breadth-first search)
     int const breadth_first_cache = lua_gettop( L) + 1;
     DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "populate_func_lookup_table_recur()\n" INDENT_END));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
 
     STACK_GROW( L, 6);
     // slot _i contains a table where we search for functions (or a full userdata with a metatable)
@@ -445,7 +444,6 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U)
     if( visit_count > 0)
     {
         DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "already visited\n" INDENT_END));
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
         return;
     }
 
@@ -500,7 +498,7 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U)
     {
         DEBUGSPEW_CODE( char const* key = (lua_type( L, -2) == LUA_TSTRING) ? lua_tostring( L, -2) : "not a string");
         DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "table '%s'\n" INDENT_END, key));
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+        DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
         // un-visit this table in case we do need to process it
         lua_pushvalue( L, -1);                                                                  // ... {_i} {bfc} k {} {}
         lua_rawget( L, cache);                                                                  // ... {_i} {bfc} k {} n
@@ -523,7 +521,6 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U)
         populate_func_lookup_table_recur( DEBUGSPEW_PARAM_COMMA( U) L, _ctx_base, lua_gettop( L), _depth);
         lua_pop( L, 1);                                                                         // ... {_i} {bfc} k
         STACK_CHECK( L, 2);
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
     }
     // remove table name from fqn stack
     lua_pushnil( L);                                                                          // ... {_i} {bfc} nil
@@ -533,7 +530,6 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U)
     lua_pop( L, 1);                                                                           // ... {_i}
     STACK_CHECK( L, 0);
     // we are done                                                                            // ... {_i} {bfc}
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
 }
 
 // #################################################################################################
@@ -548,8 +544,8 @@ void populate_func_lookup_table(lua_State* L, int i_, char const* name_)
     int start_depth = 0;
     DEBUGSPEW_CODE( Universe* U = universe_get( L));
     DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%p: populate_func_lookup_table('%s')\n" INDENT_END, L, name_ ? name_ : "nullptr"));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
-    STACK_GROW( L, 3);
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
+    STACK_GROW(L, 3);
     STACK_CHECK_START_REL(L, 0);
     LOOKUP_REGKEY.pushValue(L);                                                      // {}
     STACK_CHECK( L, 1);
@@ -599,7 +595,6 @@ void populate_func_lookup_table(lua_State* L, int i_, char const* name_)
         (void) luaL_error( L, "unsupported module type %s", lua_typename( L, lua_type( L, in_base)));
     }
     STACK_CHECK( L, 0);
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
 }
 
 // #################################################################################################
@@ -1782,9 +1777,8 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull };
     else // regular function
     {
         DEBUGSPEW_CODE(fprintf( stderr, "FUNCTION %s\n", name));
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+        DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
         copy_cached_func();                                                                                     // ... f
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
     }
     STACK_CHECK(L2, 1);
     STACK_CHECK(L1, 0);
@@ -1872,13 +1866,14 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull };
     STACK_CHECK_START_REL(L1, 0);                                                      // L1                          // L2
     STACK_CHECK_START_REL(L2, 0);                                                      // L1                          // L2
 
-    DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "inter_copy_one()\n" INDENT_END));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
-    DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "%s %s: " INDENT_END, lua_type_names[val_type], vt_names[static_cast<int>(vt)]));
+    DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "inter_copy_one()\n" INDENT_END));
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
 
-    // Non-POD can be skipped if its metatable contains { __lanesignore = true }
     LuaType val_type{ lua_type_as_enum(L1, L1_i) };
-    if( ((1 << static_cast<int>(val_type)) & pod_mask) == 0)
+    DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "%s %s: " INDENT_END, lua_type_names[static_cast<int>(val_type)], vt_names[static_cast<int>(vt)]));
+
+    // Non-POD can be skipped if its metatable contains { __lanesignore = true }
+    if (((1 << static_cast<int>(val_type)) & pod_mask) == 0)
     {
         if (lua_getmetatable(L1, L1_i))                                                // ... mt
         {
@@ -1974,8 +1969,6 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull };
         break;
     }
 
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
-
     STACK_CHECK(L2, ret ? 1 : 0);
     STACK_CHECK(L1, 0);
     return ret;
@@ -1983,30 +1976,25 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull };
 
 // #################################################################################################
 
-/*
-* Akin to 'lua_xmove' but copies values between _any_ Lua states.
-*
-* NOTE: Both the states must be solely in the current OS thread's posession.
-*
-* Note: Parameters are in this order ('L' = from first) to be same as 'lua_xmove'.
-*/
-[[nodiscard]] InterCopyResult luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_)
+// Akin to 'lua_xmove' but copies values between _any_ Lua states.
+// NOTE: Both the states must be solely in the current OS thread's possession.
+[[nodiscard]] InterCopyResult InterCopyContext::inter_copy(int n_) const
 {
-    int const top_L{ lua_gettop(L) };                             // ... {}n
+    _ASSERT_L(L1, vt == VT::NORMAL);
 
-    DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "luaG_inter_copy()\n" INDENT_END));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
+    DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "InterCopyContext::inter_copy()\n" INDENT_END));
+    DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
 
-    if (n > top_L)
+    int const top_L1{ lua_gettop(L1) };
+    if (n_ > top_L1)
     {
         // requesting to copy more than is available?
         DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "nothing to copy()\n" INDENT_END));
-        DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
         return InterCopyResult::NotEnoughValues;
     }
 
     STACK_CHECK_START_REL(L2, 0);
-    STACK_GROW(L2, n + 1);
+    STACK_GROW(L2, n_ + 1);
 
     /*
     * Make a cache table for the duration of this copy. Collects tables and
@@ -2018,10 +2006,10 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull };
 
     char tmpBuf[16];
     char const* const pBuf{ U->verboseErrors ? tmpBuf : "?" };
-    InterCopyContext c{ U, L2, L, CacheIndex{ top_L2 + 1 }, {}, VT::NORMAL, mode_, pBuf };
+    InterCopyContext c{ U, L2, L1, CacheIndex{ top_L2 + 1 }, {}, VT::NORMAL, mode, pBuf };
     bool copyok{ true };
-    STACK_CHECK_START_REL(L, 0);
-    for (int i = top_L - n + 1, j = 1; i <= top_L; ++i, ++j)
+    STACK_CHECK_START_REL(L1, 0);
+    for (int i{ top_L1 - n_ + 1 }, j{ 1 }; i <= top_L1; ++i, ++j)
     {
         if (U->verboseErrors)
         {
@@ -2034,13 +2022,11 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull };
             break;
         }
     }
-    STACK_CHECK(L, 0);
-
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
+    STACK_CHECK(L1, 0);
 
     if (copyok)
     {
-        STACK_CHECK(L2, n + 1);
+        STACK_CHECK(L2, n_ + 1);
         // Remove the cache table. Persistent caching would cause i.e. multiple
         // messages passed in the same table to use the same table also in receiving end.
         lua_remove(L2, top_L2 + 1);
@@ -2055,74 +2041,108 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull };
 
 // #################################################################################################
 
-[[nodiscard]] InterCopyResult luaG_inter_move(Universe* U, Source L, Dest L2, int n_, LookupMode mode_)
+[[nodiscard]] InterCopyResult InterCopyContext::inter_move(int n_) const
 {
-    InterCopyResult const ret{ luaG_inter_copy(U, L, L2, n_, mode_) };
-    lua_pop( L, n_);
+    InterCopyResult const ret{ inter_copy(n_) };
+    lua_pop( L1, n_);
     return ret;
 }
 
 // #################################################################################################
 
-// transfers stuff from L->_G["package"] to L2->_G["package"]
+// transfers stuff from L1->_G["package"] to L2->_G["package"]
 // returns InterCopyResult::Success if everything is fine
-// returns InterCopyResult::Error if pushed an error message in L
-// else raise an error in L
-[[nodiscard]] InterCopyResult luaG_inter_copy_package(Universe* U, Source L, Dest L2, int package_idx_, LookupMode mode_)
+// returns InterCopyResult::Error if pushed an error message in L1
+// else raise an error in L1
+[[nodiscard]] InterCopyResult InterCopyContext::inter_copy_package() const
 {
-    DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "luaG_inter_copy_package()\n" INDENT_END));
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
-    // package
-    STACK_CHECK_START_REL(L, 0);
-    STACK_CHECK_START_REL(L2, 0);
-    package_idx_ = lua_absindex(L, package_idx_);
-    if (lua_type(L, package_idx_) != LUA_TTABLE)
+    DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "InterCopyContext::inter_copy_package()\n" INDENT_END));
+
+    class OnExit
     {
-        lua_pushfstring(L, "expected package as table, got %s", luaL_typename(L, package_idx_));
-        STACK_CHECK(L, 1);
+        private:
+
+        lua_State* const L2;
+        int const top_L2;
+        DEBUGSPEW_CODE(DebugSpewIndentScope m_scope);
+
+        public:
+
+        OnExit(Universe* U_, lua_State* L2_)
+        : L2{ L2_ }
+        , top_L2{ lua_gettop(L2) }
+        DEBUGSPEW_COMMA_PARAM(m_scope{ U_ })
+        {
+        }
+
+        ~OnExit()
+        {
+            lua_settop(L2, top_L2);
+        }
+    } onExit{ U, L2 };
+
+    STACK_CHECK_START_REL(L1, 0);
+    if (lua_type_as_enum(L1, L1_i) != LuaType::TABLE)
+    {
+        lua_pushfstring(L1, "expected package as table, got %s", luaL_typename(L1, L1_i));
+        STACK_CHECK(L1, 1);
         // raise the error when copying from lane to lane, else just leave it on the stack to be raised later
-        if (mode_ == LookupMode::LaneBody)
+        if (mode == LookupMode::LaneBody)
         {
-            lua_error(L); // doesn't return
+            lua_error(L1); // doesn't return
         }
         return InterCopyResult::Error;
     }
-    lua_getglobal(L2, "package");
-    if (!lua_isnil(L2, -1)) // package library not loaded: do nothing
+    lua_getglobal(L2, "package"); // TODO: use _R._LOADED.package instead of _G.package
+    if (lua_isnil(L2, -1)) // package library not loaded: do nothing
     {
-        // package.loaders is renamed package.searchers in Lua 5.2
-        // but don't copy it anyway, as the function names change depending on the slot index!
-        // users should provide an on_state_create function to setup custom loaders instead
-        // don't copy package.preload in keeper states (they don't know how to translate functions)
-        char const* entries[] = { "path", "cpath", (mode_ == LookupMode::LaneBody) ? "preload" : nullptr /*, (LUA_VERSION_NUM == 501) ? "loaders" : "searchers"*/, nullptr };
-        for (char const* const entry : entries)
+        DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "'package' not loaded, nothing to do\n" INDENT_END));
+        STACK_CHECK(L1, 0);
+        return InterCopyResult::Success;
+    }
+
+    InterCopyResult result{ InterCopyResult::Success };
+    // package.loaders is renamed package.searchers in Lua 5.2
+    // but don't copy it anyway, as the function names change depending on the slot index!
+    // users should provide an on_state_create function to setup custom loaders instead
+    // don't copy package.preload in keeper states (they don't know how to translate functions)
+    char const* entries[] = { "path", "cpath", (mode == LookupMode::LaneBody) ? "preload" : nullptr /*, (LUA_VERSION_NUM == 501) ? "loaders" : "searchers"*/, nullptr };
+    for (char const* const entry : entries)
+    {
+        if (!entry)
+        {
+            continue;
+        }
+        DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "package.%s\n" INDENT_END, entry));
+        lua_getfield(L1, L1_i, entry);
+        if (lua_isnil(L1, -1))
+        {
+            lua_pop(L1, 1);
+        }
+        else
         {
-            if (!entry)
             {
-                continue;
+                DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U });
+                result = inter_move(1); // moves the entry to L2
+                STACK_CHECK(L1, 0);
             }
-            DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "package.%s\n" INDENT_END, entry));
-            lua_getfield(L, package_idx_, entry);
-            if (lua_isnil(L, -1))
+            if (result == InterCopyResult::Success)
             {
-                lua_pop(L, 1);
+                lua_setfield(L2, -2, entry); // set package[entry]
             }
             else
             {
-                DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed));
-                std::ignore = luaG_inter_move(U, L, L2, 1, mode_); // moves the entry to L2
-                DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
-                lua_setfield(L2, -2, entry); // set package[entry]
+                lua_pushfstring(L1, "failed to copy package entry %s", entry);
+                // raise the error when copying from lane to lane, else just leave it on the stack to be raised later
+                if (mode == LookupMode::LaneBody)
+                {
+                    lua_error(L1); // doesn't return
+                }
+                lua_pop(L1, 1);
+                break;
             }
         }
     }
-    else
-    {
-        DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "'package' not loaded, nothing to do\n" INDENT_END));
-    }
-    lua_pop(L2, 1);
-    STACK_CHECK(L2, 0);
-    STACK_CHECK(L, 0);
-    DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed));
-    return InterCopyResult::Success;
+    STACK_CHECK(L1, 0);
+    return result;
 }
diff --git a/src/tools.h b/src/tools.h
index e14ac65..06646d3 100644
--- a/src/tools.h
+++ b/src/tools.h
@@ -19,7 +19,7 @@ void push_registry_subtable(lua_State* L, UniqueKey key_);
 
 enum class VT
 {
-    NORMAL,
+    NORMAL, // keep this one first so that it's the value we get when we default-construct
     KEY,
     METATABLE
 };
@@ -60,14 +60,16 @@ struct InterCopyContext
     void copy_func() const;
     void copy_cached_func() const;
     void inter_copy_keyvaluepair() const;
+
+    public:
+
+    [[nodiscard]] InterCopyResult inter_copy_package() const;
+    [[nodiscard]] InterCopyResult inter_copy(int n_) const;
+    [[nodiscard]] InterCopyResult inter_move(int n_) const;
 };
 
 // ################################################################################################
 
-[[nodiscard]] InterCopyResult luaG_inter_copy_package(Universe* U, Source L, Dest L2, int package_idx_, LookupMode mode_);
-[[nodiscard]] InterCopyResult luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_);
-[[nodiscard]] InterCopyResult luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_);
-
 [[nodiscard]] int luaG_nameof(lua_State* L);
 
 void populate_func_lookup_table(lua_State* L, int _i, char const* _name);
diff --git a/src/universe.h b/src/universe.h
index 113ed21..eb85133 100644
--- a/src/universe.h
+++ b/src/universe.h
@@ -188,3 +188,31 @@ class Universe
 [[nodiscard]] Universe* universe_get(lua_State* L);
 [[nodiscard]] Universe* universe_create(lua_State* L);
 void universe_store(lua_State* L, Universe* U);
+
+// ################################################################################################
+
+#if USE_DEBUG_SPEW()
+class DebugSpewIndentScope
+{
+    private:
+
+    Universe* const U;
+
+    public:
+
+    static char const* const debugspew_indent;
+
+    DebugSpewIndentScope(Universe* U_)
+    : U{ U_ }
+    {
+        if (U)
+            U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed);
+    }
+
+    ~DebugSpewIndentScope()
+    {
+        if (U)
+            U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed);
+    }
+};
+#endif // USE_DEBUG_SPEW()
-- 
cgit v1.2.3-55-g6feb