# WORM KNIGHT — Engineering Notes

> 전찬우 · Team Lead / Core Architecture / Integration  
> Unity 6 (`6000.3.15f1`) · C# · URP  
> 공식 프로젝트 기간 `2026.06.17–07.07` · **17일 집중 개발**

## 1. 이 게임은 무엇인가

WORM KNIGHT는 중앙의 알 형태 넥서스를 지키는 지렁이형 3D 디펜스 게임이다. 플레이어는 자동 전진하는 머리의 방향을 조작하고, 몸통에 붙은 무기 세그먼트가 주변 몬스터를 자동으로 공격한다.

한 문장으로 줄이면 다음과 같다.

> 길어질수록 강해지고, 길어질수록 위험해진다.

몸통은 동시에 세 가지 역할을 맡는다.

1. 무기: 세그먼트마다 공격 또는 지원 기능을 가진다.
2. 성장: 보상 선택으로 새 세그먼트를 붙이거나 기존 세그먼트를 강화한다.
3. 리스크: 긴 몸이 머리와 충돌하면 충돌 지점 이후가 잘려 나간다.

잘린 세그먼트는 삭제되지 않는다. 물리 체인으로 필드에 남고, 플레이어가 현재 꼬리 끝을 분리 체인의 접속 영역에 가져오면 다시 결합한다. 손실과 회복이 모두 플레이 공간에서 일어난다.

## 2. 17일에 대응한 개발 방식

짧은 기간에 여러 담당자가 동시에 콘텐츠를 만들어야 했다. 해결 방법은 모든 기능을 코어 담당자가 직접 만드는 것이 아니라, 먼저 다음 경계를 고정하는 것이었다.

- 섹션별 소유권: Core, Monster, Level, Segment 등 내부 구현 범위를 나눴다.
- 공용 데이터 계약: `RewardData`, `GrowthStatData`, `CoreStatData`처럼 전달값을 먼저 정했다.
- 단일 진입점: 런타임 능력치는 `CoreStatProvider`로 합류한다.
- 등록 순서: Definition → Profile → Prefab → Catalog → Runtime 흐름을 사용했다.
- 관찰 도구: 밸런스 표와 런타임 DPS 계측으로 통합 뒤의 결과를 비교했다.

이 구조의 목표는 “서로의 코드를 모르게 하기”가 아니라 “서로의 내부 구현을 기다리지 않아도 연결할 수 있게 하기”였다.

### 역할 경계

개인 포트폴리오에서 주장하는 핵심 범위는 다음과 같다.

- 팀장 및 코어 구조 책임
- 컨보이 이동과 제어 모드
- 거리 기반 경로 기록과 세그먼트 추종
- 세그먼트 추가·연결, 자기 충돌, 절단, 분리 물리, 재결합
- 넥서스와 StageScene 구조
- 공용 API와 데이터 계약
- 타이틀·메타 진행과 런 시작/결과 연결
- 최종 통합과 QA 기준 정리

다음은 팀 결과로 구분한다.

- 몬스터 개별 구현
- 레벨 및 카드 시스템의 개별 구현
- 팀원이 만든 개별 세그먼트와 콘텐츠
- 팀 전체 저장소의 파일 수와 Catalog 등록량

## 3. 전체 런타임 구조

```text
Monster ---------------- RewardData -----------┐
Level / Card ------------ GrowthStatData ------┤
Title / Meta ------------ RunStartBonusData ---┤
                                                v
                                      CoreStatProvider
                                                |
                       +------------------------+--------------------+
                       v                        v                    v
                    Convoy                  Segments              HUD/Result
              movement/cut/rejoin      damage/fire interval     state display
```

코어는 팀 기능의 모든 내부 로직을 소유하지 않는다. 대신 다음 세 가지를 소유한다.

1. 공개 데이터의 모양
2. 데이터가 들어오는 지점
3. 적용 뒤 상태가 다시 나가는 방식

### 보상 흐름

```text
Enemy death
  → RewardData
  → RewardGateway.SubmitReward
  → CoreStatProvider.ApplyReward
  → experience / run gold / run diamond
  → StatsChanged
  → HUD
```

근거 파일:

- `Assets/Scripts/Shared/Contracts/RewardData.cs`
- `Assets/Scripts/Core/RewardGateway.cs`
- `Assets/Scripts/Core/CoreStatProvider.cs:188`

### 성장 흐름

```text
Card selection
  → GrowthStatData
  → CoreStatProvider.ApplyGrowth
  → common stat / add segment / upgrade segment
  → StatsChanged
```

근거 파일:

- `Assets/Scripts/Shared/Contracts/GrowthStatData.cs`
- `Assets/Scripts/Core/CoreStatProvider.cs:145`

## 4. 컨보이 이동

### 4.1 한 프레임의 적용 순서

`ConvoyController.Update`는 다음 순서를 고정한다.

1. 입력 수집
2. 수동 입력이 있으면 자동 궤도 취소
3. 현재 조작 모드에 따른 조향
4. 몬스터가 요청한 넉백 소비
5. 슬로우 배율을 반영한 기본 전진량 계산
6. 넉백 이동량 결합
7. 지면 높이에 스냅
8. 몬스터 장애물과 겹치지 않도록 위치 보정
9. 최종 Transform 위치 확정
10. 경로 샘플과 세그먼트 갱신

근거 파일: `Assets/Scripts/Convoy/ConvoyController.cs:209–269`

중요한 선택은 몬스터가 컨보이 위치를 직접 쓰지 않는다는 점이다. 몬스터 시스템은 넉백과 위치 보정 요청을 API에 전달하고, 최종 이동 적용은 컨보이 컨트롤러가 맡는다.

### 4.2 거리 기반 경로

세그먼트는 바로 앞 Transform을 단순 추적하지 않는다. 머리가 지나온 위치를 일정 거리마다 기록하고, 각 세그먼트는 머리 뒤에서 자신에게 필요한 누적 거리를 조회한다.

이 방식의 장점:

- 프레임 속도보다 월드 거리가 간격 기준이 된다.
- 긴 체인에서도 앞 조각의 작은 진동이 뒤로 계속 증폭되는 현상을 줄인다.
- 세그먼트마다 다른 물리 길이를 누적해 소켓 간격을 계산할 수 있다.

외부 보정이나 큰 넉백으로 경로 점 사이 거리가 비정상적으로 커지면 한 점을 그대로 추가하지 않는다. `MaxRepairedPathSamples` 상한 안에서 직선 중간 점으로 다시 샘플링한다.

근거 파일: `Assets/Scripts/Convoy/ConvoyController.Path.cs:34–76`

### 4.3 소켓 기반 연결

고정된 `SegmentSpacing`만 사용하면 모델 길이가 다른 세그먼트 사이에 틈이나 겹침이 생긴다. 런타임은 `FrontJoint`와 `RearJoint` 위치를 우선 사용하고, 소켓이 없을 때만 기본 간격으로 폴백한다.

따라서 경로상의 “몇 번째 점”이 아니라 “머리에서 실제로 얼마나 뒤에 있어야 하는가”가 세그먼트마다 달라질 수 있다.

### 4.4 자동 궤도

자동 궤도 반경은 고정값이 아니다.

```text
tailSafeRadius  = bufferedChainLength / (2π)
nexusSafeRadius = nexusColliderRadius + clearance
orbitRadius     = max(minRadius, tailSafeRadius, nexusSafeRadius) + comfortBonus
```

근거 파일: `Assets/Scripts/Convoy/ConvoyController.AutoOrbit.cs:176–184`

세그먼트 수가 늘어 목표 반경이 임계값 이상 달라지면 `Drive`를 유지하지 않고 `Approach` 상태로 돌아가 새 궤도에 진입한다. WASD나 스틱 입력이 들어오면 자동 상태를 즉시 취소한다.

## 5. 절단과 재결합

### 5.1 자기 충돌 판정

머리와 부착 세그먼트의 XZ 평면 거리를 검사한다. 스타터 보호 구간은 검사에서 제외하고, 충돌 지점 이후의 세그먼트를 분리 대상으로 삼는다.

근거 파일: `Assets/Scripts/Convoy/ConvoyController.Tail.cs:31–84`

### 5.2 부착 체인에서 분리 체인으로

절단 시 수행되는 핵심 작업:

1. 분리 루트 `DetachedTailGroup` 생성
2. 대상 세그먼트를 월드 좌표 유지 상태로 재부모화
3. 충돌체와 Rigidbody 준비
4. 부착 목록, 지면 검사 목록, 세그먼트 런타임 목록에서 같은 범위 제거
5. 분리 그룹 등록
6. 충돌 중심에서 바깥쪽으로 burst 적용
7. 연속 절단 방지 쿨다운 시작
8. 세그먼트 수 변경 알림

### 5.3 물리 체인

각 분리 세그먼트는 Rigidbody를 갖고, 두 번째 조각부터 앞 조각과 ConfigurableJoint로 연결된다.

- x/y/z 선형 축: Locked
- x/y/z 각도: Limited
- 앵커: FrontSocket ↔ 이전 RearSocket
- Projection: PositionAndRotation
- 인접 충돌: 비활성
- Break force/torque: Infinity

근거 파일: `Assets/Scripts/Convoy/ConvoyController.Tail.cs:139–202`

붙은 상태에 경로 기반 Transform을, 잘린 상태에 물리를 사용한 이유는 서로 다른 목표 때문이다.

- 부착 상태: 조작 응답, 간격 안정성, 재현성
- 분리 상태: 충격, 붕괴, 손실의 가시성

### 5.4 안착과 재결합

재결합 영역은 분리 즉시 나타나지 않는다.

- 모든 Rigidbody의 선형 속도가 기준 이하
- 모든 Rigidbody의 각속도가 기준 이하
- 이 상태가 `DetachedTailSettleTime` 이상 지속
- 분리 후 `DetachedTailMinRejoinAge` 이상 경과

조건을 만족하면 분리 머리 쪽에 원형 영역을 표시한다. 플레이어 머리가 아니라 현재 부착 체인의 꼬리 끝이 이 영역에 들어와야 한다.

재결합 시 물리 조인트, Rigidbody, 분리용 Collider를 정리하고 다시 부착 리스트와 세그먼트 런타임에 등록한다.

근거 파일: `Assets/Scripts/Convoy/ConvoyController.Tail.cs:279–355`

## 6. 데이터 기반 세그먼트 생산

```text
SegmentDefinition
  → SegmentLevelDefinition
  → SegmentAttackProfile / support behavior
  → level prefab
  → SegmentCatalog
  → Convoy runtime
```

이 구조에서 새 콘텐츠가 공유해야 하는 것은 코어 내부가 아니라 등록 계약이다.

### 현재 Catalog 스냅샷

2026.09.11 현재 저장소 YAML 직접 집계:

| 항목 | 수 | 근거 |
|---|---:|---|
| 등록 세그먼트 | 15 | `Assets/Segments/_Catalog/SegmentCatalog.asset` |
| 스타터 지렁이 | 5 | `Assets/Segments/_Catalog/StarterCatalog.asset` |
| 공용 성장 카드 | 8 | `Assets/Resources/LevelCard/StatUpgradeCatalog.asset` |
| 무기 강화 참조 | 36 | `Assets/Segments/_Catalog/CardSegment/WeaponEnhancementCatalog.asset` |

이 수치는 팀 프로젝트 전체 결과이며 개인 단독 제작량이 아니다.

### 공통 공격 런타임

`GenericSegmentWeapon` 계열은 Profile에서 다음과 같은 차이를 읽는다.

- 이동 방식: 직선, 포물선, 레이저, 연쇄 등
- 충돌 방식: 단일, 범위
- 발사 수, 지연, 묶음 발사
- 사거리와 대상 우선순위
- 연쇄 깊이, 거리, 감쇠
- 레이저 지속시간과 tick 간격

공통 피해 계산은 다음 층을 합성한다.

```text
AttackProfile base
  → run-start base bonus
  → weapon enhancement
  → common card base bonus
  → core flat bonus
  → segment upgrade
  → support runtime multiplier
  → DamageData
```

근거 파일: `Assets/Scripts/Segments/Attacks/GenericSegmentWeapon.cs:133–186`

## 7. 밸런스와 계측

긴 컨보이는 동시에 많은 무기가 발사되므로 화면만 보고 기여도를 판단하기 어렵다. 런타임 DPS 계측기는 다음 두 구간을 분리한다.

- Total DPS: 측정 시작 이후 누적
- Wave DPS: 현재 웨이브 구간

각 세그먼트 소스별 절대 피해와 전체 비중을 함께 본다. 자동 궤도로 조향 변수를 줄이고 같은 웨이브 조건에서 비교하면 반복 측정이 쉬워진다.

관련 제작 도구:

- `Assets/Editor/SegmentBalanceTableWindowV02.cs`
- `Assets/Editor/CardBalanceTableWindow.cs`
- `Assets/Scripts/Debug`의 런타임 계측 코드

### 정수 보상의 소수 나머지

정수 골드 1개에 10% 보너스를 매번 적용하고 내림하면 매 지급 결과는 계속 1이 될 수 있다. `CoreStatProvider`는 소수 나머지를 다음 지급으로 넘긴다.

```text
raw = base × multiplier + previousRemainder
result = floor(raw)
nextRemainder = raw - result
```

근거 파일: `Assets/Scripts/Core/CoreStatProvider.cs:809–819`

이 방식은 정수 UI를 유지하면서 작은 드롭이 반복될 때 장기 기대값을 보존한다.

## 8. 절차적 초원

`MeadowTerrainRuntimeGenerator`는 현재 `Assets/Scenes/StageScene.unity`에 직렬화되어 있다. 개발 테스트 씬에도 동일한 컴포넌트 연결이 있다.

### 생성 순서

1. 이전 생성 결과와 씬 표시 정리
2. 로딩 오버레이 표시 후 한 프레임 양보
3. 넥서스 중심 결정
4. Catmull–Rom 기반 Trail network 생성
5. TerrainData 템플릿 경로 시도
6. 실패 시 런타임 메쉬 경로 사용
7. 정점 행을 프레임별 묶음으로 생성
8. 삼각형 행을 프레임별 묶음으로 생성
9. 메쉬와 충돌체 구성
10. 재질 가중치와 장식 배치

근거 파일: `Assets/Scripts/World/Map/MeadowTerrainRuntimeGenerator.cs:368–468`

### 높이

시드 기반 Perlin/fBM 노이즈에 외곽 융기를 더해 전장 높이를 만든다. 중앙과 길 주변은 플레이 가독성을 위해 별도 영향을 받는다.

### 길과 재질

넥서스 공터와 각 Trail 경계까지의 signed distance 중 가장 가까운 값을 사용한다.

```text
distance <= 0              : dirt 100%
0 < distance <= midWidth   : dirt → middle
distance > midWidth        : middle → grass
```

근거 파일: `Assets/Scripts/World/Map/MeadowTerrainRuntimeGenerator.cs:3251–3300`

### 성능 대응

- 생성 루프를 여러 프레임에 분할
- alphamap 전체 대신 영향 범위의 Rect만 편집
- 장식 후보 수와 샘플 수에 상한 적용
- 반복 식생은 GPU instancing 경로 사용
- 결정적 seed로 같은 입력의 재현성 유지

이번 문서 제작에서는 씬 바인딩과 코드를 확인했지만 실제 생성 시간과 타깃 기기 프레임을 새로 측정하지 않았다.

## 9. 런과 메타 진행

```text
Title
  → worm / upgrade / map selection
  → RunStartBonusData
  → RunLoadoutContext
  → Stage
  → CurrentGold / CurrentRunDiamond
  → RunResultData
  → RunResultContext
  → MetaProgressionManager
  → PlayerPrefs JSON
```

### 런 시작

`MetaProgressionManager.BuildStartBonus`가 지렁이 보너스와 영구 업그레이드 보너스를 합성한다. 결과를 `RunLoadoutContext`에 넣고 Stage에서 코어가 소비한다.

근거 파일: `Assets/Scripts/Core/Meta/MetaProgressionManager.cs:116–126`

### 런 경제와 메타 경제

- Gold: 현재 런의 선택을 위한 재화
- Diamond: 결과 정산 뒤 다음 런의 해금과 강화에 쓰는 메타 재화

### 저장

메타 진행은 `SaveVersion = 1`을 가진 `SaveData`로 직렬화한다.

- 저장 전 NormalizeState
- PlayerPrefs에는 JSON 문자열 저장
- 로드 시 빈 문자열, JSON 예외, null, 잘못된 버전 거부
- 적용 뒤 다시 NormalizeState
- 변경 시 저장 여부를 `SaveOnChange`로 제어

근거 파일: `Assets/Scripts/Core/Meta/MetaProgressionManager.cs:264–305`

PlayerPrefs는 이 규모의 로컬 프로토타입에는 단순하지만, 제품 단계에서 보안, 원자적 파일 교체, 대규모 마이그레이션이 필요하면 별도 저장 계층으로 옮겨야 한다.

## 10. 주요 설계 선택과 대가

### 경로 추종 + 분리 물리

- 선택: 부착 상태에는 결정적 경로 추종, 분리 상태에는 연결체 물리
- 얻은 것: 조작 응답과 붕괴 표현을 동시에 확보
- 대가: 상태 전환 시 컴포넌트와 목록의 수명주기 관리

### 단일 계약 진입점 + 폴백

- 선택: 정상 경로는 RewardGateway, 누락 시 CoreStatProvider 직접 전달과 Warning
- 얻은 것: 통합 실수에도 사용자 보상 손실 방지
- 대가: 폴백이 표준 경로로 굳지 않도록 경고와 QA 필요

### 데이터 생산 + 검증 도구

- 선택: 새 콘텐츠의 차이는 Profile과 Catalog로 이동
- 얻은 것: 팀원이 같은 생산 순서를 반복 가능
- 대가: 참조 누락과 중복 ID를 검사하는 도구 유지 필요

### 프레임 분할 절차 생성

- 선택: 코루틴으로 정점과 삼각형 작업 분할
- 얻은 것: 로딩 화면 응답 유지
- 대가: 총 생성 시간 자체는 별도 최적화와 계측 필요

## 11. 현재 검증 범위

이번 포트폴리오 제작에서 새로 확인한 것:

- `ProjectWormKnight` 현재 실제 코드
- Unity 버전 `6000.3.15f1`
- `Assets` 하위 C# 스크립트 299개
- Convoy 이동, 경로 복원, 자동 궤도, 절단/물리/재결합 구현
- 보상·성장·코어 데이터 계약
- 메타 시작 데이터와 JSON 저장 구현
- StageScene의 절차 초원 생성기 바인딩
- Catalog 직렬화 목록 수
- 개인 폴더의 실제 플레이 GIF와 발표 슬라이드
- 역할 문서의 개인 책임과 팀 책임

이번 작업에서 새로 실행하지 않은 것:

- Unity 컴파일
- PlayMode 회귀 테스트
- 자동 테스트 스위트
- 타깃 기기 성능 프로파일링
- 장시간 플레이와 저장 마이그레이션

따라서 이 문서는 현재 코드 구조와 기존 실행 근거를 설명하는 기술 포트폴리오이며, 새 빌드의 출시 승인 보고서는 아니다.

## 12. 회고

가장 중요한 결과는 기능 수가 아니다. 17일이라는 제약 안에서 다음을 하나의 구조로 묶었다는 점이다.

- 몸의 길이를 화력과 위험으로 동시에 사용한 게임 규칙
- 안정적 조작과 물리적 손실을 함께 만드는 하이브리드 체인
- 담당자가 병렬로 움직일 수 있는 데이터 계약
- 콘텐츠를 반복 생산할 Definition/Profile/Catalog 흐름
- 결과를 관찰하고 다시 조정할 밸런스 도구
- 타이틀부터 런, 결과, 다음 성장까지 이어지는 실행 루프

다음 단계의 우선순위는 기능 추가보다 재현 가능한 런타임 검증이다. 고정 시드 회귀, 긴 체인 스트레스, 절단·재결합 반복, 장시간 메모리, 저장 버전 마이그레이션을 자동 시나리오로 만드는 것이 가장 큰 개선점이다.
