DR-042 Phase C (legibility, part 2): walls block enemies (C5) — restore the fortress fantasy

Player-built structures now physically block enemies (husks walked straight through walls before). Dedicated
"Structure" physics layer (slot 9) so the player passes its own walls while enemies are stopped:

- New WorldCollisionConfig.StructureMask, baked from the "Structure" layer in WorldCollisionAuthoring (mirrors
  EnvironmentMask). EnemyAISystem ORs it into the movement sweep filter (CollidesWith = envMask | structMask) —
  no new system, same 1-2 SphereCasts per enemy.
- Wall/Turret/Pylon prefabs get a cell-sized BoxCollider on the Structure layer (Wall's existing one relayered).
- Physics matrix: Default x Structure unchecked, so the kinematic player CC (Default) passes its own walls while
  the enemy's explicit cast still hits them. Despawn frees collision for free (collider dies with the entity).

Play-verified baked filters: StructMask=512; structure colliders BelongsTo=512, CollidesWith=0xFFFFFFFE
(includes Environment for the enemy cast, EXCLUDES bit 0 so the player passes). 389/389 EditMode, no exceptions.
Server-only/static colliders -> deterministic, no client divergence. SaveData stays v5.

Phase C complete (C5-C7). A visual fun-gate (husk stops at wall, player walks through) is the operator's eyes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 21:26:56 -07:00
parent 419debad74
commit bd8458853b
8 changed files with 78 additions and 12 deletions
+23 -1
View File
@@ -102,7 +102,8 @@ GameObject:
- component: {fileID: 9053853372340598254} - component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220} - component: {fileID: 6834786618115927220}
- component: {fileID: 7685488391646220227} - component: {fileID: 7685488391646220227}
m_Layer: 0 - component: {fileID: 1225369404710843925}
m_Layer: 9
m_Name: Pylon m_Name: Pylon
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -177,3 +178,24 @@ MonoBehaviour:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.StructureAuthoring m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.StructureAuthoring
Kind: 6 Kind: 6
MaxHp: 150 MaxHp: 150
--- !u!65 &1225369404710843925
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1.6, y: 2, z: 1.6}
m_Center: {x: 0, y: 1, z: 0}
+23 -1
View File
@@ -378,7 +378,8 @@ GameObject:
- component: {fileID: 9053853372340598254} - component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220} - component: {fileID: 6834786618115927220}
- component: {fileID: 1794795016809289889} - component: {fileID: 1794795016809289889}
m_Layer: 0 - component: {fileID: 9049467567705961987}
m_Layer: 9
m_Name: Turret m_Name: Turret
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -455,6 +456,27 @@ MonoBehaviour:
CooldownTicks: 30 CooldownTicks: 30
Damage: 12 Damage: 12
MaxHp: 120 MaxHp: 120
--- !u!65 &9049467567705961987
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1.6, y: 2, z: 1.6}
m_Center: {x: 0, y: 1, z: 0}
--- !u!1 &4051895978514069616 --- !u!1 &4051895978514069616
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
+1 -1
View File
@@ -103,7 +103,7 @@ GameObject:
- component: {fileID: 6834786618115927220} - component: {fileID: 6834786618115927220}
- component: {fileID: 8793146551006314905} - component: {fileID: 8793146551006314905}
- component: {fileID: 7779358222264100756} - component: {fileID: 7779358222264100756}
m_Layer: 0 m_Layer: 9
m_Name: Wall m_Name: Wall
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -15,6 +15,9 @@ namespace ProjectM.Authoring
{ {
[Tooltip("Name of the Unity layer carrying the static world colliders (boundary ring + landmarks).")] [Tooltip("Name of the Unity layer carrying the static world colliders (boundary ring + landmarks).")]
public string EnvironmentLayerName = "Environment"; public string EnvironmentLayerName = "Environment";
[Tooltip("DR-042 C5: Unity layer carrying player-built structure colliders (Wall/Turret/Pylon) that block enemies.")]
public string StructureLayerName = "Structure";
private class WorldCollisionBaker : Baker<WorldCollisionAuthoring> private class WorldCollisionBaker : Baker<WorldCollisionAuthoring>
{ {
@@ -23,7 +26,9 @@ namespace ProjectM.Authoring
int layer = LayerMask.NameToLayer(authoring.EnvironmentLayerName); int layer = LayerMask.NameToLayer(authoring.EnvironmentLayerName);
uint mask = layer >= 0 ? 1u << layer : 0u; uint mask = layer >= 0 ? 1u << layer : 0u;
var entity = GetEntity(TransformUsageFlags.None); var entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new WorldCollisionConfig { EnvironmentMask = mask }); int structLayer = LayerMask.NameToLayer(authoring.StructureLayerName);
uint structMask = structLayer >= 0 ? 1u << structLayer : 0u;
AddComponent(entity, new WorldCollisionConfig { EnvironmentMask = mask, StructureMask = structMask });
} }
} }
} }
@@ -100,8 +100,9 @@ namespace ProjectM.Server
var ecb = new EntityCommandBuffer(Allocator.Temp); var ecb = new EntityCommandBuffer(Allocator.Temp);
bool havePhysics = SystemAPI.TryGetSingleton<PhysicsWorldSingleton>(out var physics); bool havePhysics = SystemAPI.TryGetSingleton<PhysicsWorldSingleton>(out var physics);
uint envMask = SystemAPI.TryGetSingleton<WorldCollisionConfig>(out var worldCol) ? worldCol.EnvironmentMask : 0u; uint envMask = SystemAPI.TryGetSingleton<WorldCollisionConfig>(out var worldCol) ? worldCol.EnvironmentMask : 0u;
var envFilter = new CollisionFilter { BelongsTo = ~0u, CollidesWith = envMask, GroupIndex = 0 }; uint sweepMask = envMask | worldCol.StructureMask; // DR-042 C5: also collide enemies against player-built walls
bool sweep = havePhysics && envMask != 0u; var envFilter = new CollisionFilter { BelongsTo = ~0u, CollidesWith = sweepMask, GroupIndex = 0 };
bool sweep = havePhysics && sweepMask != 0u;
const float SweepRadius = 0.5f; // collide-and-slide sphere radius for Husk movement const float SweepRadius = 0.5f; // collide-and-slide sphere radius for Husk movement
foreach (var (xform, stats, cooldown, knockback, windup, region) in foreach (var (xform, stats, cooldown, knockback, windup, region) in
@@ -11,7 +11,14 @@ namespace ProjectM.Simulation
/// </summary> /// </summary>
public struct WorldCollisionConfig : IComponentData public struct WorldCollisionConfig : IComponentData
{ {
/// <summary>BelongsTo bitmask of the Environment physics layer (<c>1u &lt;&lt; layerIndex</c>).</summary>
/// <summary>BelongsTo bitmask of the Environment physics layer (<c>1u &lt;&lt; layerIndex</c>).</summary> /// <summary>BelongsTo bitmask of the Environment physics layer (<c>1u &lt;&lt; layerIndex</c>).</summary>
public uint EnvironmentMask; public uint EnvironmentMask;
/// <summary>DR-042 C5: BelongsTo bitmask of the "Structure" physics layer (player-built Wall/Turret/Pylon
/// colliders). OR'd into the enemy-movement sweep filter so husks collide-and-slide against walls. 0 if the
/// layer is absent (feature inert). The layer matrix excludes Structure&times;the player layer so the player CC
/// passes through its own walls.</summary>
public uint StructureMask;
} }
} }
+14 -5
View File
@@ -3,10 +3,11 @@
--- !u!55 &1 --- !u!55 &1
PhysicsManager: PhysicsManager:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 13 serializedVersion: 23
m_Gravity: {x: 0, y: -9.81, z: 0} m_Gravity: {x: 0, y: -9.81, z: 0}
m_DefaultMaterial: {fileID: 0} m_DefaultMaterial: {fileID: 0}
m_BounceThreshold: 2 m_BounceThreshold: 2
m_DefaultMaxDepenetrationVelocity: 10
m_SleepThreshold: 0.005 m_SleepThreshold: 0.005
m_DefaultContactOffset: 0.01 m_DefaultContactOffset: 0.01
m_DefaultSolverIterations: 6 m_DefaultSolverIterations: 6
@@ -16,11 +17,11 @@ PhysicsManager:
m_EnableAdaptiveForce: 0 m_EnableAdaptiveForce: 0
m_ClothInterCollisionDistance: 0.1 m_ClothInterCollisionDistance: 0.1
m_ClothInterCollisionStiffness: 0.2 m_ClothInterCollisionStiffness: 0.2
m_ContactsGeneration: 1 m_LayerCollisionMatrix: fffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff m_SimulationMode: 0
m_AutoSimulation: 1
m_AutoSyncTransforms: 0 m_AutoSyncTransforms: 0
m_ReuseCollisionCallbacks: 1 m_ReuseCollisionCallbacks: 1
m_InvokeCollisionCallbacks: 1
m_ClothInterCollisionSettingsToggle: 0 m_ClothInterCollisionSettingsToggle: 0
m_ClothGravity: {x: 0, y: -9.81, z: 0} m_ClothGravity: {x: 0, y: -9.81, z: 0}
m_ContactPairsMode: 0 m_ContactPairsMode: 0
@@ -31,6 +32,14 @@ PhysicsManager:
m_WorldSubdivisions: 8 m_WorldSubdivisions: 8
m_FrictionType: 0 m_FrictionType: 0
m_EnableEnhancedDeterminism: 0 m_EnableEnhancedDeterminism: 0
m_EnableUnifiedHeightmaps: 1 m_ImprovedPatchFriction: 0
m_GenerateOnTriggerStayEvents: 1
m_SolverType: 0 m_SolverType: 0
m_DefaultMaxAngularSpeed: 50 m_DefaultMaxAngularSpeed: 50
m_ScratchBufferChunkCount: 4
m_CurrentBackendId: 4072204805
m_FastMotionThreshold: 3.4028235e+38
m_SceneBuffersReleaseInterval: 0
m_ReleaseSceneBuffers: 0
m_LogVerbosity: 3
m_IncrementalStaticBroadphase: 1
+1 -1
View File
@@ -14,7 +14,7 @@ TagManager:
- -
- -
- Environment - Environment
- - Structure
- -
- -
- -