param( [string]$SkillFile = "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/HakureiNorwayHeroSkill.cs", [string]$ActionFile = "Unity/Assets/Scripts/TH1_Logic/Action/ActionLogic.cs", [string]$FragmentFile = "Unity/Assets/Scripts/TH1_Anim/Fragments/FragmentSuikaFallingSplash.cs", [string]$FragmentDataFile = "Unity/Assets/Scripts/TH1_Anim/Fragments/FragmentData.cs", [string]$AttackGroundFragmentFile = "Unity/Assets/Scripts/TH1_Anim/Fragments/FragmentAttackGround.cs", [string]$PlayerDataFile = "Unity/Assets/Scripts/TH1_Data/PlayerData.cs", [string]$UnitLogicFile = "Unity/Assets/Scripts/TH1_Logic/Unit/UnitLogic.cs", [string]$FragmentManagerFile = "Unity/Assets/Scripts/TH1_Anim/FragmentManager.cs", [string]$AtomFile = "Unity/Assets/Scripts/TH1_Anim/UnitAtomAnim/UnitAtomAnim.cs", [string]$AtomDataFile = "Unity/Assets/Scripts/TH1_Anim/UnitAtomAnim/UnitAtomAnimData.cs", [string]$AtomMoveFile = "Unity/Assets/Scripts/TH1_Anim/UnitAtomAnim/UnitAtomAnimMove.cs" ) $ErrorActionPreference = "Stop" $repoRoot = git rev-parse --show-toplevel 2>$null if (-not $repoRoot) { throw "Not inside a git repository." } $repoRoot = [System.IO.Path]::GetFullPath($repoRoot.Trim()) function Read-RepoFile([string]$relativePath) { $fullPath = Join-Path $repoRoot $relativePath if (-not (Test-Path -LiteralPath $fullPath)) { throw "Required file not found: $fullPath" } return Get-Content -LiteralPath $fullPath -Raw -Encoding UTF8 } $skillText = Read-RepoFile $SkillFile $actionText = Read-RepoFile $ActionFile $fragmentText = Read-RepoFile $FragmentFile $fragmentDataText = Read-RepoFile $FragmentDataFile $attackGroundFragmentText = Read-RepoFile $AttackGroundFragmentFile $playerDataText = Read-RepoFile $PlayerDataFile $unitLogicText = Read-RepoFile $UnitLogicFile $fragmentManagerText = Read-RepoFile $FragmentManagerFile $atomText = Read-RepoFile $AtomFile $atomDataText = Read-RepoFile $AtomDataFile $atomMoveText = Read-RepoFile $AtomMoveFile function Assert-Contains([string]$text, [string]$needle, [string]$label) { if (-not $text.Contains($needle)) { throw "Suika falling splash animation guardrail failed: $label missing '$needle'." } } Assert-Contains $atomText 'ParabolaMove' 'atom animation type' Assert-Contains $atomText 'UnitAtomAnimType.ParabolaMove => new UnitAtomAnimParabolaMove' 'atom animation factory' Assert-Contains $atomDataText 'UnitAtomAnimParabolaMoveData' 'atom animation data' Assert-Contains $atomDataText 'float duration = 1.1f' 'parabola default duration' Assert-Contains $atomDataText 'float height = 18f' 'parabola default height' Assert-Contains $atomMoveText 'class UnitAtomAnimParabolaMove' 'parabola atom animation' Assert-Contains $fragmentManagerText 'FragmentType.SuikaFallingSplash => new FragmentSuikaFallingSplash' 'fragment factory' Assert-Contains $fragmentText 'class FragmentSuikaFallingSplash' 'Suika fragment' Assert-Contains $fragmentText 'private const float JumpDuration = 1.1f;' 'Suika fragment jump duration' Assert-Contains $fragmentText 'private const float JumpHeight = 18f;' 'Suika fragment jump height' Assert-Contains $fragmentText 'SplashImpactPause' 'Suika fragment post-splash pause' Assert-Contains $fragmentText 'UnitAtomAnimType.ParabolaMove' 'Suika fragment launch arc' Assert-Contains $fragmentText 'AnimPhase.AttackImpact + 20' 'post-impact final move phase' Assert-Contains $fragmentText 'UnitAtomAnimType.Move' 'post-impact final move' Assert-Contains $fragmentText 'RefreshFinalState' 'final refresh' Assert-Contains $fragmentText 'RefreshLandingSight' 'landing sight refresh' Assert-Contains $fragmentText 'foreach (var grid in Data.SightRefreshGrids)' 'new sight grid iteration' Assert-Contains $fragmentText 'gridRenderer?.InstantUpdateGrid(true)' 'new sight grid force refresh' Assert-Contains $playerDataText 'GetAroundGridIdList(range, grid, remainCenter: true)' 'movement sight includes standing grid' Assert-Contains $fragmentDataText 'public List SightRefreshGrids;' 'fragment sight refresh data' Assert-Contains $unitLogicText 'public List SuikaFallingSplashSightRefreshGrids;' 'attack info sight refresh data' Assert-Contains $actionText 'sightRefreshGrids: attackInfo.SuikaFallingSplashSightRefreshGrids' 'attack fragment sight refresh handoff' Assert-Contains $actionText 'List suikaGroundSightRefreshGrids = null;' 'ground jump sight refresh cache' Assert-Contains $actionText 'sightRefreshGrids: suikaGroundSightRefreshGrids' 'ground jump fragment sight refresh handoff' Assert-Contains $actionText 'FragmentType.SuikaFallingSplash, suikaData' 'ground self-jump fragment' Assert-Contains $actionText 'TryCreateSuikaFallingSplashFragment' 'attack action fragment switch' Assert-Contains $actionText 'FragmentType.SuikaFallingSplash' 'attack action fragment type' Assert-Contains $actionText 'visualCollector?.FlushTo(suikaFallingSplashFragment)' 'splash damage visual collection' Assert-Contains $skillText 'TryRepositionUnitWithoutMoveSideEffects(map, suika, target)' 'ground self-jump data-only reposition' Assert-Contains $skillText 'CollectSuikaFallingSplashNewSightGrids' 'Suika sight refresh collector' Assert-Contains $skillText 'GetAroundGridIdList(range, landingGrid, remainCenter: true)' 'Suika landing sight includes final grid' Assert-Contains $skillText 'attackInfo.IsSuikaFallingSplash = true;' 'skill attack marker' Assert-Contains $skillText 'attackInfo.SuikaFallingSplashFinalGrid = landingGrid;' 'skill final grid marker' Assert-Contains $skillText 'ExecuteSuikaFallingSplashDamage(map, suika, targetGrid, AnimPhase.AttackImpact + 10)' 'post-impact splash visual phase' Assert-Contains $skillText 'TargetGridHasOtherUnit(map, suika, landingGrid)' 'death-replacement target occupancy guard' if ($attackGroundFragmentText -match 'SkillType\.SuikaFallingSplash\s*=>\s*ProjectileType\.Bomb') { throw "Suika falling splash animation guardrail failed: FragmentAttackGround must not map SuikaFallingSplash to Bomb projectile." } $method = [regex]::Match($skillText, '(?ms)public static bool TryExecuteSuikaFlyAfterAttack\(MapData map, AttackInfo attackInfo\).*?^\s*private static void ExecuteSuikaFallingSplashDamage') if (-not $method.Success) { throw "Suika falling splash animation guardrail failed: cannot locate TryExecuteSuikaFlyAfterAttack body." } $body = $method.Value if ($body -notmatch 'ExecuteSuikaFallingSplashDamage\(map, suika, targetGrid, AnimPhase\.AttackImpact \+ 10\);[\s\S]*?TargetGridHasOtherUnit\(map, suika, landingGrid\)[\s\S]*?TryFindRandomEmptyAround\(map, suika, targetGrid, out landingGrid\)[\s\S]*?sightRefreshGrids = CollectSuikaFallingSplashNewSightGrids\(map, suika, attackInfo\.OriginPlayer,[\s\S]*?landingGrid\);[\s\S]*?TryRepositionUnitWithoutMoveSideEffects\(map, suika, landingGrid\)[\s\S]*?attackInfo\.IsSuikaFallingSplash = true;[\s\S]*?attackInfo\.SuikaFallingSplashFinalGrid = landingGrid;[\s\S]*?attackInfo\.SuikaFallingSplashSightRefreshGrids = sightRefreshGrids;[\s\S]*?UpdateSightByPath') { throw "Suika falling splash animation guardrail failed: attack-after-splash must settle damage, avoid death-replacement occupants, cache new sight grids, reposition data, then update sight." } $groundMethod = [regex]::Match($actionText, '(?ms)public class UnitAttackGroundAction.*?protected override bool Execute\(CommonActionParams actionParams\).*?^\s*public override bool CheckCan') if (-not $groundMethod.Success) { throw "Suika falling splash animation guardrail failed: cannot locate UnitAttackGroundAction execute body." } if ($groundMethod.Value -notmatch 'suikaGroundSightRefreshGrids = HakureiNorwayHeroSkillUtil\.CollectSuikaFallingSplashNewSightGrids[\s\S]*?unit1\.AttackGroundExecute\(actionParams\.MapData, targetGrid, out animSkillData,[\s\S]*?out skillAttackGroundCountsAsActiveAttack\)[\s\S]*?sightRefreshGrids: suikaGroundSightRefreshGrids') { throw "Suika falling splash animation guardrail failed: ground self-jump must cache sight grids before AttackGroundExecute updates sight, then pass them into the Suika fragment." } $fragmentMethod = [regex]::Match($fragmentText, '(?ms)public class FragmentSuikaFallingSplash.*?^\s*public override void OnUpdate') if (-not $fragmentMethod.Success) { throw "Suika falling splash animation guardrail failed: cannot locate FragmentSuikaFallingSplash body." } if ($fragmentMethod.Value -notmatch 'UnitAtomAnimParabolaMoveData[\s\S]*?JumpHeight[\s\S]*?JumpDuration[\s\S]*?PlayTargetAttackImpact\(\);[\s\S]*?PlayLandingImpact\(\);[\s\S]*?AnimPhase\.AttackImpact \+ 15[\s\S]*?SplashImpactPause[\s\S]*?AnimPhase\.AttackImpact \+ 20[\s\S]*?UnitAtomAnimType\.Move[\s\S]*?AnimPhase\.Settle') { throw "Suika falling splash animation guardrail failed: fragment must play a high, slow parabola, impact/splash pause, optional final move, then settle refresh." } Write-Host "Suika falling splash animation guardrail passed."