---
id: wiki-2026-0508-2026-04-26-skybound-stage-minibo
title: Skybound — Stage Miniboss Pattern Differentiation
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Miniboss Patterns, Stage-Specific Miniboss]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [skybound, gamedev, miniboss-design, attack-pattern, stage-identity]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: typescript
  framework: phaser3
---

# Skybound — Stage Miniboss Pattern Differentiation

## 매 한 줄
> **"매 8 stage 의 miniboss 가 'pattern signature' 의 distinct 일 때 stage identity 의 안 collapse"**. 매 Skybound 의 4-26 작업은 stage 별 miniboss 의 attack pattern (radial / sweep / charge / split / chain / mirror / summoner / null-zone) 의 1:1 mapping 정립.

## 매 핵심

### 매 Stage-to-Pattern Mapping
1. **Stage 1**: Radial — 8-direction burst, slow turn rate.
2. **Stage 2**: Sweep — laser arc, telegraphed.
3. **Stage 3**: Charge — dash line + recover stun.
4. **Stage 4**: Split — projectile fragments mid-flight.
5. **Stage 5**: Chain — bounce projectiles between mobs.
6. **Stage 6**: Mirror — clones player movement (lagged).
7. **Stage 7**: Summoner — spawns minions in waves.
8. **Stage 8**: Null-zone — area denial circles, forces movement.

### 매 Differentiation Rules
- 매 pattern 의 unique tell (visual + audio).
- 매 pattern 별 counter-play (positioning / timing / kiting).
- 매 stage progression 의 pattern complexity escalation.

### 매 Phase Variants
- 매 miniboss HP < 50% 시 pattern 의 modifier 추가 (faster, more, larger).
- 매 enrage (HP < 20%) 의 mixed pattern.

## 💻 패턴

### Pattern Definition
```typescript
type AttackPattern =
  | { kind: 'radial'; bullets: number; bulletSpeed: number; rotateRate: number }
  | { kind: 'sweep'; arcDeg: number; durationMs: number; telegraphMs: number }
  | { kind: 'charge'; chargeSpeed: number; recoverMs: number }
  | { kind: 'split'; firstStage: { count: number; speed: number }; splitAt: number; splitInto: number }
  | { kind: 'chain'; bounces: number; targets: 'enemies' | 'walls' }
  | { kind: 'mirror'; lagMs: number }
  | { kind: 'summoner'; minionId: string; perWave: number; everyMs: number }
  | { kind: 'null-zone'; radius: number; lifespanMs: number; spawnEveryMs: number };

const STAGE_PATTERNS: Record<number, AttackPattern> = {
  1: { kind: 'radial', bullets: 8, bulletSpeed: 200, rotateRate: 0.5 },
  2: { kind: 'sweep', arcDeg: 120, durationMs: 1500, telegraphMs: 600 },
  3: { kind: 'charge', chargeSpeed: 600, recoverMs: 1200 },
  4: { kind: 'split', firstStage: { count: 3, speed: 240 }, splitAt: 0.5, splitInto: 5 },
  5: { kind: 'chain', bounces: 3, targets: 'enemies' },
  6: { kind: 'mirror', lagMs: 600 },
  7: { kind: 'summoner', minionId: 'drone-small', perWave: 4, everyMs: 4000 },
  8: { kind: 'null-zone', radius: 120, lifespanMs: 5000, spawnEveryMs: 2500 },
};
```

### Pattern Runner
```typescript
class MinibossController {
  pattern: AttackPattern;
  phase: 'p1' | 'enrage' = 'p1';

  update(dt: number, hp: number, maxHp: number) {
    if (hp / maxHp < 0.2) this.phase = 'enrage';
    const speedMul = this.phase === 'enrage' ? 1.6 : 1;
    this.runPattern(this.pattern, dt, speedMul);
  }

  private runPattern(p: AttackPattern, dt: number, mul: number) {
    switch (p.kind) {
      case 'radial':  return this.runRadial(p, dt, mul);
      case 'sweep':   return this.runSweep(p, dt, mul);
      case 'charge':  return this.runCharge(p, dt, mul);
      case 'split':   return this.runSplit(p, dt, mul);
      case 'chain':   return this.runChain(p, dt, mul);
      case 'mirror':  return this.runMirror(p, dt, mul);
      case 'summoner':return this.runSummoner(p, dt, mul);
      case 'null-zone': return this.runNullZone(p, dt, mul);
    }
  }
}
```

### Radial Volley
```typescript
private runRadial(p: AttackPattern & { kind: 'radial' }, dt: number, mul: number) {
  this.cooldown -= dt;
  if (this.cooldown > 0) return;
  this.cooldown = 1.2 / mul;
  for (let i = 0; i < p.bullets; i++) {
    const angle = (i / p.bullets) * Math.PI * 2 + this.aimAngle;
    spawnProjectile(this.pos, angle, p.bulletSpeed * mul);
  }
  this.aimAngle += p.rotateRate * dt;
}
```

### Mirror (lagged player clone)
```typescript
private runMirror(p: AttackPattern & { kind: 'mirror' }, _dt: number, _mul: number) {
  const samples = this.playerHistory.samplesOlderThan(p.lagMs);
  const target = samples.lastBefore(p.lagMs);
  if (target) {
    this.moveToward(target.pos, this.speed);
    if (this.fireCooldown <= 0) {
      spawnProjectileAt(this.pos, target.pos.angleFrom(this.pos), 320);
      this.fireCooldown = 0.8;
    }
  }
}
```

### Null-Zone Spawning
```typescript
private runNullZone(p: AttackPattern & { kind: 'null-zone' }, _dt: number, mul: number) {
  this.zoneCooldown -= _dt;
  if (this.zoneCooldown > 0) return;
  this.zoneCooldown = (p.spawnEveryMs / 1000) / mul;
  spawnNullZone({
    pos: this.playerPosOffsetRandom(200),
    radius: p.radius,
    lifespanMs: p.lifespanMs,
  });
}
```

## 매 결정 기준
| 상황 | Approach |
|---|---|
| Patterns feel samey | 1 stage = 1 signature pattern |
| Late-stage too easy | Enrage modifier (1.5-2x speed) |
| Pattern unreadable | Telegraph + audio sting |
| Mirror too punishing | Lag 600ms+ (player can outrun) |

**기본값**: 매 8-pattern enum, stage:pattern = 1:1, enrage 1.6x speed mul.

## 🔗 Graph
- Adjacent: [[API 응답 및 상태 모델링 (State Modeling and API Responses)|Discriminated-Unions]]

## 🤖 LLM 활용
**언제**: pattern enumeration, counter-play brainstorm, telegraph copy.
**언제 X**: hitbox tuning, frame-perfect timing.

## ❌ 안티패턴
- **All bosses radial**: identity collapse — 1:1 pattern.
- **No enrage phase**: predictable end — 20% HP threshold modifier.
- **Mirror lag too short**: unbeatable — 600ms+ lag.
- **Telegraph 누락**: unfair — visual+audio always.

## 🧪 검증 / 중복
- Verified (Touhou pattern taxonomy, Hades boss design).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — 8-pattern stage:miniboss mapping 정리 |
