From 52b3664adf13b2372fb1fc7d2ea84dedd05ab986 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 12 Jul 2026 00:19:03 +0100 Subject: [PATCH] Publish weapon fire refusals --- .../AetheriaRuntimeDaemonSimulation.cs | 35 +++++++++++++++++++ .../AetheriaRuntimeSnapshotDocuments.cs | 3 ++ docs/original-game-migration-matrix.md | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs index 3c83b385d..fb57262f2 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs @@ -292,6 +292,7 @@ namespace GameCult.Aetheria.State.Verse var triggerResult = CommitWeaponRound(attacker, weapon); if (triggerResult == WeaponRoundResult.ReloadStarted) AppendWeaponEvent(run, zone, attacker, weapon, frameId, "weapon.reload.started"); + PublishWeaponRoundResult(run, zone, attacker, weapon, frameId, triggerResult); if (triggerResult != WeaponRoundResult.Fired) continue; } weapon.State.BurstRemaining = weapon.BurstCount; @@ -309,6 +310,7 @@ namespace GameCult.Aetheria.State.Verse var roundResult = CommitWeaponRound(attacker, weapon); if (roundResult == WeaponRoundResult.ReloadStarted) AppendWeaponEvent(run, zone, attacker, weapon, frameId, "weapon.reload.started"); + PublishWeaponRoundResult(run, zone, attacker, weapon, frameId, roundResult); if (roundResult != WeaponRoundResult.Fired) { weapon.State.BurstRemaining = 0; @@ -379,6 +381,39 @@ namespace GameCult.Aetheria.State.Verse }); } + private static void PublishWeaponRoundResult( + AetheriaRuntimeRunCheckpointCommit run, + AetheriaRuntimeZoneSnapshotCommit zone, + AetheriaRuntimeEntitySnapshotCommit entity, + ResolvedWeapon weapon, + long frameId, + WeaponRoundResult result) + { + var reason = result == WeaponRoundResult.InsufficientEnergy ? "insufficient-energy" : + result == WeaponRoundResult.NoAmmo ? "no-ammunition" : ""; + if (string.IsNullOrEmpty(reason)) + { + weapon.State.LastRefusalReason = ""; + return; + } + if (string.Equals(weapon.State.LastRefusalReason, reason, StringComparison.Ordinal)) + return; + + weapon.State.LastRefusalReason = reason; + AetheriaRuntimeGameEvents.Append(run, new AetheriaRuntimeGameEventCommit + { + EventId = $"frame:{frameId}:zone:{zone.ZoneIndex}:entity:{entity.EntityIndex}:weapon:{weapon.State.OwnerIndex}:{weapon.State.BehaviorIndex}:refused:{reason}", + Kind = "weapon.fire.refused", + FrameId = frameId, + ZoneIndex = zone.ZoneIndex, + SourceEntityIndex = entity.EntityIndex, + TargetEntityIndex = entity.TargetEntityIndex, + SubjectKey = reason, + ItemKey = weapon.ItemKey, + ScalarValue = weapon.State.Ammo + }); + } + private static void UpdateWeaponLock( AetheriaRuntimeEntitySnapshotCommit attacker, AetheriaRuntimeEntitySnapshotCommit target, diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs index 806d438ec..080fe6145 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs @@ -1113,6 +1113,9 @@ namespace GameCult.Aetheria.State.Verse [Key(18)] public int LockTargetEntityIndex { get; set; } = -1; + + [Key(19)] + public string LastRefusalReason { get; set; } = ""; } [MessagePackObject] diff --git a/docs/original-game-migration-matrix.md b/docs/original-game-migration-matrix.md index 300393373..8b8ad69de 100644 --- a/docs/original-game-migration-matrix.md +++ b/docs/original-game-migration-matrix.md @@ -36,7 +36,7 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity. | Minimap/tactical map | `ZoneRenderer`, map cameras | daemon world facts | explicit map projections | Unity and Electron | facts fragmented; generic lowerers absent | same state in both runtimes | | Fields/fog/gravity | zone renderer and shaders | daemon/plugin semantics | Fields plugin documents | EveUnity fields lowerer | owner-exists; basic lowering; composition parity absent | visual parity captures | | HUD/schematic | ARPG HUD and `SchematicDisplay` | Eve composition from daemon facts | semantic status/components | EveUnity UI | reference | source-fact and timing matrix | -| Hit/lock/warnings | `ActionGameManager` feedback | daemon event facts | transient feedback stream | VFX/UI/audio | bounded deduplicated game-event ledger and Eve `feedback.stream` wired for pickup outcomes, projectile launch/impact, entity damage, alive-to-dead destruction, and authoritative weapon reload start/completion chronology; reload interval is proven projectile-free | add lock transitions, resource refusals, thermal and docking event families with runtime effect consumption | +| Hit/lock/warnings | `ActionGameManager` feedback | daemon event facts | transient feedback stream | VFX/UI/audio | bounded deduplicated game-event ledger and Eve `feedback.stream` wired for pickup outcomes, projectile launch/impact, entity damage, alive-to-dead destruction, authoritative weapon reload chronology, and episode-deduplicated `weapon.fire.refused` facts for insufficient energy or missing ammunition; refusal identity, authored weapon, target, reason, and remaining magazine state are runtime-independent cockpit inputs | add lock transitions, thermal and docking event families, negative transaction fixtures, and runtime effect consumption | | Assets | `Assets/Resources` | Aetheria provider | asset catalog + CultMesh CDN | runtime cache/load | wired; basic lowering; not live-proven remotely | cold/warm remote-only live load and cache proof | | Save/resume | original persistence paths | daemon/CultCache | published current state | clients rehydrate | owner-exists; not parity-proven | mid-loop restart | | Terminus loop | original game composition | daemon/Ymir/Eve | pilot surface | Aetheria.Unity | incomplete; no scenario proof | fly/fire/loot/dock/trade |