--- id: wiki-2026-0508-skybound-enemy-orientation-fix title: Skybound Enemy Orientation Fix category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Enemy Facing Bug, Sprite Flip Fix, Orientation Update Loop] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [skybound, combat-ai, sprite, orientation, bugfix] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: skybound-engine --- # Skybound Enemy Orientation Fix ## 매 한 줄 > **"매 enemy facing 의 source-of-truth 는 velocity, not last-input"**. Skybound 의 enemy 가 stationary 시 wrong direction face 하던 bug — orientation 을 velocity-derived 로 unify 하고, idle fallback 으로 player-facing 적용. 매 2D action game 의 standard fix. ## 매 핵심 ### 매 문제 - Enemy 가 path target 에 도달 후 멈추면 last-known facing 유지. - Player 가 옆에 다가가도 그대로 → unrealistic + hitbox mismatch. - Sprite flip 이 movement input 에만 react. ### 매 fix axes - **Velocity-driven facing**: |vx| > epsilon 시 sign(vx) 로 flip. - **Idle fallback to player**: |v| < eps 일 때 face nearest threat. - **Hysteresis**: rapid flip 방지 (oscillation guard). - **Animation sync**: orientation change 시 anim state machine 의 transition. ### 매 응용 1. Skybound enemy AI. 2. 2D platformer enemy facing. 3. Top-down shooter NPC orientation. ## 💻 패턴 ### Velocity-driven facing ```typescript const FACING_EPS = 0.05; function updateFacing(enemy: Enemy, dt: number): void { if (Math.abs(enemy.velocity.x) > FACING_EPS) { enemy.facing = enemy.velocity.x > 0 ? "right" : "left"; enemy.idleTime = 0; return; } enemy.idleTime += dt; if (enemy.idleTime > 0.2) { const player = enemy.world.player; enemy.facing = player.position.x >= enemy.position.x ? "right" : "left"; } } ``` ### Hysteresis guard (anti-flip) ```typescript const FLIP_HYSTERESIS_MS = 120; function setFacing(enemy: Enemy, target: Facing, now: number): void { if (enemy.facing === target) return; if (now - enemy.lastFlipMs < FLIP_HYSTERESIS_MS) return; enemy.facing = target; enemy.lastFlipMs = now; enemy.sprite.flipX = target === "left"; } ``` ### Animation state sync ```typescript function syncAnimToFacing(enemy: Enemy): void { const dir = enemy.facing; const stateName = `${enemy.animState}_${dir}`; if (enemy.sprite.currentAnim !== stateName) { enemy.sprite.play(stateName, { preserveFrame: true }); } } ``` ### 8-direction variant ```typescript type Dir8 = "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW"; function vecToDir8(v: Vec2): Dir8 { const angle = Math.atan2(v.y, v.x); // -π..π const idx = Math.round(((angle + Math.PI) / (Math.PI / 4))) % 8; return (["W", "SW", "S", "SE", "E", "NE", "N", "NW"] as Dir8[])[idx]; } ``` ### Hitbox mirror with facing ```typescript function getHitboxWorld(enemy: Enemy): Rect { const local = enemy.attackHitbox; const x = enemy.facing === "right" ? enemy.position.x + local.x : enemy.position.x - local.x - local.w; return { x, y: enemy.position.y + local.y, w: local.w, h: local.h }; } ``` ### Test: facing follows velocity ```typescript test("facing follows velocity sign", () => { const e = makeEnemy({ position: { x: 0, y: 0 } }); e.velocity = { x: 2, y: 0 }; updateFacing(e, 0.016); expect(e.facing).toBe("right"); e.velocity = { x: -2, y: 0 }; updateFacing(e, 0.016); expect(e.facing).toBe("left"); }); ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Pure 2D side-scroller | velocity sign(vx) 만 사용 | | Top-down 4/8-dir | vecToDir8 with hysteresis | | Stationary turret | always face nearest threat | | Flying enemy | facing = velocity dir, decoupled from gravity | **기본값**: velocity-driven + idle fallback + hysteresis + anim sync. ## 🔗 Graph - 응용: [[Skybound Knowledge Hub]] ## 🤖 LLM 활용 **언제**: 2D action game enemy facing bug, hitbox mirror, anim sync. **언제 X**: 3D character (use root motion + IK), turn-based grid. ## ❌ 안티패턴 - **Last-input facing**: stationary 시 stale. - **No hysteresis**: zigzag motion 에서 sprite flicker. - **Hitbox not mirroring**: visual ↔ collision mismatch. - **Anim transition snap**: facing change 시 frame reset → ugly. ## 🧪 검증 / 중복 - Verified (Skybound bugfix 2026-04 + 2D game dev standard practice). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — FULL spec rewrite with orientation patterns |