Turrets: 40 Ore + per-base cap of 6 + fit-one-cell textured model (fix cheap/spam/massive/untextured)

Operator: turrets were "super duper cheap", spammable "unlimited", "spaced weirdly", "massive and not textured".
All four were real and independently rooted (placement grid was actually fine).

- Cost: TurretCostOre 10 -> 40 (authoring default + the serialized Gameplay subscene value, which overrides the
  code default). A node yields 30 Ore, so a turret is now ~1.3 nodes instead of 1/3 of one.
- Cap: new Tuning.TurretCap=6, enforced server-authoritatively in BuildPlaceSystem (count live Base turrets while
  building the occupancy set; reject placement at the cap, same-tick-safe). Was unlimited.
- Model: the 1.6x Synty ballista (~5m on a 1m cell, clipping neighbours) scaled to 0.8 to fit one cell; the C5
  BoxCollider shrunk to match (0.8x1.2x0.8, center y 0.6); all 6 sub-renderers swapped off the flat untextured
  teal Mat_StructureOwned_Cyan to the Synty atlas PolygonFantasyKingdom_Mat_01_A (textured). Play-verified
  TurretCost=40 Ore / cap=6 baked; no exceptions.

Also fixes 3 EditMode tests that pinned the old dash knobs (the prior tuning commit changed iframe 12->14 /
cooldown 45->36 but I committed it without re-running tests): DashSystemTests now derives the expected dash speed
from TuningConfig.Defaults() (robust to future tuning) + asserts now+14/+36; TuningConfigTests pins the new
defaults. 390/390 EditMode green.

Investigation: wf_c6c87dc5-9c3 (turret lane). Operator fork: 40 Ore + cap 6 (stricter).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 23:07:57 -07:00
parent 1b704ca0b9
commit 09183cc139
7 changed files with 33 additions and 22 deletions
@@ -35,8 +35,8 @@ namespace ProjectM.Tests
return (world, group);
}
// Dash speed derived from the baked knobs: 4.0 units / (12 ticks / 60) = 20 units/s.
const float ExpectedDashSpeed = 20f;
// Dash speed derived from the live knobs: DashDistance / (IFrameWindowTicks/60). Tracks TuningConfig.Defaults().
static readonly float ExpectedDashSpeed = TuningConfig.Defaults().DashDistance / (TuningConfig.Defaults().IFrameWindowTicks / 60f);
static Entity MakeDasher(EntityManager em, float2 facing)
{
@@ -69,7 +69,7 @@ namespace ProjectM.Tests
var ctrl = em.GetComponentData<CharacterControl>(e).MoveVelocity;
Assert.AreEqual(0f, ctrl.x, 1e-3f, "Dash heading (0,1) overrides X to 0.");
Assert.AreEqual(0f, ctrl.y, 1e-3f, "Planar dash keeps Y at 0.");
Assert.AreEqual(ExpectedDashSpeed, ctrl.z, 1e-3f, "i-frame window overrides velocity to Dir*dashSpeed (20).");
Assert.AreEqual(ExpectedDashSpeed, ctrl.z, 1e-3f, "i-frame window overrides velocity to Dir*dashSpeed (derived from the dash knobs).");
Assert.AreEqual(200f, em.GetComponentData<CharacterComponent>(e).GroundedMovementSharpness, 1e-3f,
"i-frame window raises GroundedMovementSharpness to ~200 (the blink).");
}
@@ -133,9 +133,9 @@ namespace ProjectM.Tests
var ds = em.GetComponentData<DashState>(e);
Assert.AreEqual(100u, ds.StartTick, "StartTick = now.");
Assert.AreEqual(112u, ds.IFrameUntilTick, "IFrameUntilTick = now + 12.");
Assert.AreEqual(121u, ds.RecoverUntilTick, "RecoverUntilTick = now + 12 + 9.");
Assert.AreEqual(145u, em.GetComponentData<DashCooldown>(e).NextTick, "Cooldown = now + 45.");
Assert.AreEqual(114u, ds.IFrameUntilTick, "IFrameUntilTick = now + 14.");
Assert.AreEqual(123u, ds.RecoverUntilTick, "RecoverUntilTick = now + 14 + 9.");
Assert.AreEqual(136u, em.GetComponentData<DashCooldown>(e).NextTick, "Cooldown = now + 36.");
}
}
@@ -24,9 +24,9 @@ namespace ProjectM.Tests
{
var d = TuningConfig.Defaults();
Assert.AreEqual(4.0f, d.DashDistance, 1e-6f, "DashDistance");
Assert.AreEqual(12f, d.IFrameWindowTicks, 1e-6f, "IFrameWindowTicks");
Assert.AreEqual(14f, d.IFrameWindowTicks, 1e-6f, "IFrameWindowTicks");
Assert.AreEqual(9f, d.RecoverTailTicks, 1e-6f, "RecoverTailTicks");
Assert.AreEqual(45f, d.DashCooldownTicks, 1e-6f, "DashCooldownTicks");
Assert.AreEqual(36f, d.DashCooldownTicks, 1e-6f, "DashCooldownTicks");
Assert.AreEqual(200f, d.DashSharpness, 1e-6f, "DashSharpness");
Assert.AreEqual(30f, d.ChargerWindupTicks, 1e-6f, "ChargerWindupTicks");
Assert.AreEqual(16f, d.ChargerLungeSpeed, 1e-6f, "ChargerLungeSpeed");