Aetheria/Assets/Shaders/Compute/ShieldField/ShieldPanel.cs

522 lines
24 KiB
C#
Raw Permalink Normal View History

Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace ShieldField
{
public enum FracturePattern { Radial = 0, Star = 1, Lance = 2, Spiral = 3, Crystal = 4, Fizzle = 5 }
public class ShieldPanel : MonoBehaviour
{
// ---------------- authoring ----------------
[Header("Tiling")]
public TilingKind tiling = TilingKind.PenroseP3;
public float panelRadius = 5f;
public float cellSize = 0.35f;
public bool mergeRhombs = true; // Penrose triangles -> P3 rhombs
public bool takeDual = false; // render the dual tiling instead
public int seed = 1337;
[Header("Assets")]
public ComputeShader sim;
public Shader panelShader;
[Header("Materialisation")]
public float growDuration = 0.35f;
public float wavefrontSpeed = 8f;
public float delayJitter = 0.06f;
[Header("Wave")]
[Tooltip("c^2. Raise for a faster shock; watch the CFL warning in the console.")]
public float waveSpeed2 = 26f;
public float damping = 1.6f;
public float stressClamp = 4f;
[Range(1, 16)] public int substeps = 6;
public float fixedSubstepDt = 0.0025f;
[Header("Through-thickness echo")]
[Tooltip("Substeps for one round trip through the plate. Small = fast back-face spall.")]
[Range(1, 15)] public int echoDelaySteps = 5;
[Tooltip("Negative: a free back face returns the compressive pulse as tension.")]
public float echoReflect = -0.65f;
[Header("Material (tempered)")]
public float tensileStrength = 0.9f;
[Tooltip("Surface compression at full charge. This is the shield's hit budget.")]
public float temperInit = 0.55f;
public float temperErosion = 2.2f;
[Tooltip("How much stored core energy a break dumps back into the field.")]
public float storedEnergyGain = 0.8f;
public float damageGain = 6f;
[Range(0.05f, 1f)] public float breakAt = 0.75f;
[Range(0f, 0.9f)] public float crackStart = 0.18f;
[Tooltip("Breaks permitted per frame. Without this the cascade finishes in one frame and you see nothing.")]
[Range(0.001f, 1f)] public float breakBudgetFraction = 0.02f;
[Header("Shards")]
public float radialKick = 1.6f;
public float lateralKick = 2.2f;
public float normalKick = 1.1f;
public float spinKick = 7f;
public float shardLife = 2.5f;
public float shardDrag = 0.7f;
[Range(2, 8)] public int maxShards = 6;
[Header("Render")]
public float conduitGain = 2.5f;
// ---------------- runtime ----------------
struct PendingHit
{
public Vector2 pos, dir;
public float energy, radius, obliquity, spin;
public FracturePattern pattern;
public float starArms, focus, twist;
}
readonly Queue<PendingHit> pending = new();
TilingData data;
Material mat;
MaterialPropertyBlock mpb;
GraphicsBuffer bCells, bVerts, bEdges, bState;
GraphicsBuffer bUA, bUB, bV, bEcho, bFlow, bVisible, bBudget;
GraphicsBuffer argsFill;
bool uUsingA = true;
int kInit, kInject, kWave, kEcho, kFracture, kUpdate;
int groups, echoSlot;
float simTime;
Bounds worldBounds;
uint[] budgetScratch; // cached to avoid a per-frame allocation (D12)
Fix D17/D20/D21 and the pool-rebuild trap in ShieldPanel (Cut 3) D17: KInject/KWaveStep/KFracture gate on growth, which only KUpdate writes and only after this same frame's injection dispatch, so a panel struck the instant it spawns swallowed its own creating hit. ResetSim now seeds bState directly from a precomputed per-cell arm state (backdated by one growDuration) instead of leaving KInit's zero in place, so the strike centre reads as already grown. D20: _PanelSpawnTime was hardcoded to 0f every frame; it now tracks each panel's own backdated arm instant, so growth keeps advancing correctly on the frames after spawn instead of collapsing back under the gate. Pool-rebuild trap (not map-numbered, but load-bearing for the whole pool argument in §2.4): OnEnable unconditionally rebuilt and OnDisable unconditionally released, so every SetActive cycle a pooled panel goes through paid the 1.3-21ms Rebuild cost the pool exists to avoid. OnEnable now only rebuilds when not yet built; release moves to OnDestroy. ResetSim is also self-healing (builds if not yet built) rather than depending on OnEnable's timing, which a batchmode probe showed is not synchronous with Object.Instantiate in every context this runs in. D21: Penrose's iteration count was CeilToInt(...)+1, always overshooting the requested cellSize by a full extra phi-subdivision. Rounding to the nearest achievable iteration count instead brings Penrose within 2-5x of Hex's cell count at the same radius/cellSize, down from an order of magnitude. TilingBuilder.cs gains a CellState mirror struct (matching ShieldCommon.hlsl's CellState field-for-field) so ResetSim can write growth/temper/breakTime directly and the Cut 3 verification probe can read them back without a new readback path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:51:11 +02:00
// D17/D20 (Cut 3, docs/shield-panel-cut.md): the panel's own arm instant, backdated by
// one growDuration so a cell at the strike centre (spawnDelay ~= 0) is already past the
// KInject/KWaveStep/KFracture growth gate the moment it is struck. See ResetSim.
float panelSpawnTime;
CellState[] growthSeed; // cached per-cell arm state; rebuilt only when the tiling is (re)built,
// re-uploaded (not reallocated) on every ResetSim — no interception allocation.
Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
const int EchoSlots = 16;
static readonly int MaxV = Limits.MaxVerts;
// shader ids
static readonly int idCells = Shader.PropertyToID("_Cells");
static readonly int idVerts = Shader.PropertyToID("_Verts");
static readonly int idEdges = Shader.PropertyToID("_Edges");
static readonly int idState = Shader.PropertyToID("_State");
static readonly int idUIn = Shader.PropertyToID("_UIn");
static readonly int idUOut = Shader.PropertyToID("_UOut");
static readonly int idU = Shader.PropertyToID("_U");
static readonly int idV = Shader.PropertyToID("_V");
static readonly int idEcho = Shader.PropertyToID("_Echo");
static readonly int idFlow = Shader.PropertyToID("_EdgeFlow");
static readonly int idVisible = Shader.PropertyToID("_Visible");
static readonly int idBudget = Shader.PropertyToID("_BreakBudget");
// ==============================================================
Fix D17/D20/D21 and the pool-rebuild trap in ShieldPanel (Cut 3) D17: KInject/KWaveStep/KFracture gate on growth, which only KUpdate writes and only after this same frame's injection dispatch, so a panel struck the instant it spawns swallowed its own creating hit. ResetSim now seeds bState directly from a precomputed per-cell arm state (backdated by one growDuration) instead of leaving KInit's zero in place, so the strike centre reads as already grown. D20: _PanelSpawnTime was hardcoded to 0f every frame; it now tracks each panel's own backdated arm instant, so growth keeps advancing correctly on the frames after spawn instead of collapsing back under the gate. Pool-rebuild trap (not map-numbered, but load-bearing for the whole pool argument in §2.4): OnEnable unconditionally rebuilt and OnDisable unconditionally released, so every SetActive cycle a pooled panel goes through paid the 1.3-21ms Rebuild cost the pool exists to avoid. OnEnable now only rebuilds when not yet built; release moves to OnDestroy. ResetSim is also self-healing (builds if not yet built) rather than depending on OnEnable's timing, which a batchmode probe showed is not synchronous with Object.Instantiate in every context this runs in. D21: Penrose's iteration count was CeilToInt(...)+1, always overshooting the requested cellSize by a full extra phi-subdivision. Rounding to the nearest achievable iteration count instead brings Penrose within 2-5x of Hex's cell count at the same radius/cellSize, down from an order of magnitude. TilingBuilder.cs gains a CellState mirror struct (matching ShieldCommon.hlsl's CellState field-for-field) so ResetSim can write growth/temper/breakTime directly and the Cut 3 verification probe can read them back without a new readback path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:51:11 +02:00
// Cut 3 (docs/shield-panel-cut.md §2.4): a pooled panel is SetActive(false)/(true) every
// time it is returned to and taken back out of Assets/Scripts/Prototype.cs's pool, which
// fires OnDisable/OnEnable on every reuse, not just once at load. The pool's entire reason
// for existing is that a rebuild is 1.3-21 ms and a ResetSim is 8-11 us (§2.4); OnEnable
// unconditionally rebuilding and OnDisable unconditionally releasing would pay the
// expensive path on every single interception, silently defeating the pool. So the tiling
// and buffers are built once (guarded by `data == null`) and released only when the
// GameObject is actually destroyed, not merely deactivated -- ShieldInterceptor.ResetSim()
// is what re-arms a reused instance (see ResetSim below), not OnEnable.
void OnEnable() { if (data == null) Rebuild(); }
void OnDisable() { }
void OnDestroy() { ReleaseAll(); }
// Cut 3 verification surface: counts real tiling builds so the pooling fix (OnEnable only
// rebuilds when data == null) can be pinned against a regression that rebuilds on every
// pooled reuse (see ShieldPanelCut3Verify.CheckPoolReuseDoesNotRebuild).
public int DebugRebuildCount { get; private set; }
Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
[UnityEngine.ContextMenu("Rebuild")]
public void Rebuild()
{
Fix D17/D20/D21 and the pool-rebuild trap in ShieldPanel (Cut 3) D17: KInject/KWaveStep/KFracture gate on growth, which only KUpdate writes and only after this same frame's injection dispatch, so a panel struck the instant it spawns swallowed its own creating hit. ResetSim now seeds bState directly from a precomputed per-cell arm state (backdated by one growDuration) instead of leaving KInit's zero in place, so the strike centre reads as already grown. D20: _PanelSpawnTime was hardcoded to 0f every frame; it now tracks each panel's own backdated arm instant, so growth keeps advancing correctly on the frames after spawn instead of collapsing back under the gate. Pool-rebuild trap (not map-numbered, but load-bearing for the whole pool argument in §2.4): OnEnable unconditionally rebuilt and OnDisable unconditionally released, so every SetActive cycle a pooled panel goes through paid the 1.3-21ms Rebuild cost the pool exists to avoid. OnEnable now only rebuilds when not yet built; release moves to OnDestroy. ResetSim is also self-healing (builds if not yet built) rather than depending on OnEnable's timing, which a batchmode probe showed is not synchronous with Object.Instantiate in every context this runs in. D21: Penrose's iteration count was CeilToInt(...)+1, always overshooting the requested cellSize by a full extra phi-subdivision. Rounding to the nearest achievable iteration count instead brings Penrose within 2-5x of Hex's cell count at the same radius/cellSize, down from an order of magnitude. TilingBuilder.cs gains a CellState mirror struct (matching ShieldCommon.hlsl's CellState field-for-field) so ResetSim can write growth/temper/breakTime directly and the Cut 3 verification probe can read them back without a new readback path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:51:11 +02:00
DebugRebuildCount++;
Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
ReleaseAll();
if (sim == null || panelShader == null) return;
var soup = Tilings.Generate(tiling, panelRadius, cellSize, seed);
if (tiling == TilingKind.PenroseP3 && mergeRhombs)
soup = TilingBuilder.MergeRhombs(soup);
if (takeDual)
soup = TilingBuilder.TakeDual(soup);
data = TilingBuilder.Build(soup,
weldEps: Mathf.Max(cellSize * 0.02f, 1e-4f),
clipRadius: panelRadius,
wavefrontOrigin: Vector2.zero,
wavefrontSpeed: wavefrontSpeed,
delayJitter: delayJitter,
seed: seed);
AllocateBuffers();
CacheKernels();
ResetSim();
CheckCfl();
}
void AllocateBuffers()
{
int n = data.CellCount;
groups = Mathf.CeilToInt(n / 64f);
bCells = new GraphicsBuffer(GraphicsBuffer.Target.Structured, n, CellStatic.Stride);
bVerts = new GraphicsBuffer(GraphicsBuffer.Target.Structured, data.verts.Length, sizeof(float) * 2);
bEdges = new GraphicsBuffer(GraphicsBuffer.Target.Structured, Mathf.Max(data.EdgeCount, 1), DualEdge.Stride);
Fix D17/D20/D21 and the pool-rebuild trap in ShieldPanel (Cut 3) D17: KInject/KWaveStep/KFracture gate on growth, which only KUpdate writes and only after this same frame's injection dispatch, so a panel struck the instant it spawns swallowed its own creating hit. ResetSim now seeds bState directly from a precomputed per-cell arm state (backdated by one growDuration) instead of leaving KInit's zero in place, so the strike centre reads as already grown. D20: _PanelSpawnTime was hardcoded to 0f every frame; it now tracks each panel's own backdated arm instant, so growth keeps advancing correctly on the frames after spawn instead of collapsing back under the gate. Pool-rebuild trap (not map-numbered, but load-bearing for the whole pool argument in §2.4): OnEnable unconditionally rebuilt and OnDisable unconditionally released, so every SetActive cycle a pooled panel goes through paid the 1.3-21ms Rebuild cost the pool exists to avoid. OnEnable now only rebuilds when not yet built; release moves to OnDestroy. ResetSim is also self-healing (builds if not yet built) rather than depending on OnEnable's timing, which a batchmode probe showed is not synchronous with Object.Instantiate in every context this runs in. D21: Penrose's iteration count was CeilToInt(...)+1, always overshooting the requested cellSize by a full extra phi-subdivision. Rounding to the nearest achievable iteration count instead brings Penrose within 2-5x of Hex's cell count at the same radius/cellSize, down from an order of magnitude. TilingBuilder.cs gains a CellState mirror struct (matching ShieldCommon.hlsl's CellState field-for-field) so ResetSim can write growth/temper/breakTime directly and the Cut 3 verification probe can read them back without a new readback path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:51:11 +02:00
bState = new GraphicsBuffer(GraphicsBuffer.Target.Structured, n, CellState.Stride);
Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
bUA = new GraphicsBuffer(GraphicsBuffer.Target.Structured, n, sizeof(float));
bUB = new GraphicsBuffer(GraphicsBuffer.Target.Structured, n, sizeof(float));
bV = new GraphicsBuffer(GraphicsBuffer.Target.Structured, n, sizeof(float));
bEcho = new GraphicsBuffer(GraphicsBuffer.Target.Structured, n * EchoSlots, sizeof(float));
bFlow = new GraphicsBuffer(GraphicsBuffer.Target.Structured, Mathf.Max(data.EdgeCount, 1), sizeof(float));
bBudget = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 2, sizeof(uint));
bVisible = new GraphicsBuffer(GraphicsBuffer.Target.Append | GraphicsBuffer.Target.Structured,
n, sizeof(uint));
bCells.SetData(data.cells);
bVerts.SetData(data.verts);
if (data.EdgeCount > 0) bEdges.SetData(data.dualEdges);
// One args buffer, sized to the widest pass (Outline: n*2 segments * 6 verts).
// RenderParams exposes no per-pass selector in 6000.3.24f1 (D2), and a multi-pass material
// draws every pass from one indirect call anyway (D5/§2.1.4-5), so the three
// per-pass buffers this shipped with were dead weight, not three draw paths.
argsFill = MakeArgs((uint)(MaxV * 2 * 6));
mat = new Material(panelShader) { hideFlags = HideFlags.DontSave };
mpb = new MaterialPropertyBlock();
var b = data.bounds;
float pad = panelRadius * 1.5f + shardLife * 3f;
worldBounds = new Bounds(transform.TransformPoint(b.center),
b.size + Vector3.one * pad);
budgetScratch ??= new uint[2];
Fix D17/D20/D21 and the pool-rebuild trap in ShieldPanel (Cut 3) D17: KInject/KWaveStep/KFracture gate on growth, which only KUpdate writes and only after this same frame's injection dispatch, so a panel struck the instant it spawns swallowed its own creating hit. ResetSim now seeds bState directly from a precomputed per-cell arm state (backdated by one growDuration) instead of leaving KInit's zero in place, so the strike centre reads as already grown. D20: _PanelSpawnTime was hardcoded to 0f every frame; it now tracks each panel's own backdated arm instant, so growth keeps advancing correctly on the frames after spawn instead of collapsing back under the gate. Pool-rebuild trap (not map-numbered, but load-bearing for the whole pool argument in §2.4): OnEnable unconditionally rebuilt and OnDisable unconditionally released, so every SetActive cycle a pooled panel goes through paid the 1.3-21ms Rebuild cost the pool exists to avoid. OnEnable now only rebuilds when not yet built; release moves to OnDestroy. ResetSim is also self-healing (builds if not yet built) rather than depending on OnEnable's timing, which a batchmode probe showed is not synchronous with Object.Instantiate in every context this runs in. D21: Penrose's iteration count was CeilToInt(...)+1, always overshooting the requested cellSize by a full extra phi-subdivision. Rounding to the nearest achievable iteration count instead brings Penrose within 2-5x of Hex's cell count at the same radius/cellSize, down from an order of magnitude. TilingBuilder.cs gains a CellState mirror struct (matching ShieldCommon.hlsl's CellState field-for-field) so ResetSim can write growth/temper/breakTime directly and the Cut 3 verification probe can read them back without a new readback path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:51:11 +02:00
BuildGrowthSeed();
}
// D17/D20: precompute the arm-time CellState for every cell once per tiling build, keyed
// only on each cell's baked `spawnDelay` (TilingBuilder.cs, itself derived from the
// wavefrontOrigin the tiling was built with -- always the panel centre, per Cut 1's note
// that one tiling serves every panel). ResetSim re-uploads this same array on every arm/
// reuse; nothing here runs at interception time (§3's "no allocation at interception").
void BuildGrowthSeed()
{
growthSeed ??= new CellState[data.CellCount];
if (growthSeed.Length != data.CellCount) growthSeed = new CellState[data.CellCount];
for (int i = 0; i < growthSeed.Length; i++)
{
growthSeed[i] = default;
growthSeed[i].breakTime = -1f;
growthSeed[i].temper = temperInit;
// Backdate the arm instant by one growDuration (panelSpawnTime, set alongside this
// in ResetSim) so a cell with spawnDelay ~= 0 -- the strike centre -- reads as
// already past the 0.6 growth gate the instant it is struck, while a cell whose
// baked spawnDelay is >= growDuration starts at 0 and grows forward normally as
// simTime advances past panelSpawnTime. This is what fixes D17 (the growth gate
// otherwise swallows the hit that created the panel, because KUpdate -- the only
// writer of `growth` -- does not run until after this same frame's KInject) without
// touching the gate itself or adding a per-interception dispatch.
growthSeed[i].growth = Mathf.Clamp01(
(growDuration - data.cells[i].spawnDelay) / Mathf.Max(growDuration, 1e-3f));
}
Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
}
static GraphicsBuffer MakeArgs(uint vertsPerInstance)
{
var a = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1,
GraphicsBuffer.IndirectDrawArgs.size);
a.SetData(new[] { new GraphicsBuffer.IndirectDrawArgs {
vertexCountPerInstance = vertsPerInstance,
instanceCount = 0, startVertex = 0, startInstance = 0 } });
return a;
}
void CacheKernels()
{
kInit = sim.FindKernel("KInit");
kInject = sim.FindKernel("KInject");
kWave = sim.FindKernel("KWaveStep");
kEcho = sim.FindKernel("KEcho");
kFracture = sim.FindKernel("KFracture");
kUpdate = sim.FindKernel("KUpdate");
foreach (int k in new[] { kInit, kInject, kWave, kEcho, kFracture, kUpdate })
{
sim.SetBuffer(k, idCells, bCells);
sim.SetBuffer(k, idEdges, bEdges);
sim.SetBuffer(k, idState, bState);
sim.SetBuffer(k, idV, bV);
sim.SetBuffer(k, idEcho, bEcho);
sim.SetBuffer(k, idFlow, bFlow);
sim.SetBuffer(k, idBudget, bBudget);
}
sim.SetBuffer(kInit, idUOut, bUA);
sim.SetBuffer(kWave, idFlow, bFlow);
sim.SetBuffer(kUpdate, idVisible, bVisible);
}
[UnityEngine.ContextMenu("Reset Simulation")]
public void ResetSim()
{
Fix D17/D20/D21 and the pool-rebuild trap in ShieldPanel (Cut 3) D17: KInject/KWaveStep/KFracture gate on growth, which only KUpdate writes and only after this same frame's injection dispatch, so a panel struck the instant it spawns swallowed its own creating hit. ResetSim now seeds bState directly from a precomputed per-cell arm state (backdated by one growDuration) instead of leaving KInit's zero in place, so the strike centre reads as already grown. D20: _PanelSpawnTime was hardcoded to 0f every frame; it now tracks each panel's own backdated arm instant, so growth keeps advancing correctly on the frames after spawn instead of collapsing back under the gate. Pool-rebuild trap (not map-numbered, but load-bearing for the whole pool argument in §2.4): OnEnable unconditionally rebuilt and OnDisable unconditionally released, so every SetActive cycle a pooled panel goes through paid the 1.3-21ms Rebuild cost the pool exists to avoid. OnEnable now only rebuilds when not yet built; release moves to OnDestroy. ResetSim is also self-healing (builds if not yet built) rather than depending on OnEnable's timing, which a batchmode probe showed is not synchronous with Object.Instantiate in every context this runs in. D21: Penrose's iteration count was CeilToInt(...)+1, always overshooting the requested cellSize by a full extra phi-subdivision. Rounding to the nearest achievable iteration count instead brings Penrose within 2-5x of Hex's cell count at the same radius/cellSize, down from an order of magnitude. TilingBuilder.cs gains a CellState mirror struct (matching ShieldCommon.hlsl's CellState field-for-field) so ResetSim can write growth/temper/breakTime directly and the Cut 3 verification probe can read them back without a new readback path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:51:11 +02:00
// Cut 3: a freshly-instantiated pool instance's OnEnable is not guaranteed to have run
// by the time the interceptor calls ResetSim() on it in the same call stack (observed in
// the batchmode probe: Object.Instantiate's Awake/OnEnable are not synchronous with the
// call that created the clone in every context this runs in). ResetSim is what every
// spawn and reuse already goes through, so it -- not OnEnable's timing -- is what
// guarantees "built before struck". This costs nothing on the pooled-reuse path that
// §2.4's budget is about: data is already non-null there, so this is one null check, not
// a second rebuild.
if (data == null) { Rebuild(); return; } // Rebuild() ends by calling ResetSim() itself
if (sim == null) return;
Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
simTime = 0f;
echoSlot = 0;
uUsingA = true;
pending.Clear();
sim.SetInt("_CellCount", data.CellCount);
sim.SetFloat("_TemperInit", temperInit);
sim.SetBuffer(kInit, idUOut, bUA);
sim.Dispatch(kInit, groups, 1, 1);
// second clear so both halves of the ping-pong start at rest
sim.SetBuffer(kInit, idUOut, bUB);
sim.Dispatch(kInit, groups, 1, 1);
sim.SetBuffer(kInit, idUOut, bUA);
Fix D17/D20/D21 and the pool-rebuild trap in ShieldPanel (Cut 3) D17: KInject/KWaveStep/KFracture gate on growth, which only KUpdate writes and only after this same frame's injection dispatch, so a panel struck the instant it spawns swallowed its own creating hit. ResetSim now seeds bState directly from a precomputed per-cell arm state (backdated by one growDuration) instead of leaving KInit's zero in place, so the strike centre reads as already grown. D20: _PanelSpawnTime was hardcoded to 0f every frame; it now tracks each panel's own backdated arm instant, so growth keeps advancing correctly on the frames after spawn instead of collapsing back under the gate. Pool-rebuild trap (not map-numbered, but load-bearing for the whole pool argument in §2.4): OnEnable unconditionally rebuilt and OnDisable unconditionally released, so every SetActive cycle a pooled panel goes through paid the 1.3-21ms Rebuild cost the pool exists to avoid. OnEnable now only rebuilds when not yet built; release moves to OnDestroy. ResetSim is also self-healing (builds if not yet built) rather than depending on OnEnable's timing, which a batchmode probe showed is not synchronous with Object.Instantiate in every context this runs in. D21: Penrose's iteration count was CeilToInt(...)+1, always overshooting the requested cellSize by a full extra phi-subdivision. Rounding to the nearest achievable iteration count instead brings Penrose within 2-5x of Hex's cell count at the same radius/cellSize, down from an order of magnitude. TilingBuilder.cs gains a CellState mirror struct (matching ShieldCommon.hlsl's CellState field-for-field) so ResetSim can write growth/temper/breakTime directly and the Cut 3 verification probe can read them back without a new readback path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:51:11 +02:00
// D17/D20: KInit above just zeroed every cell's `growth` (and this panel's own local
// clock, simTime, back to 0). Overwrite with the precomputed arm-time state instead of
// leaving it at 0 -- see BuildGrowthSeed's comment for why waiting on KUpdate to catch
// up is one frame too late for a hit queued this same call. `growDuration` may have
// changed in the inspector since the seed was built, but recomputing it here would be a
// per-arm allocation-shaped cost the pool's 8-11 us budget (§2.4) has no room for;
// Rebuild (a manual, non-pooled action) is what re-derives the seed from authored
// fields, matching CheckCfl's existing must-Rebuild-to-see-it contract.
panelSpawnTime = -growDuration;
bState.SetData(growthSeed);
Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
}
void CheckCfl()
{
float minArea = float.MaxValue;
int maxDeg = 0;
foreach (var c in data.cells)
{
minArea = Mathf.Min(minArea, c.area);
maxDeg = Mathf.Max(maxDeg, (int)c.nbrCount);
}
// rough stability bound for the explicit leapfrog on an irregular graph
float lim = waveSpeed2 * fixedSubstepDt * fixedSubstepDt * maxDeg / Mathf.Max(minArea, 1e-6f);
if (lim > 1.8f)
Debug.LogWarning($"[ShieldPanel] CFL estimate {lim:F2} exceeds ~1.8 and the wave " +
$"will likely diverge. Lower waveSpeed2 or fixedSubstepDt, or raise cellSize. " +
$"(smallest cell area {minArea:E2}, max degree {maxDeg})", this);
}
Fix D17/D20/D21 and the pool-rebuild trap in ShieldPanel (Cut 3) D17: KInject/KWaveStep/KFracture gate on growth, which only KUpdate writes and only after this same frame's injection dispatch, so a panel struck the instant it spawns swallowed its own creating hit. ResetSim now seeds bState directly from a precomputed per-cell arm state (backdated by one growDuration) instead of leaving KInit's zero in place, so the strike centre reads as already grown. D20: _PanelSpawnTime was hardcoded to 0f every frame; it now tracks each panel's own backdated arm instant, so growth keeps advancing correctly on the frames after spawn instead of collapsing back under the gate. Pool-rebuild trap (not map-numbered, but load-bearing for the whole pool argument in §2.4): OnEnable unconditionally rebuilt and OnDisable unconditionally released, so every SetActive cycle a pooled panel goes through paid the 1.3-21ms Rebuild cost the pool exists to avoid. OnEnable now only rebuilds when not yet built; release moves to OnDestroy. ResetSim is also self-healing (builds if not yet built) rather than depending on OnEnable's timing, which a batchmode probe showed is not synchronous with Object.Instantiate in every context this runs in. D21: Penrose's iteration count was CeilToInt(...)+1, always overshooting the requested cellSize by a full extra phi-subdivision. Rounding to the nearest achievable iteration count instead brings Penrose within 2-5x of Hex's cell count at the same radius/cellSize, down from an order of magnitude. TilingBuilder.cs gains a CellState mirror struct (matching ShieldCommon.hlsl's CellState field-for-field) so ResetSim can write growth/temper/breakTime directly and the Cut 3 verification probe can read them back without a new readback path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:51:11 +02:00
// ==============================================================
// Read-only verification surface for the Cut 3 batchmode probe (Tests.asmdef cannot see
// Assembly-CSharp -- the map's Q7 -- so this, like Cut 1/2's probes, is what stands in for
// a unit test). Nothing outside ShieldPanel may WRITE these buffers (the authority map's
// "nothing outside ShieldPanel touches its buffers" is about writers); GraphicsBuffer.
// GetData is a read.
public int DebugCellCount => data?.CellCount ?? 0;
public GraphicsBuffer DebugStateBuffer => bState;
public GraphicsBuffer DebugVField => bV;
public Vector2 DebugCellCenter(int i) => data.cells[i].centroid;
Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
// ==============================================================
/// <summary>Strike the panel. worldDir is the projectile's travel direction.</summary>
public void Hit(Vector3 worldPos, Vector3 worldDir, float energy,
FracturePattern pattern = FracturePattern.Radial,
float radius = -1f)
{
if (data == null) return;
Vector3 local = transform.InverseTransformPoint(worldPos);
Vector3 ldir = transform.InverseTransformDirection(worldDir).normalized;
Vector2 flat = new Vector2(ldir.x, ldir.y);
float obliquity = Mathf.Clamp01(1f - Mathf.Abs(ldir.z));
pending.Enqueue(new PendingHit
{
pos = new Vector2(local.x, local.y),
dir = flat.sqrMagnitude > 1e-6f ? flat.normalized : Vector2.right,
energy = energy,
radius = radius > 0f ? radius : Mathf.Max(cellSize * 2.5f, panelRadius * 0.09f),
obliquity = obliquity,
pattern = pattern,
starArms = Random.Range(3, 9),
focus = Mathf.Lerp(0.15f, 0.55f, Random.value),
twist = Random.Range(0.4f, 1.6f),
spin = Random.value * Mathf.PI * 2f
});
}
// ==============================================================
void Update()
{
if (sim == null || data == null || mat == null) return;
float dt = Application.isPlaying ? Time.deltaTime : 1f / 60f;
simTime += dt;
sim.SetInt("_CellCount", data.CellCount);
sim.SetFloat("_Time", simTime);
PushMaterialUniforms();
// ---- break budget resets every frame; this is what paces the cascade
uint cap = (uint)Mathf.Max(1, Mathf.RoundToInt(data.CellCount * breakBudgetFraction));
budgetScratch[0] = 0u;
budgetScratch[1] = cap;
bBudget.SetData(budgetScratch);
// ---- inject queued hits
sim.SetFloat("_Dt", fixedSubstepDt);
while (pending.Count > 0)
{
var h = pending.Dequeue();
sim.SetVector("_HitPos", h.pos);
sim.SetVector("_HitDir", h.dir);
sim.SetFloat("_HitEnergy", h.energy);
sim.SetFloat("_HitRadius", h.radius);
sim.SetFloat("_HitObliquity", h.obliquity);
sim.SetInt("_Pattern", (int)h.pattern);
sim.SetFloat("_StarArms", h.starArms);
sim.SetFloat("_PatternFocus", h.focus);
sim.SetFloat("_SpiralTwist", h.twist);
sim.SetFloat("_HitSpin", h.spin);
sim.SetInt("_EchoSlot", echoSlot);
sim.SetBuffer(kInject, idV, bV);
sim.Dispatch(kInject, groups, 1, 1);
}
// ---- wave substeps (fixed dt: the CFL limit does not care about frame rate)
sim.SetFloat("_WaveSpeed2", waveSpeed2);
sim.SetFloat("_Damping", damping);
sim.SetFloat("_StressClamp", stressClamp);
sim.SetInt("_EchoDelay", echoDelaySteps);
sim.SetFloat("_EchoReflect", echoReflect);
for (int s = 0; s < substeps; s++)
{
var src = uUsingA ? bUA : bUB;
var dst = uUsingA ? bUB : bUA;
sim.SetBuffer(kWave, idUIn, src);
sim.SetBuffer(kWave, idUOut, dst);
sim.Dispatch(kWave, groups, 1, 1);
uUsingA = !uUsingA;
sim.SetInt("_EchoSlot", echoSlot);
sim.Dispatch(kEcho, groups, 1, 1);
echoSlot = (echoSlot + 1) % EchoSlots;
}
var cur = uUsingA ? bUA : bUB;
// ---- fracture
sim.SetFloat("_TensileStrength", tensileStrength);
sim.SetFloat("_TemperErosion", temperErosion);
sim.SetFloat("_StoredEnergyGain", storedEnergyGain);
sim.SetFloat("_DamageGain", damageGain);
sim.SetFloat("_BreakAt", breakAt);
sim.SetFloat("_CrackStart", crackStart);
sim.SetFloat("_RadialKick", radialKick);
sim.SetFloat("_LateralKick", lateralKick);
sim.SetFloat("_NormalKick", normalKick);
sim.SetFloat("_SpinKick", spinKick);
sim.SetBuffer(kFracture, idUIn, cur);
sim.Dispatch(kFracture, groups, 1, 1);
// ---- per-frame state advance + visibility
sim.SetFloat("_Dt", dt);
Fix D17/D20/D21 and the pool-rebuild trap in ShieldPanel (Cut 3) D17: KInject/KWaveStep/KFracture gate on growth, which only KUpdate writes and only after this same frame's injection dispatch, so a panel struck the instant it spawns swallowed its own creating hit. ResetSim now seeds bState directly from a precomputed per-cell arm state (backdated by one growDuration) instead of leaving KInit's zero in place, so the strike centre reads as already grown. D20: _PanelSpawnTime was hardcoded to 0f every frame; it now tracks each panel's own backdated arm instant, so growth keeps advancing correctly on the frames after spawn instead of collapsing back under the gate. Pool-rebuild trap (not map-numbered, but load-bearing for the whole pool argument in §2.4): OnEnable unconditionally rebuilt and OnDisable unconditionally released, so every SetActive cycle a pooled panel goes through paid the 1.3-21ms Rebuild cost the pool exists to avoid. OnEnable now only rebuilds when not yet built; release moves to OnDestroy. ResetSim is also self-healing (builds if not yet built) rather than depending on OnEnable's timing, which a batchmode probe showed is not synchronous with Object.Instantiate in every context this runs in. D21: Penrose's iteration count was CeilToInt(...)+1, always overshooting the requested cellSize by a full extra phi-subdivision. Rounding to the nearest achievable iteration count instead brings Penrose within 2-5x of Hex's cell count at the same radius/cellSize, down from an order of magnitude. TilingBuilder.cs gains a CellState mirror struct (matching ShieldCommon.hlsl's CellState field-for-field) so ResetSim can write growth/temper/breakTime directly and the Cut 3 verification probe can read them back without a new readback path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:51:11 +02:00
sim.SetFloat("_PanelSpawnTime", panelSpawnTime);
Land the six shield-panel sources, ported to built-in and compiling Cut 1 of docs/shield-panel-cut.md: land ShieldPanel.cs, ShieldPanel.shader, ShieldSim.compute, ShieldCommon.hlsl, TilingBuilder.cs and Tilings.cs under Assets/Shaders/Compute/ShieldField/, alongside Lightning/ and Slime/. Port (URP -> built-in, three substitutions per the map's §2.1 probe): - drop the UniversalPipeline SubShader tag, add IgnoreProjector - swap the URP Core.hlsl include for UnityCG.cginc - TransformWorldToHClip -> UnityWorldToClipPos (3 call sites) Drawing collapse (D2/D5): RenderParams has no shaderPass member in 6000.3.24f1, and a multi-pass material draws every SubShader pass from one indirect call regardless, so the three per-pass args buffers and draw calls collapse to one args buffer sized to the widest pass (Outline) and one Graphics.RenderPrimitivesIndirect call. Deleted: argsOutline, argsConduit, drawFill/drawOutline/drawConduits and their branches. Also deleted: ShieldPanelTester (the click harness the operator ruled against; it also can't run — activeInputHandler is New Input System only) and [ExecuteAlways] (incompatible with a pool per D13; Rebuild stays on the context menu). Defects fixed: - D1: TilingBuilder.cs's inner `dist` shadowed the outer one (CS0136); renamed to nbrDist. - D3: CellStatic was 15 floats against a declared Stride of 16; added pad2 on both the HLSL and C# sides. - D4: `centroid` is reserved in the compute compiler; renamed the ShieldCommon.hlsl field to `center` (kept the C# field name as-is; it's not reserved there and the two sides don't need matching names, only matching layout). - D6: fixed the comment on _BreakBudget — it's two elements (count + cap), not the "single element" the old comment claimed. - D12: the two-element uint[] Update() allocated every frame is now a cached field, cheaper still under a pool of many live panels. - D18 (R5 — operator ruling, not deferred to a follow-up): Penrose ignored panelRadius entirely — a fixed authored iteration count meant the tiling was just uniformly rescaled, not resampled, so cell count never grew with the panel. Every other tiling here uses cellSize to hold local cell size constant while radius extends coverage; Penrose now derives its iteration count the same way (each substitution divides edge length by phi, solved for the count that brings a unit edge to ~cellSize once scaled to radius). This retires the now-meaningless `penroseIterations` field and the `subdivisions` parameter it threaded through Tilings.Generate. - Also removed a dead `idx` counter in Tilings.Voronoi (D8's mention of dead code; the Vector2.zero sentinel ambiguity itself is unfixed, same as D7/D19 — Voronoi and TriHex stay recorded defects, not fixed, per R5). Not fixed here, per the map: D7 (Voronoi is unusably slow) and D19 (TriHex has zero dual edges) are recorded, not fixed. D9/D10 (fracture tuning) are Cut 5. D17 (growth gate blocks the spawning hit) and D20 (_PanelSpawnTime hardcoded to 0) are Cut 3. Repo-specific fix not in the map: Assets/Scripts/UI/ContextMenu.cs defines a global `ContextMenu` MonoBehaviour that shadows UnityEngine.ContextMenu, so `[ContextMenu("Rebuild")]` doesn't compile here (CS0616) even though it did in the map's throwaway probe project. Used the fully-qualified `[UnityEngine.ContextMenu(...)]` instead. Adds Assets/Scripts/Editor/ShieldPanelCut1Verify.cs, a batchmode probe (no NUnit path exists per the map's Q7 — Tests.asmdef can't see Assembly-CSharp): asserts zero shader/compute compile errors and that Penrose cell count actually scales with panelRadius (D18). Also adds tests/mutation_tests_shield_panel_cut1.py, which mutates the D18 fix, the D4 fix, and the shaderPass deletion in turn and asserts the probe goes red each time, restoring the source after each case (own file per the convention mutation_tests_addressables_cut.py established, so as not to touch item-provenance's or the addressables cut's harnesses). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:08:04 +02:00
sim.SetFloat("_GrowDuration", growDuration);
sim.SetFloat("_GlowDecay", 3.2f);
sim.SetFloat("_ShardLife", shardLife);
sim.SetFloat("_ShardDrag", shardDrag);
bVisible.SetCounterValue(0);
sim.Dispatch(kUpdate, groups, 1, 1);
GraphicsBuffer.CopyCount(bVisible, argsFill, sizeof(uint));
Draw(cur);
}
void PushMaterialUniforms()
{
mat.SetFloat("_CrackStart", crackStart);
mat.SetFloat("_ShardLife", shardLife);
mat.SetInt("_MaxShards", maxShards);
mat.SetFloat("_ConduitGain", conduitGain);
}
void Draw(GraphicsBuffer curU)
{
mpb.Clear();
mpb.SetBuffer(idCells, bCells);
mpb.SetBuffer(idVerts, bVerts);
mpb.SetBuffer(idEdges, bEdges);
mpb.SetBuffer(idState, bState);
mpb.SetBuffer(idU, curU);
mpb.SetBuffer(idFlow, bFlow);
mpb.SetBuffer(idVisible, bVisible);
mpb.SetMatrix("_PanelToWorld", transform.localToWorldMatrix);
mpb.SetFloat("_Time01", simTime);
worldBounds.center = transform.position;
var rp = new RenderParams(mat)
{
worldBounds = worldBounds,
matProps = mpb,
shadowCastingMode = ShadowCastingMode.Off,
receiveShadows = false,
layer = gameObject.layer
};
// RenderParams exposes no per-pass selector in 6000.3.24f1 (D2), and a multi-pass
// material draws every SubShader pass from one indirect call regardless
// (D5) — Fill, Outline and Conduit all run here, pixel-identical to the
// old three-call version (§2.1.5), off one args buffer sized to the
// widest pass (Outline).
Graphics.RenderPrimitivesIndirect(rp, MeshTopology.Triangles, argsFill, 1, 0);
}
// ==============================================================
void ReleaseAll()
{
void R(ref GraphicsBuffer b) { b?.Release(); b = null; }
R(ref bCells); R(ref bVerts); R(ref bEdges); R(ref bState);
R(ref bUA); R(ref bUB); R(ref bV); R(ref bEcho); R(ref bFlow);
R(ref bVisible); R(ref bBudget);
R(ref argsFill);
if (mat != null)
{
if (Application.isPlaying) Destroy(mat); else DestroyImmediate(mat);
mat = null;
}
data = null;
}
void OnDrawGizmosSelected()
{
Gizmos.color = new Color(0.4f, 0.8f, 1f, 0.5f);
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawWireSphere(Vector3.zero, panelRadius);
}
}
}