Debugging utility for the game that answers what the player is standing on and nearby invisible colliders. Provides two console commands nz_under and nz_unclip. nz_under traces below the player, inspects controller ground, body sweep, renderer state, same-model 'twins', and finds nearby non-drawing colliders; nz_unclip removes named invisible (non-drawing) objects from the running scene.
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NZombies;
/// <summary>
/// `nz_under [radius]` — WHAT YOU ARE STANDING ON, AND WHAT ELSE LIKE IT IS AROUND YOU (2026-10-05). The user, on Live Wire:
/// *"what surface am i currently on? its invisible for me, and theres one just like it a bit to the side"*.
///
/// ⚠️ INVISIBLE AND SOLID IS USUALLY A CLIP BRUSH. The BSP importer keeps a Source map's clip brushes (player clips) as hidden
/// meshes with mesh collision, under "Clip Brushes" — 419 of them on Live Wire. Straight down from your feet this names the
/// object, its path and components, whether anything draws it, and the surface; then a grid of downward traces around you
/// lists every other surface that collides but draws nothing, nearest first, with its size and where it is from you.
/// </summary>
public static class UnderFoot
{
[ConCmd( "nz_under" )]
public static void Under( float radius = 400f )
{
var scene = Game.ActiveScene;
var bodies = PlayerSpawner.AllBodies();
var me = bodies.FirstOrDefault( PlayerPresence.Mine ) ?? bodies.FirstOrDefault();
if ( !scene.IsValid() || !me.IsValid() )
{
Log.Warning( "[nz-under] no player — in play?" );
return;
}
var feet = me.WorldPosition;
var ctrl = me.Components.Get<PlayerController>( FindMode.EverythingInSelfAndDescendants );
var facing = Rotation.FromYaw( ctrl.IsValid() ? ctrl.EyeAngles.yaw : me.WorldRotation.Yaw() );
// ⛔ THE CONTROLLER'S OWN ANSWER FIRST, THEN YOUR BODY'S BOX. A ray from your centre misses what you rest on whenever
// it is under the edge of your box, or only collides with players: on Live Wire it hit a floor 32 u BELOW the feet
// while the user stood on something invisible (2026-10-05). `TraceBody` sweeps the body's own box with the same
// collision rules the movement uses.
if ( ctrl.IsValid() )
{
var g = ctrl.GroundObject;
Log.Info( $"[nz-under] the controller says you stand on: {(g.IsValid() ? $"'{PathOf( g )}'" : "NOTHING (in the air)")}"
+ (g.IsValid() ? $" · {(Draws( g ) ? "DRAWN" : "draws NOTHING — invisible")} · collider {ctrl.GroundComponent?.GetType().Name ?? "-"}"
+ $" · surface '{ctrl.GroundSurface?.ResourceName ?? "-"}' · tags [{string.Join( ",", g.Tags.TryGetAll() )}]" : "") );
if ( g.IsValid() ) Log.Info( $"[nz-under] components: {Describe( g )}" );
if ( ctrl.GroundComponent is Collider gc && gc.IsValid() )
{
var bb = gc.GetWorldBounds();
Log.Info( $"[nz-under] its bounds {Fmt( bb.Mins )}..{Fmt( bb.Maxs )} · {bb.Size.x:0} × {bb.Size.y:0} × {bb.Size.z:0} u"
+ $" · its top is {bb.Maxs.z - feet.z:+0;-0} u from your feet" );
}
var body = ctrl.TraceBody( feet + Vector3.Up * 4f, feet + Vector3.Down * 64f, 1f, 1f );
Log.Info( body.Hit && body.GameObject.IsValid()
? $"[nz-under] your body's box comes to rest on '{PathOf( body.GameObject )}' at {Fmt( body.HitPosition )}"
+ $" ({MathF.Max( 0f, body.Distance - 4f ):0} u down) · {(Draws( body.GameObject ) ? "DRAWN" : "draws NOTHING — invisible")}"
+ $" · surface '{body.Surface?.ResourceName ?? "-"}'"
: "[nz-under] your body's box finds nothing within 64 u below" );
}
var tr = scene.Trace.Ray( feet + Vector3.Up * 24f, feet + Vector3.Down * 256f ).IgnoreGameObjectHierarchy( me ).Run();
if ( !tr.Hit || !tr.GameObject.IsValid() )
{
Log.Info( $"[nz-under] nothing within 256 u straight below your centre at {Fmt( feet )}" );
return;
}
Log.Info( $"[nz-under] straight below your centre: '{PathOf( tr.GameObject )}' at {Fmt( tr.HitPosition )}"
+ $" ({tr.Distance - 24f:0} u below your feet) · {(Draws( tr.GameObject ) ? "DRAWN" : "draws NOTHING — invisible")}" );
Log.Info( $"[nz-under] components: {Describe( tr.GameObject )} · surface '{tr.Surface?.ResourceName ?? "-"}'"
+ $" · tags [{string.Join( ",", tr.GameObject.Tags.TryGetAll() )}]" );
Renderer( tr.GameObject );
// ⚠️ ITS TWINS TOO: "one just like it a bit to the side" is the same model on another object, which the hidden-only
// sweep below cannot see when its renderer claims to draw (the Live Wire turbines, 2026-10-05).
if ( tr.GameObject.Components.Get<ModelRenderer>( FindMode.EverythingInSelf )?.Model is Model model )
{
var twins = scene.GetAllComponents<ModelRenderer>()
.Where( r => r.IsValid() && r.Model == model && r.GameObject != tr.GameObject
&& r.WorldPosition.Distance( feet ) < 2000f )
.OrderBy( r => r.WorldPosition.Distance( feet ) ).Take( 4 ).ToList();
Log.Info( $"[nz-under] the same model elsewhere within 2000 u: {twins.Count}" );
foreach ( var t in twins )
{
Log.Info( $"[nz-under] twin '{PathOf( t.GameObject )}' at {Fmt( t.WorldPosition )}" );
Renderer( t.GameObject );
}
}
// Others like it: every collider around you that draws nothing, from a PHYSICS OVERLAP, with its exact bounds.
// ⛔ NOT RAYS ON A GRID, the first cut: 24 u apart, they stepped right over Clip Brush 83, an 8 u-thick wall (2026-10-05).
bool Mine( GameObject o )
{
for ( var g = o; g.IsValid(); g = g.Parent ) if ( g == me ) return true;
return false;
}
var area = BBox.FromPositionAndSize( feet + Vector3.Up * 32f, new Vector3( radius * 2f, radius * 2f, 320f ) );
var near = new List<(GameObject Go, BBox B, float D)>();
foreach ( var o in scene.FindInPhysics( area ).Distinct() )
{
if ( !o.IsValid() || Mine( o ) || Draws( o ) ) continue;
var col = o.Components.Get<Collider>( FindMode.EverythingInSelf );
if ( !col.IsValid() ) continue;
var b = col.GetWorldBounds();
near.Add( (o, b, DistanceTo( b, feet )) );
}
Log.Info( $"[nz-under] colliders within {radius:0} u that draw nothing: {near.Count}" + (near.Count > 12 ? " (nearest 12)" : "") );
foreach ( var (go, b, d) in near.OrderBy( x => x.D ).Take( 12 ) )
{
var off = b.Center - feet;
var ahead = Vector3.Dot( off, facing.Forward );
var right = Vector3.Dot( off, facing.Right );
Log.Info( $"[nz-under] '{go.Name}'{(ctrl.IsValid() && go == ctrl.GroundObject ? " ← you are on this" : "")}"
+ $" · {b.Size.x:0} × {b.Size.y:0} × {b.Size.z:0} u, top at z {b.Maxs.z:0} ({MathF.Round( b.Maxs.z - feet.z ):+0;-0;0} u from your feet)"
+ $" · nearest point {d:0} u away · centre {MathF.Abs( ahead ):0} u {(ahead >= 0 ? "ahead" : "behind")},"
+ $" {MathF.Abs( right ):0} u to your {(right >= 0 ? "right" : "left")} · under '{go.Parent?.Name ?? "-"}'" );
}
}
/// <summary>How far a point is from the nearest point of a box (0 inside it).</summary>
static float DistanceTo( BBox b, Vector3 p )
=> new Vector3( Math.Clamp( p.x, b.Mins.x, b.Maxs.x ), Math.Clamp( p.y, b.Mins.y, b.Maxs.y ),
Math.Clamp( p.z, b.Mins.z, b.Maxs.z ) ).Distance( p );
/// <summary>
/// `nz_unclip "Clip Brush 83"` — take a named invisible clip out of the RUNNING map, so a removal made in the map scene can
/// be felt at once instead of after a reload (2026-10-05, Live Wire). LIVE ONLY: the map scene file is what keeps it out.
/// Refuses anything that draws, so a typo cannot delete a visible piece of the map.
/// </summary>
[ConCmd( "nz_unclip" )]
public static void Unclip( string name = "" )
{
var scene = Game.ActiveScene;
if ( !scene.IsValid() || string.IsNullOrWhiteSpace( name ) )
{
Log.Warning( "[nz-under] nz_unclip \"<object name>\" — e.g. \"Clip Brush 83\" (nz_under names them)" );
return;
}
var hits = scene.GetAllObjects( true ).Where( o => o.IsValid() && o.Name == name ).ToList();
if ( hits.Count == 0 )
{
Log.Warning( $"[nz-under] nothing named '{name}' in the running scene" );
return;
}
foreach ( var o in hits )
{
if ( Draws( o ) )
{
Log.Warning( $"[nz-under] '{PathOf( o )}' draws something — not removing a visible object" );
continue;
}
Log.Info( $"[nz-under] removed '{PathOf( o )}' from the running map (live only — the map scene keeps it out for good)" );
o.Destroy();
}
}
/// <summary>
/// What this object's model renderer is doing right now: its model (error? how many meshes? bounds), its scene object
/// (does it exist, is rendering on, its bounds and tint), and its materials — the questions behind "it collides but I
/// cannot see it".
/// </summary>
static void Renderer( GameObject go )
{
var r = go.Components.Get<ModelRenderer>( FindMode.EverythingInSelf );
if ( !r.IsValid() )
{
Log.Info( "[nz-under] no ModelRenderer on it" );
return;
}
var m = r.Model;
var so = r.SceneObject;
Log.Info( $"[nz-under] renderer enabled {r.Enabled} · type {r.RenderType} · tint {r.Tint} · object scale {go.WorldScale}"
+ $" · tags [{string.Join( ",", go.Tags.TryGetAll() )}]" );
Log.Info( $"[nz-under] model '{m?.ResourcePath ?? "none"}' · error {m?.IsError} · meshes {m?.MeshCount}"
+ $" · bounds {(m is null ? "-" : $"{Fmt( m.Bounds.Mins )}..{Fmt( m.Bounds.Maxs )}")}"
+ $" · render bounds {(m is null ? "-" : $"{Fmt( m.RenderBounds.Mins )}..{Fmt( m.RenderBounds.Maxs )}")}" );
Log.Info( $"[nz-under] scene object {(so.IsValid() ? $"rendering {so.RenderingEnabled} · bounds {Fmt( so.Bounds.Mins )}..{Fmt( so.Bounds.Maxs )} · tint {so.ColorTint}" : "NONE")}"
+ $" · materials [{(m is null ? "" : string.Join( ", ", m.Materials.Select( x => x?.ResourcePath ?? "null" ) ))}]"
+ $" · override '{r.MaterialOverride?.ResourcePath ?? "-"}'" );
}
/// <summary>Does anything on this object draw? A mesh not hidden in game, or a model renderer that is not shadow-only.</summary>
static bool Draws( GameObject go )
{
foreach ( var c in go.Components.GetAll() )
{
if ( c is MeshComponent m && m.Enabled && !m.HideInGame ) return true;
if ( c is ModelRenderer r && r.Enabled && r.Model is not null
&& r.RenderType != ModelRenderer.ShadowRenderType.ShadowsOnly ) return true;
}
return false;
}
static string Describe( GameObject go )
=> string.Join( ", ", go.Components.GetAll().Select( c => c is MeshComponent m
? $"MeshComponent (hidden in game: {m.HideInGame}, collision {m.Collision})"
: c.GetType().Name ) );
static string PathOf( GameObject go )
{
var names = new List<string>();
for ( var g = go; g.IsValid() && g is not Scene; g = g.Parent ) names.Insert( 0, g.Name );
return string.Join( " / ", names );
}
static Vector3 Centre( (float MinX, float MaxX, float MinY, float MaxY, float Top, int Hits) e )
=> new( (e.MinX + e.MaxX) * 0.5f, (e.MinY + e.MaxY) * 0.5f, e.Top );
static string Fmt( Vector3 v ) => $"({v.x:0}, {v.y:0}, {v.z:0})";
}