Enemy.tsx: useScaledTick の props クロージャ依存を worldRef 直参照に直し、target-lock の workaround を撤去する
Description
背景
apps/client/src/scenes/Battle/Enemy.tsx の useScaledTick (~line 192 付近) は
enemy props のクロージャに依存している。EnemyLayer は memo されており、Enemy
自身も memo + forceRender で「離散変更時のみ再描画」設計のため、本来は毎フレーム
再描画されない。
しかし worldRef.current.enemies 側で enemy オブジェクトが毎フレーム置換される
構造のため、再描画されない限り Enemy.tsx の useScaledTick は古い enemy 参照を
握ったままになり、position 等が更新されず敵が**「足踏み + 0.5秒ごとにワープ」**
する。
偶然成立していたカラクリ
battle-logic.ts の handleEnemyClick ラッパが useTargetLock 由来の
rawHandleEnemyClick を deps に取り、その rawHandleEnemyClick が isEnemyInRange
(deps: [characters, alta] → 毎フレーム不安定) を deps にとっていたため、
handleEnemyClick → wrapper → EnemyLayer の props onEnemyClick が毎フレーム新
identity になり、EnemyLayer の memo が毎フレーム破れて再描画 → Enemy も再描画
→ クロージャ更新、という偶然のチェーンで正常動作していた。
現状の workaround (15e45752)
apps/client/src/hooks/battle-logic/target-lock.ts の handleEnemyClick deps に
未使用の _isEnemyInRange を残し、identity を毎フレーム不安定にしている。
ファイル先頭・宣言直上にコメントで意図を強調 (88e7547b)。
// useTargetLock 内
const _isEnemyInRange = useCallback(/* ... */, [characters, alta]);
const handleEnemyClick = useCallback(
/* 本体では _isEnemyInRange を使わない */,
[_isEnemyInRange, setTargetLockEnemyId] // ← workaround
);
本来の修正方針
Enemy.tsx の useScaledTick 内で enemy を props クロージャでなく、
worldRef.current.enemies から instanceId 引きで毎フレーム取り直す形に
直す。
// 概念コード
useScaledTick(() => {
const current = worldRef.current.enemies.find(e => e.instanceId === enemy.instanceId);
if (!current) return;
// current.position 等を読む
});
そうすれば EnemyLayer の memo が本来の最適化として機能し、
- target-lock.ts の _isEnemyInRange workaround を撤去できる
- 毎フレーム VDOM diff を回避できて性能改善も見込める
影響範囲・優先度
-
優先度: 中。現状 workaround で機能はしているが、
-
lint/自動修正で _isEnemyInRange が消えると即回帰
-
characters / alta が参照変化しないフレームで identity が安定する条件が
-
成立すると、workaround の有効性自体が崩れる可能性
- Character / Projectile / Alta など他の per-frame コンポーネントにも同種の
クロージャ問題が潜在している可能性 (要監査)
関連
- workaround 適用コミット: 15e45752
- workaround 強化コミット: 88e7547b (PR レビュー指摘対応)
- 関連ファイル:
-
apps/client/src/scenes/Battle/Enemy.tsx (本修正対象)
-
apps/client/src/hooks/battle-logic/target-lock.ts (workaround 撤去対象)
-
apps/client/src/scenes/Battle/EnemyLayer.tsx (memo の挙動確認用)
完了条件
- [ ] Enemy.tsx の useScaledTick が worldRef 直参照に変更されている
- [ ] target-lock.ts の _isEnemyInRange が削除されている (or 純粋に使う方向で書き直されている)
- [ ] 「足踏み + ワープ」回帰がないことを実機確認
- [ ] Character / Projectile / Alta など他の per-frame コンポーネントも同種問題がないか監査