Hole Game

v0.7-meshclip

Real 3D void — shader cutout + physics walls · The hole is a real 3D void now, not a flat disc. A URP shader carves the ground and streets where the hole opens; a 5.4 m cylindrical wall of contact-modified box colliders catches absorbables once they descend below ground level; the ground collider itself is a dynamically rebuilt mesh with the hole subtracted from it. Things fall under real Unity gravity, tumble with their actual center of mass, and remain whole rigid bodies as they go down — no scripted lerp, no clip-on-the-cube.

Build notes

Shader cutout (`HoleClippable.shader`)

  • Custom URP HLSL shader with ForwardLit + ShadowCaster + DepthOnly passes. Each pass discards a fragment when its world-space XZ distance from _HoleCenter is less than _HoleRadius AND its world-space Y is below _HoleCenter.y.
  • Globals _HoleCenter (Vector4) and _HoleRadius (float) are pushed each LateUpdate by HoleClipFeeder from the player's hole transform plus a 0.04 Y offset (so the cut plane is just above ground level).
  • Applied to world geometry only — green ground plane and asphalt streets. Absorbables (buildings, cars, props) are *not* clippable. They stay as solid geometry and remain visible while they fall through the cutout.
  • Defensive: AbsorbableObject.Awake scans its child renderers and swaps any clippable material back to URP/Lit, preserving _BaseColor / _BaseMap / scale / offset. Even if the scene builder accidentally tags an absorbable's child, runtime fixes it.
  • Single-hole only (player). Bots' holes don't get the visual treatment yet — multi-hole arrays are deferred.

Cylindrical wall (`HoleWallCollider.cs`)

  • Each hole gets a ring of 18–72 BoxCollider segments arranged in a cylinder of radius = hole radius, extending from Y=-0.03 down to Y=-5.43 (5.4 m deep). Segment count scales with circumference so segments stay ~0.42 m wide.
  • Each segment opts in to hasModifiableContacts = true. A static Physics.ContactModifyEvent callback iterates contact points each frame and ignores any contact whose Y is above -0.12 (configurable contactActivationDepth).
  • Net effect: the wall is invisible to objects approaching from above ground level — they slide cleanly into the hole — but solid below ~12 cm depth, so once a cube is in, it can't escape sideways. No frame-quantized pop, no spawn/despawn cost.
  • Walls deactivate on Defeat() (so the player isn't trapped under their own dead hole's walls) and reactivate on respawn.
  • The hole's Rigidbody collision detection is ContinuousSpeculative so fast-moving holes don't tunnel through the wall mesh during a contact-modify rebuild.

Dynamic ground mesh (`GroundTileManager.cs`)

  • Replaced the previous 1,764-tile box-collider toggle approach. Now a single MeshCollider whose sharedMesh is rebuilt at runtime with the hole geometrically subtracted.
  • Algorithm: divide a 60×60 ground into 0.5 m cells. Each FixedUpdate, scan all cells; for each, test whether its AABB intersects any hole's circle (closest-point-to-center distance squared vs radius squared). Cells that intersect are skipped; the rest are emitted as quads.
  • Rebuild only fires when any hole has moved more than 8 cm or its radius has changed by 3 cm — keeps cost amortized over multiple frames.
  • After each rebuild, OverlapSphereNonAlloc wakes any sleeping rigidbodies within hole radius + 1.4 m. Without this, cubes that came to rest under gravity would stay asleep when the cutout passed under them — fixes the 'drive past, nothing happens' bug.

Real physics on absorbables

  • AbsorbableObject reverted to pure Unity gravity. No custom gravityAccel, no scripted lerp.
  • Rigidbody config: useGravity = true, interpolation = Interpolate, collisionDetectionMode = ContinuousDynamic (handles fast falls without tunneling), sleepThreshold = 0.005 (won't sleep over the slightest jitter).
  • centerOfMass = box.center so tall buildings tip naturally over the hole rim instead of pivoting at the transform origin.
  • Mass scales by tier: cone 0.5 → tiny 1.5 → small 3 → car 8 → building 30. Bigger objects fall under the same g but feel weightier in collisions.
  • Consume threshold pushed to topY < -4.5 (was 1.5) so cubes vanish near the bottom of the wall cylinder, not at ground level. You see them descend into the void.

Visible void floor

  • The HoleVisual disc is no longer a thin overlay at Y=0.04. It's parented to the hole actor at localY = -4.8 — deep at the bottom of the wall cylinder.
  • Looking down through the shader cutout, you see the dark disc as the floor of the void. The hole has perceptible depth.
  • Disc keeps its 0.88 Z-axis squash so it reads elliptical from the camera (consistent with prior versions).

Known limitations

  • Cubes occasionally refuse to fall when the hole passes underneath at high speed. Likely a wake-up timing issue — the body is awake, but the mesh rebuild hasn't subtracted the cell yet by the time gravity tries to pull it through. Defer to next release.
  • Bots don't get the shader cutout. Their holes still look like a flat colored disc on the ground.