diff --git a/polymer/eduke32/source/lunatic/defs.ilua b/polymer/eduke32/source/lunatic/defs.ilua
index 707740679..af6c93a01 100644
--- a/polymer/eduke32/source/lunatic/defs.ilua
+++ b/polymer/eduke32/source/lunatic/defs.ilua
@@ -465,7 +465,8 @@ projectile_t;
 typedef struct {
     uint32_t _flags;  // XXX: do we want to have this accessible at game time?
     int32_t _cacherange;
-    const projectile_t _defproj;
+    projectile_t *_proj;
+    const projectile_t *_defproj;
 } tiledata_t;
 
 typedef struct {
@@ -647,7 +648,6 @@ weapondata_x_MAX_WEAPONS g_playerWeapon[MAXPLAYERS];
 weapondata_t g_weaponOverridden[MAX_WEAPONS];
 int16_t WeaponPickupSprites[MAX_WEAPONS];
 tiledata_t g_tile[MAXTILES];
-projectile_t ProjectileData[MAXTILES];
 projectile_t SpriteProjectile[MAXSPRITES];
 
 int32_t g_noResetVars;
@@ -985,7 +985,7 @@ local BNOT = {
     CHAIN_MASK_EVENT = bit.bnot(actor.FLAGS._CHAIN_MASK_EVENT),
 }
 
-local projectile = defs_c.creategtab(ffiC.ProjectileData, ffiC.MAXTILES, "projectile[]")
+local projectile = defs_c.creategtab_membidx_ptr(ffiC.g_tile, "_proj", ffiC.MAXTILES, "projectile")
 local g_tile = setmtonce({}, defs_c.GenStructMetatable("g_tile", "MAXTILES", tile_static_members))
 
 --== Custom operations for BUILD data structures ==--
diff --git a/polymer/eduke32/source/lunatic/defs_common.lua b/polymer/eduke32/source/lunatic/defs_common.lua
index cf7405597..22774bc74 100644
--- a/polymer/eduke32/source/lunatic/defs_common.lua
+++ b/polymer/eduke32/source/lunatic/defs_common.lua
@@ -886,6 +886,30 @@ function creategtab_membidx(ctab, membname, maxidx, name)
     return setmtonce(tab, tmpmt)
 end
 
+-- Create a a safe indirection for an ffi.C struct array, accessing a given
+-- pointer member, which either points to one element, or is NULL.
+function creategtab_membidx_ptr(ctab, membname, maxidx, name)
+    local tab = {}
+    local tmpmt = {
+        __index = function(tab, key)
+            if (key>=0 and key < maxidx) then
+                local ptr = ctab[key][membname]
+                if (ptr ~= nil) then
+                    return ctab[key][membname][0]
+                end
+                return nil
+--                error(name .. '[' .. key .. '] is null', 2)
+            end
+            error('out-of-bounds '..name..'[] read access', 2)
+        end,
+        __newindex = function()
+            error('cannot write directly to '..name..'[]', 2)
+        end,
+    }
+
+    return setmtonce(tab, tmpmt)
+end
+
 -- Construct const struct from table
 function conststruct(tab)
     local strtab = { "struct {" }
diff --git a/polymer/eduke32/source/lunatic/dynsymlist b/polymer/eduke32/source/lunatic/dynsymlist
index 5ea8cda04..1a05ee2ba 100644
--- a/polymer/eduke32/source/lunatic/dynsymlist
+++ b/polymer/eduke32/source/lunatic/dynsymlist
@@ -167,7 +167,6 @@ g_playerWeapon;
 g_weaponOverridden;
 WeaponPickupSprites;
 g_tile;
-ProjectileData;
 SpriteProjectile;
 
 g_noResetVars;
diff --git a/polymer/eduke32/source/lunatic/test.lua b/polymer/eduke32/source/lunatic/test.lua
index d4cda8a47..77ccf33c7 100644
--- a/polymer/eduke32/source/lunatic/test.lua
+++ b/polymer/eduke32/source/lunatic/test.lua
@@ -395,10 +395,16 @@ gameevent
         pl.ammo_amount.SHRINKER = 2
 
         -- MORTER2 from test/weaponvars.con
-        player[0].weapon.SHOTGUN.shoots = 1653
-        local proj = projectile[1653]
-        proj.drop = 0
-        proj:set_trail(D.SMALLSMOKE)
+        local PNUM = 1653
+        local proj = projectile[PNUM]
+        if (proj ~= nil) then
+            printf('Have projectile %d', PNUM)
+            player[0].weapon.SHOTGUN.shoots = PNUM
+            proj.drop = 0
+            proj:set_trail(D.SMALLSMOKE)
+        else
+            printf('^10Do NOT have projectile %d, test/weaponvars.con not loaded?', PNUM)
+        end
 
         if (gv._LUNATIC_STRICT == 0) then
             t = gv.gethiticks()