Fix move transform visual refresh
This commit is contained in:
parent
c72daf8c46
commit
bbfed5bf97
@ -0,0 +1,53 @@
|
|||||||
|
# TH1-CI-2026-06-30-002 Move Transform Visual Refresh
|
||||||
|
|
||||||
|
- Status: Fixed in code; guardrail added; Unity validation pending
|
||||||
|
- First recorded date: 2026-06-30
|
||||||
|
- Severity: High
|
||||||
|
|
||||||
|
## Raw Symptom
|
||||||
|
|
||||||
|
Player report: a unit passively displaced by a giant into a port-water tile looked like it was still a ship after later moving ashore.
|
||||||
|
|
||||||
|
Existing local evidence:
|
||||||
|
|
||||||
|
- `DOC/bugs.json` has an open report: "船移动上岸但是没有立刻变成陆地单位".
|
||||||
|
- CrashSight summaries include repeated `BoatUnitOnLandState` / `BoatUnitOnLandBeforeAction` diagnostics around move fragments and landing-related paths.
|
||||||
|
|
||||||
|
## Why This Recurs
|
||||||
|
|
||||||
|
TH1 movement mutates authoritative data before the move Fragment plays. `UnitLogic.MoveToLogic` correctly handles:
|
||||||
|
|
||||||
|
- `LandAndPort` unit entering `ResourceType.Port` through `LandToBoat`.
|
||||||
|
- `WaterAndAshore` unit entering `TerrainType.Land` through `BoatToLand`.
|
||||||
|
|
||||||
|
However, `UnitTypeTransform` only changes data and skill state. Renderer sprite refresh normally happens at the end of `FragmentMove`, so during the move animation the visible unit can still use the pre-transform sprite. This is especially visible after passive displacement into a port followed by landing.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`FragmentMove` started the move animation before refreshing the `UnitRenderer` image from already-mutated `UnitData`. When the action had already converted a unit between land and ship forms, the renderer could animate with the stale sprite until the fragment settle step.
|
||||||
|
|
||||||
|
## Root-Cause Fix
|
||||||
|
|
||||||
|
- Added `UnitRenderer.InstantUpdateUnitImageOnly()` for image/status refresh without changing visibility or position.
|
||||||
|
- `FragmentMove` now calls that method before enqueuing the move atom animation, so movement involving `LandToBoat` / `BoatToLand` starts with the current data form while preserving the existing final settle refresh.
|
||||||
|
|
||||||
|
## Guardrail Added
|
||||||
|
|
||||||
|
- `Tools/CheckMoveTransformVisualRefresh.ps1` checks that:
|
||||||
|
- `UnitRenderer` exposes the image-only refresh method.
|
||||||
|
- `FragmentMove` refreshes transformed unit image before starting `UnitAtomAnimType.Move`.
|
||||||
|
- `FragmentMove` still performs its final `InstantUpdateUnit(true)` settle refresh.
|
||||||
|
|
||||||
|
## Verification Performed
|
||||||
|
|
||||||
|
- Pending in this work item:
|
||||||
|
- `Tools/CheckMoveTransformVisualRefresh.ps1`
|
||||||
|
- `dotnet build Unity/TH1.Hotfix.csproj --no-restore`
|
||||||
|
|
||||||
|
## Remaining Validation Gaps
|
||||||
|
|
||||||
|
- Unity Editor visual validation is still required for the exact scenario:
|
||||||
|
1. A giant spawns or moves onto an occupied tile.
|
||||||
|
2. The displaced land unit passively moves into an allied port-water tile.
|
||||||
|
3. The resulting ship moves ashore.
|
||||||
|
4. During and after the landing animation, the unit sprite matches its land form.
|
||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
| ID | 日期 | 状态 | 严重度 | 问题 | 根治方向 | 记录 |
|
| ID | 日期 | 状态 | 严重度 | 问题 | 根治方向 | 记录 |
|
||||||
| --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| TH1-CI-2026-06-30-002 | 2026-06-30 | Fixed in code; guardrail added; Unity validation pending | High | 上下船移动的数据形态已变化,但 `FragmentMove` 开始动画时仍可能显示旧船/旧陆地外观 | 在移动 Fragment 起步前用当前 `UnitData` 刷新单位图片,同时保留终点 settle 刷新 | [record](2026-06-30-move-transform-visual-refresh.md) |
|
||||||
| TH1-CI-2026-06-30-001 | 2026-06-30 | Fixed in code; guardrail added; Unity validation pending | High | `UnitAttackGround` support/place skills can reset Peace wonder progress because the entrypoint name is treated as active attack | Centralize ground-target active-attack semantics and require every `AttackGroundExecute` skill to declare whether it counts as an attack | [record](2026-06-30-attackground-peace-wonder-semantics.md) |
|
| TH1-CI-2026-06-30-001 | 2026-06-30 | Fixed in code; guardrail added; Unity validation pending | High | `UnitAttackGround` support/place skills can reset Peace wonder progress because the entrypoint name is treated as active attack | Centralize ground-target active-attack semantics and require every `AttackGroundExecute` skill to declare whether it counts as an attack | [record](2026-06-30-attackground-peace-wonder-semantics.md) |
|
||||||
| TH1-CI-2026-06-29-001 | 2026-06-29 | Fixed in code; 17-player 10-turn batch passed | Critical | AI Director fallback can repeat zero-effect actions until the AI loop guard fires | Filter no-progress fallback actions, align BuildWonder CheckCan with Execute prerequisites, and keep batch diagnostics compact | [record](2026-06-29-ai-director-zero-effect-action-loop.md) |
|
| TH1-CI-2026-06-29-001 | 2026-06-29 | Fixed in code; 17-player 10-turn batch passed | Critical | AI Director fallback can repeat zero-effect actions until the AI loop guard fires | Filter no-progress fallback actions, align BuildWonder CheckCan with Execute prerequisites, and keep batch diagnostics compact | [record](2026-06-29-ai-director-zero-effect-action-loop.md) |
|
||||||
| TH1-CI-2026-06-27-001 | 2026-06-27 | Fixed in code; guardrail added; Unity validation pending | High | Suika Lv4 falling splash water restriction applied to empty grids but not unit targets on water | Share falling-splash water target validation across ground targeting, unit targeting, and `UnitAttack` CheckCan filtering | [record](2026-06-27-suika-falling-splash-water-targeting.md) |
|
| TH1-CI-2026-06-27-001 | 2026-06-27 | Fixed in code; guardrail added; Unity validation pending | High | Suika Lv4 falling splash water restriction applied to empty grids but not unit targets on water | Share falling-splash water target validation across ground targeting, unit targeting, and `UnitAttack` CheckCan filtering | [record](2026-06-27-suika-falling-splash-water-targeting.md) |
|
||||||
|
|||||||
52
Tools/CheckMoveTransformVisualRefresh.ps1
Normal file
52
Tools/CheckMoveTransformVisualRefresh.ps1
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
param(
|
||||||
|
[string]$FragmentMoveFile = "Unity/Assets/Scripts/TH1_Anim/Fragments/FragmentMove.cs",
|
||||||
|
[string]$UnitRendererFile = "Unity/Assets/Scripts/TH1_Renderer/UnitRenderer.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())
|
||||||
|
$fragmentMovePath = Join-Path $repoRoot $FragmentMoveFile
|
||||||
|
$unitRendererPath = Join-Path $repoRoot $UnitRendererFile
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $fragmentMovePath)) {
|
||||||
|
throw "FragmentMove file not found: $fragmentMovePath"
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $unitRendererPath)) {
|
||||||
|
throw "UnitRenderer file not found: $unitRendererPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
$fragmentMoveText = Get-Content -LiteralPath $fragmentMovePath -Raw -Encoding UTF8
|
||||||
|
$unitRendererText = Get-Content -LiteralPath $unitRendererPath -Raw -Encoding UTF8
|
||||||
|
|
||||||
|
foreach ($needle in @(
|
||||||
|
'public void InstantUpdateUnitImageOnly()',
|
||||||
|
'RenderUpdateUnitImage();'
|
||||||
|
)) {
|
||||||
|
if (-not $unitRendererText.Contains($needle)) {
|
||||||
|
throw "Move transform visual-refresh guardrail failed: UnitRenderer missing '$needle'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($needle in @(
|
||||||
|
'Data.UnitRenderer?.InstantUpdateUnitImageOnly();',
|
||||||
|
'Data.UnitRenderer.AnimManager.EnqueueAnim(UnitAtomAnimType.Move,animData);',
|
||||||
|
'Data.UnitRenderer?.InstantUpdateUnit(true);'
|
||||||
|
)) {
|
||||||
|
if (-not $fragmentMoveText.Contains($needle)) {
|
||||||
|
throw "Move transform visual-refresh guardrail failed: FragmentMove missing '$needle'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$startRefresh = $fragmentMoveText.IndexOf('Data.UnitRenderer?.InstantUpdateUnitImageOnly();')
|
||||||
|
$moveAnim = $fragmentMoveText.IndexOf('Data.UnitRenderer.AnimManager.EnqueueAnim(UnitAtomAnimType.Move,animData);')
|
||||||
|
if ($startRefresh -lt 0 -or $moveAnim -lt 0 -or $startRefresh -gt $moveAnim) {
|
||||||
|
throw "Move transform visual-refresh guardrail failed: FragmentMove must refresh transformed unit image before starting the move animation."
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Move transform visual-refresh guardrail passed."
|
||||||
@ -71,6 +71,7 @@ namespace TH1_Anim.Fragments
|
|||||||
{
|
{
|
||||||
_step1_move = true;
|
_step1_move = true;
|
||||||
if (Data.OriginGrid == null || Data.TargetGrid == null) return;
|
if (Data.OriginGrid == null || Data.TargetGrid == null) return;
|
||||||
|
Data.UnitRenderer?.InstantUpdateUnitImageOnly();
|
||||||
var animData = UnitAtomAnimDataFactory.Create(UnitAtomAnimType.Move,Data.OriginGrid.Pos.V2(), Data.TargetGrid.Pos.V2(),Data.Path);
|
var animData = UnitAtomAnimDataFactory.Create(UnitAtomAnimType.Move,Data.OriginGrid.Pos.V2(), Data.TargetGrid.Pos.V2(),Data.Path);
|
||||||
Data.UnitRenderer.AnimManager.EnqueueAnim(UnitAtomAnimType.Move,animData);
|
Data.UnitRenderer.AnimManager.EnqueueAnim(UnitAtomAnimType.Move,animData);
|
||||||
//刷新目标城市的状态(比如离开了着火的占领城市)
|
//刷新目标城市的状态(比如离开了着火的占领城市)
|
||||||
@ -184,4 +185,4 @@ namespace TH1_Anim.Fragments
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -418,6 +418,12 @@ namespace TH1_Renderer
|
|||||||
return _unitData != null && _unitData.InMainSight();
|
return _unitData != null && _unitData.InMainSight();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InstantUpdateUnitImageOnly()
|
||||||
|
{
|
||||||
|
if (_unitMono == null || !TryRefreshUnitRefs()) return;
|
||||||
|
RenderUpdateUnitImage();
|
||||||
|
}
|
||||||
|
|
||||||
//瞬间更新unit的 die的情况
|
//瞬间更新unit的 die的情况
|
||||||
public bool InstantUpdateTryDie()
|
public bool InstantUpdateTryDie()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user