Hygiene B3: single-sourcing & magic-number consolidation
- StructureCatalogAuthoring: WallCostOre -> WallCostBiomass (it bakes a Biomass cost; [FormerlySerializedAs] preserves the scene value). - Harvester/Fabricator authoring: resource-id byte defaults reference ResourceId.Ore/.Charge instead of magic 2/4. - RegionMath.RegionBoundaryX (= ExpeditionOffsetX*0.5) single-sources the region-flip X used by HudSystem + OnboardingSystem (was 500f in 3 places). - CharacterComponent.DefaultGroundedSharpness single-sources the CC sharpness 15f (GetDefault, DashSystem, PlayerDeathStateSystem, PlayerCharacterAuthoring). - InventorySlot [InternalBufferCapacity] references Tuning.InventoryMaxSlots. - ConnectionMode enum -> byte-const class (project convention; removes the latent enum-in-Burst trap); field + one Seed() param become byte. - Tuning.ChargerWindupTicks single-sources the Charger telegraph windup (EnemyBaker + TuningConfig.Defaults; was a bare 30 that could drift). - (TicksPerSecond deliberately NOT added: no seconds->ticks conversion site exists; the tick-count fields are per-authoring designer tunables, so a const would be unreferenced.) 451/451 EditMode tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,10 +15,10 @@ namespace ProjectM.Authoring
|
|||||||
public class FabricatorAuthoring : MonoBehaviour
|
public class FabricatorAuthoring : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Tooltip("Input resource id consumed per run (1=Aether, 2=Ore, 3=Biomass).")]
|
[Tooltip("Input resource id consumed per run (1=Aether, 2=Ore, 3=Biomass).")]
|
||||||
public byte InResourceId = 2; // Ore
|
public byte InResourceId = ResourceId.Ore;
|
||||||
[Min(1)] public int InAmount = 1;
|
[Min(1)] public int InAmount = 1;
|
||||||
[Tooltip("Output resource id deposited to the global ledger (4 = EB-2 Charge / turret ammo).")]
|
[Tooltip("Output resource id deposited to the global ledger (4 = EB-2 Charge / turret ammo).")]
|
||||||
public byte OutResourceId = 4; // Charge
|
public byte OutResourceId = ResourceId.Charge;
|
||||||
[Min(1)] public int OutAmount = 3;
|
[Min(1)] public int OutAmount = 3;
|
||||||
[Min(1)] public int PeriodTicks = 30;
|
[Min(1)] public int PeriodTicks = 30;
|
||||||
[Tooltip("EB-2: 1 = ledger-fed (consume the input from the shared ledger; no conveyor). 0 = M7 MachineInput chain.")]
|
[Tooltip("EB-2: 1 = ledger-fed (consume the input from the shared ledger; no conveyor). 0 = M7 MachineInput chain.")]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace ProjectM.Authoring
|
|||||||
public class HarvesterAuthoring : MonoBehaviour
|
public class HarvesterAuthoring : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Tooltip("Resource id this generator produces (1=Aether, 2=Ore, 3=Biomass).")]
|
[Tooltip("Resource id this generator produces (1=Aether, 2=Ore, 3=Biomass).")]
|
||||||
public byte OutputResourceId = 2; // Ore
|
public byte OutputResourceId = ResourceId.Ore;
|
||||||
[Min(1)] public int Yield = 1;
|
[Min(1)] public int Yield = 1;
|
||||||
[Min(1)] public int PeriodTicks = 60;
|
[Min(1)] public int PeriodTicks = 60;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using ProjectM.Simulation;
|
using ProjectM.Simulation;
|
||||||
using Unity.Entities;
|
using Unity.Entities;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Serialization;
|
||||||
|
|
||||||
namespace ProjectM.Authoring
|
namespace ProjectM.Authoring
|
||||||
{
|
{
|
||||||
@@ -23,8 +24,8 @@ namespace ProjectM.Authoring
|
|||||||
[Tooltip("Wall structure ghost prefab (StructureAuthoring{Wall} + GhostAuthoring).")]
|
[Tooltip("Wall structure ghost prefab (StructureAuthoring{Wall} + GhostAuthoring).")]
|
||||||
public GameObject WallPrefab;
|
public GameObject WallPrefab;
|
||||||
|
|
||||||
[Tooltip("Ore cost to build a wall.")]
|
[Tooltip("Biomass cost to build a wall.")]
|
||||||
[Min(0)] public int WallCostOre = 4;
|
[Min(0)] [FormerlySerializedAs("WallCostOre")] public int WallCostBiomass = 4;
|
||||||
|
|
||||||
[Tooltip("Pylon cosmetic-beacon ghost prefab (StructureAuthoring{Pylon} + GhostAuthoring).")]
|
[Tooltip("Pylon cosmetic-beacon ghost prefab (StructureAuthoring{Pylon} + GhostAuthoring).")]
|
||||||
public GameObject PylonPrefab;
|
public GameObject PylonPrefab;
|
||||||
@@ -69,7 +70,7 @@ namespace ProjectM.Authoring
|
|||||||
Type = StructureType.Wall,
|
Type = StructureType.Wall,
|
||||||
Prefab = GetEntity(authoring.WallPrefab, TransformUsageFlags.Dynamic),
|
Prefab = GetEntity(authoring.WallPrefab, TransformUsageFlags.Dynamic),
|
||||||
CostResourceId = ResourceId.Biomass, // DR-042 C6b: walls cost Biomass (the dead currency's only sink)
|
CostResourceId = ResourceId.Biomass, // DR-042 C6b: walls cost Biomass (the dead currency's only sink)
|
||||||
CostAmount = authoring.WallCostOre,
|
CostAmount = authoring.WallCostBiomass,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ namespace ProjectM.Authoring
|
|||||||
// {ChargerAuthoring(LungeState), SpitterAuthoring(SpitterState)} — both would match ZERO AI passes.
|
// {ChargerAuthoring(LungeState), SpitterAuthoring(SpitterState)} — both would match ZERO AI passes.
|
||||||
if (GetComponent<ChargerAuthoring>() != null && spitter != null)
|
if (GetComponent<ChargerAuthoring>() != null && spitter != null)
|
||||||
Debug.LogError($"Enemy '{authoring.name}' has BOTH ChargerAuthoring and SpitterAuthoring; it would match no AI pass and never move. Remove one.", authoring);
|
Debug.LogError($"Enemy '{authoring.name}' has BOTH ChargerAuthoring and SpitterAuthoring; it would match no AI pass and never move. Remove one.", authoring);
|
||||||
if (GetComponent<ChargerAuthoring>() != null) { kind = ZoneEnemyMath.KindCharger; windup = 30; }
|
if (GetComponent<ChargerAuthoring>() != null) { kind = ZoneEnemyMath.KindCharger; windup = (byte)Tuning.ChargerWindupTicks; }
|
||||||
else if (spitter != null) { kind = ZoneEnemyMath.KindSpitter; windup = (byte)Mathf.Clamp(spitter.WindupTicks, 1, 255); }
|
else if (spitter != null) { kind = ZoneEnemyMath.KindSpitter; windup = (byte)Mathf.Clamp(spitter.WindupTicks, 1, 255); }
|
||||||
else if (GetComponent<SwarmerAuthoring>() != null) { kind = ZoneEnemyMath.KindSwarmer; windup = (byte)Tuning.AttackWindupTicks; /* B4: match the server windup (grunt-path swarmers use GruntWindupTicks); baked 6 was a snap-ramp lie */ }
|
else if (GetComponent<SwarmerAuthoring>() != null) { kind = ZoneEnemyMath.KindSwarmer; windup = (byte)Tuning.AttackWindupTicks; /* B4: match the server windup (grunt-path swarmers use GruntWindupTicks); baked 6 was a snap-ramp lie */ }
|
||||||
AddComponent(entity, new EnemyTelegraph { WindupTicks = windup, Kind = kind });
|
AddComponent(entity, new EnemyTelegraph { WindupTicks = windup, Kind = kind });
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace ProjectM.Authoring
|
|||||||
public class PlayerCharacterAuthoring : MonoBehaviour
|
public class PlayerCharacterAuthoring : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Tooltip("Sharpness of ground velocity smoothing (higher = snappier).")]
|
[Tooltip("Sharpness of ground velocity smoothing (higher = snappier).")]
|
||||||
public float GroundedMovementSharpness = 15f;
|
public float GroundedMovementSharpness = CharacterComponent.DefaultGroundedSharpness;
|
||||||
|
|
||||||
public AuthoringKinematicCharacterProperties CharacterProperties = AuthoringKinematicCharacterProperties.GetDefault();
|
public AuthoringKinematicCharacterProperties CharacterProperties = AuthoringKinematicCharacterProperties.GetDefault();
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace ProjectM.Client
|
|||||||
[UpdateInGroup(typeof(PresentationSystemGroup))]
|
[UpdateInGroup(typeof(PresentationSystemGroup))]
|
||||||
public partial class OnboardingSystem : SystemBase
|
public partial class OnboardingSystem : SystemBase
|
||||||
{
|
{
|
||||||
const float ExpeditionRegionXMin = 500f; // player x past this = the +1000 expedition region (mirrors HudSystem)
|
const float ExpeditionRegionXMin = RegionMath.RegionBoundaryX; // player x past this = the +1000 expedition region (mirrors HudSystem)
|
||||||
|
|
||||||
GameObject _go;
|
GameObject _go;
|
||||||
UIDocument _doc;
|
UIDocument _doc;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace ProjectM.Client
|
|||||||
static readonly Color SlotSelBg = new(0.16f, 0.26f, 0.32f, 0.95f);
|
static readonly Color SlotSelBg = new(0.16f, 0.26f, 0.32f, 0.95f);
|
||||||
static readonly Color SlotIdleBorder = new(1f, 1f, 1f, 0.08f);
|
static readonly Color SlotIdleBorder = new(1f, 1f, 1f, 0.08f);
|
||||||
const int MaxPips = 12;
|
const int MaxPips = 12;
|
||||||
const float ExpeditionRegionXMin = 500f; // camera x past this = the +1000 expedition region (DR-013)
|
const float ExpeditionRegionXMin = RegionMath.RegionBoundaryX; // camera x past this = the +1000 expedition region (DR-013)
|
||||||
|
|
||||||
GameObject _hudGo;
|
GameObject _hudGo;
|
||||||
UIDocument _doc;
|
UIDocument _doc;
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ namespace ProjectM.Client
|
|||||||
Busy = false;
|
Busy = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Seed(World world, ConnectionMode mode, string address, ushort port)
|
static void Seed(World world, byte mode, string address, ushort port)
|
||||||
{
|
{
|
||||||
if (world is not { IsCreated: true }) return;
|
if (world is not { IsCreated: true }) return;
|
||||||
var em = world.EntityManager;
|
var em = world.EntityManager;
|
||||||
|
|||||||
@@ -11,17 +11,19 @@ namespace ProjectM.Simulation
|
|||||||
/// <c>NetworkStreamRequestConnect</c> and clears <see cref="Requested"/>. Direct IP/LAN only for now —
|
/// <c>NetworkStreamRequestConnect</c> and clears <see cref="Requested"/>. Direct IP/LAN only for now —
|
||||||
/// Unity Relay is deferred to a later pass. Created per-world as a singleton.
|
/// Unity Relay is deferred to a later pass. Created per-world as a singleton.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ConnectionMode : byte
|
/// <summary>Connection intent values (byte-const, matching the project's op/id convention — not an enum, so
|
||||||
|
/// they stay safe if a consumer is ever [BurstCompile]d). Written to <see cref="ConnectionConfig.Mode"/>.</summary>
|
||||||
|
public static class ConnectionMode
|
||||||
{
|
{
|
||||||
None,
|
public const byte None = 0;
|
||||||
Host,
|
public const byte Host = 1;
|
||||||
Join,
|
public const byte Join = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ConnectionConfig : IComponentData
|
public struct ConnectionConfig : IComponentData
|
||||||
{
|
{
|
||||||
/// <summary>What to do with this world's network stream.</summary>
|
/// <summary>What to do with this world's network stream.</summary>
|
||||||
public ConnectionMode Mode;
|
public byte Mode;
|
||||||
|
|
||||||
/// <summary>Dotted IPv4 to connect to (Join). Ignored for Host (binds AnyIpv4).</summary>
|
/// <summary>Dotted IPv4 to connect to (Join). Ignored for Host (binds AnyIpv4).</summary>
|
||||||
public FixedString64Bytes Address;
|
public FixedString64Bytes Address;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ namespace ProjectM.Simulation
|
|||||||
RecoverTailTicks = 9f,
|
RecoverTailTicks = 9f,
|
||||||
DashCooldownTicks = 36f, // tune: was 45 (0.75s) -> 0.60s, snappier horde-kiter cadence
|
DashCooldownTicks = 36f, // tune: was 45 (0.75s) -> 0.60s, snappier horde-kiter cadence
|
||||||
DashSharpness = 200f,
|
DashSharpness = 200f,
|
||||||
ChargerWindupTicks = 30f,
|
ChargerWindupTicks = Tuning.ChargerWindupTicks,
|
||||||
ChargerLungeSpeed = 16f,
|
ChargerLungeSpeed = 16f,
|
||||||
ChargerLungeDurationTicks = 18f,
|
ChargerLungeDurationTicks = 18f,
|
||||||
ChargerWhiffStaggerTicks = 36f,
|
ChargerWhiffStaggerTicks = 36f,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace ProjectM.Simulation
|
|||||||
/// subscene MUST be re-baked (consistently in both worlds) or the connect handshake desyncs.
|
/// subscene MUST be re-baked (consistently in both worlds) or the connect handshake desyncs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GhostComponent(OwnerSendType = SendToOwnerType.All)]
|
[GhostComponent(OwnerSendType = SendToOwnerType.All)]
|
||||||
[InternalBufferCapacity(24)]
|
[InternalBufferCapacity(Tuning.InventoryMaxSlots)]
|
||||||
public struct InventorySlot : IBufferElementData
|
public struct InventorySlot : IBufferElementData
|
||||||
{
|
{
|
||||||
/// <summary>Item carried in this slot (0 = empty/unused; aligns with InventoryMath's 0-id no-op).</summary>
|
/// <summary>Item carried in this slot (0 = empty/unused; aligns with InventoryMath's 0-id no-op).</summary>
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ namespace ProjectM.Simulation
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public struct CharacterComponent : IComponentData
|
public struct CharacterComponent : IComponentData
|
||||||
{
|
{
|
||||||
|
/// <summary>The CC's default grounded-movement smoothing sharpness — the single source for GetDefault, the
|
||||||
|
/// authoring default, DashSystem's base, and the death-state reset.</summary>
|
||||||
|
public const float DefaultGroundedSharpness = 15f;
|
||||||
|
|
||||||
/// <summary>How quickly RelativeVelocity is lerped toward the target velocity on the ground.</summary>
|
/// <summary>How quickly RelativeVelocity is lerped toward the target velocity on the ground.</summary>
|
||||||
public float GroundedMovementSharpness;
|
public float GroundedMovementSharpness;
|
||||||
|
|
||||||
@@ -26,7 +30,7 @@ namespace ProjectM.Simulation
|
|||||||
{
|
{
|
||||||
return new CharacterComponent
|
return new CharacterComponent
|
||||||
{
|
{
|
||||||
GroundedMovementSharpness = 15f,
|
GroundedMovementSharpness = DefaultGroundedSharpness,
|
||||||
StepAndSlopeHandling = BasicStepAndSlopeHandlingParameters.GetDefault(),
|
StepAndSlopeHandling = BasicStepAndSlopeHandlingParameters.GetDefault(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace ProjectM.Simulation
|
|||||||
// Feel knobs are LIVE-tunable via the TuningConfig singleton (MC-0): OnUpdate reads it each tick and falls
|
// Feel knobs are LIVE-tunable via the TuningConfig singleton (MC-0): OnUpdate reads it each tick and falls
|
||||||
// back to TuningConfig.Defaults() when absent (release builds / EditMode), so behaviour is identical to the
|
// back to TuningConfig.Defaults() when absent (release builds / EditMode), so behaviour is identical to the
|
||||||
// old baked consts. DefaultSharpness (the restore target) + SimTickRate stay compile-time (not tuned).
|
// old baked consts. DefaultSharpness (the restore target) + SimTickRate stay compile-time (not tuned).
|
||||||
const float DefaultSharpness = 15f; // CharacterComponent.GetDefault() base
|
const float DefaultSharpness = CharacterComponent.DefaultGroundedSharpness; // CharacterComponent.GetDefault() base
|
||||||
const float SimTickRate = 60f;
|
const float SimTickRate = 60f;
|
||||||
|
|
||||||
[BurstCompile]
|
[BurstCompile]
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace ProjectM.Simulation
|
|||||||
if (SystemAPI.HasComponent<CharacterComponent>(entity))
|
if (SystemAPI.HasComponent<CharacterComponent>(entity))
|
||||||
{
|
{
|
||||||
var cc = SystemAPI.GetComponent<CharacterComponent>(entity);
|
var cc = SystemAPI.GetComponent<CharacterComponent>(entity);
|
||||||
cc.GroundedMovementSharpness = 15f;
|
cc.GroundedMovementSharpness = CharacterComponent.DefaultGroundedSharpness;
|
||||||
SystemAPI.SetComponent(entity, cc);
|
SystemAPI.SetComponent(entity, cc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ namespace ProjectM.Simulation
|
|||||||
/// under interp lag (>= ~250ms reaction + interp buffer; Slice 1 readability). 0/1 = near-instant (legacy).</summary>
|
/// under interp lag (>= ~250ms reaction + interp buffer; Slice 1 readability). 0/1 = near-instant (legacy).</summary>
|
||||||
public const int AttackWindupTicks = 22;
|
public const int AttackWindupTicks = 22;
|
||||||
|
|
||||||
|
/// <summary>Charger lunge telegraph windup (ticks). Single source shared by EnemyBaker (the baked client
|
||||||
|
/// danger-ramp denominator) and TuningConfig.Defaults, mirroring the Grunt AttackWindupTicks pattern so the
|
||||||
|
/// baked telegraph can't drift from the server windup.</summary>
|
||||||
|
public const int ChargerWindupTicks = 30;
|
||||||
|
|
||||||
// ---- Production / automation (M7: Harvester/Conveyor/Fabricator) ----
|
// ---- Production / automation (M7: Harvester/Conveyor/Fabricator) ----
|
||||||
|
|
||||||
/// <summary>Max production cycles a single machine awards in one process (bounds within-session
|
/// <summary>Max production cycles a single machine awards in one process (bounds within-session
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ namespace ProjectM.Simulation
|
|||||||
/// transiently-coexisting arenas can never interact in the shared PhysicsWorld.</summary>
|
/// transiently-coexisting arenas can never interact in the shared PhysicsWorld.</summary>
|
||||||
public const float RoomStrideX = 500f;
|
public const float RoomStrideX = 500f;
|
||||||
|
|
||||||
|
/// <summary>Region-flip boundary X (half the expedition offset): a player/camera past this reads as the
|
||||||
|
/// +1000 expedition region. Single source for the HUD / onboarding / atmosphere region checks (DR-013).</summary>
|
||||||
|
public const float RegionBoundaryX = ExpeditionOffsetX * 0.5f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// World-space origin of expedition room sub-slot <paramref name="subSlot"/> (0 or 1 — the run FSM
|
/// World-space origin of expedition room sub-slot <paramref name="subSlot"/> (0 or 1 — the run FSM
|
||||||
/// ping-pongs consecutive rooms between two offsets so the next room spawns at the idle slot while the
|
/// ping-pongs consecutive rooms between two offsets so the next room spawns at the idle slot while the
|
||||||
|
|||||||
Reference in New Issue
Block a user