05 봇 AI - kyhsa93/karda GitHub Wiki
05. 봇 AI
5.1 병력 규모 (D4 대규모 기본)
규모를 지키는 설계: 분대 = sim 유닛 1개
- 08장 8.7 예산의 동시 유닛 ≤ 150은 sim 유닛(배열
world.units의 항목) 기준이다. 보병 분대를 유닛 하나로 두고 분대원은 그 유닛 안의 슬롯(분대 중심에서의 오프셋)과 개인 체력 목록으로 표현하면, 인형 수가 늘어도 sim 유닛 수는 그대로다.
- 분대원은 자기 위치를 따로 적분하지 않는다: 위치 = 분대 위치 + 대형 슬롯 오프셋(엄폐 슬롯에 들어가면 슬롯마다 지형·엄폐물에 맞춘 오프셋) — 계산은 분대 명령이 바뀔 때와 1Hz. 인형의 걸음·자세는 렌더가 보간한다.
- 개인 판정이 필요한 곳은 플레이어가 관여할 때뿐이다: 플레이어의 탄이 분대원 캡슐에 맞는지, 플레이어를 쏘는 분대원이 누구인지. 봇 대 봇은 분대 단위 통계다.
대규모 편제 (진영당, 대칭)
| 구성 |
수 |
인형 |
sim 유닛 |
| 소총 분대(5인) |
10 |
50 |
10 |
| 대전차 분대(5인) |
4 |
20 |
4 |
| 기관총 분대(5인) |
3 |
15 |
3 |
| 대공조(3인) |
2 |
6 |
2 |
| 저격조(2인) |
2 |
4 |
2 |
| 전차 |
4 |
— |
4 |
| 장갑차 |
4 |
— |
4 |
| 경차량 |
3 |
— |
3 |
| 수송 트럭 |
2 |
— |
2 |
| 방공 차량 |
2 |
— |
2 |
| 공격 헬기 |
2 |
— |
2 |
| 수송 헬기 |
2 |
— |
2 |
| 전투기 |
2 |
— |
2 |
| 본진 고정 대공(포 2 + SAM 1) |
3 |
— |
3 |
| 진영 합계 |
|
95 |
45 |
- 양 진영 합계: 인형 190, sim 유닛 90. 플레이어 가치 보정으로 플레이어 진영의 소총 분대를 2개 줄인다(추정, 하니스 조정 손잡이
rosterScale.playerSide) → 인형 180 + 플레이어, sim 유닛 88.
- sim 유닛 최대치: 편제 88 + 파도 스폰 겹침 ≤ 16 + 은퇴 전 잔해 ≤ 30(사망 90초 뒤 은퇴, 기술 설계 9.4) = ≤ 134 ≤ 150 → 08장 예산 유지.
- 비교: 배틀필드 대규모 점령전은 64명 + 탈것 약 20대(참고치). 여기는 인형 180 + 탈것 44대 — 인형 수로 약 3배, D4 "대규모"에 맞는다.
- 표준 규모(설정에서 선택, 모바일 폴백 후보 — 추가 결정 A9): 대규모 × 0.6 → 진영당 분대 13·지상 탈것 9·항공 3(공격·수송·전투기 각 1)·고정 3, 인형 약 58.
- 빠른 점령전: 대규모 × 0.5 → 진영당 분대 10·지상 탈것 7·공격 헬기 1·전투기 1·고정 2.
- 구현 (B1-9,
content/battle/rosters.json·sim/battle/roster.ts·spawner.ts): 규모별 편제를 곱셈 대신 표로 명시한다(forces.large·forces.quick — 반올림 모호함 제거). 빠른 점령전 진영당: 소총 5·대전차 2·기관총 1·대공조 1·저격조 1(분대 10), 전차 2·장갑차 2·경차량 1·트럭 1·방공 차량 1(지상 탈것 7), 플레이어 진영은 소총 −1. 적 진영 편제 배율은 분류마다 반올림(보통 소총 5 → 쉬움 4·어려움 6). available 목록이 아직 없는 분류(B1은 항공 전부)를 뺀다. modes.json의 forces가 어느 표를 쓸지 고른다.
- 스폰: 경기 시작에 전체 편제, 이후 30초마다 파도. 분대는 교전·적 없는 아군 거점과 본진을 번갈아(거점 반경 0.8 안), 탈것은 본진 40~160m. 탈것 슬롯은 파괴 뒤 분류별 재생성 시간(전차 90초 등, 2.3 표)이 지나야 다음 파도에 나온다. 티켓 0이면 그 진영은 스폰 안 함, 살아 있는 유닛이 150이면 멈춤.
- 난이도: 봇이 플레이어를 쏠 때의 명중 배율(쉬움 0.6 / 보통 1.0 / 어려움 1.3)과 반응 시간 배율(1.4 / 1.0 / 0.8). 봇 대 봇 명중은 난이도와 무관(전황이 난이도로 흔들리지 않게). 대신 적 진영 편제 배율: 쉬움 0.85 / 보통 1.0 / 어려움 1.15 (추정).
5.2 지휘 구조 (3층)
[진영 지휘관] commander.ts — 5초마다 (연합 0.0s, VPA 2.5s 엇갈림)
│ 소대마다 명령: 공격 P / 방어 P / 예비 / 호위 / 항공 임무
▼
[소대] platoon.ts — 1Hz (보병 분대 2~3 + 탈것 0~1로 묶음)
│ 상태: 집결 → 이동(탑승) → 하차 → 공격 → 점령 유지·방어 → 후퇴·재편
│ 구성원에게 목표 위치(슬롯)
▼
[유닛] brain.ts(확장) — 표적 선택 1Hz(10개 묶음 분산), 사격 10Hz
movement.ts — 이동 120Hz(기존)
[분대원] 슬롯 오프셋·자세·개인 체력 (분대 유닛 안, 1Hz 갱신 + 렌더 보간)
진영 지휘관 — 거점 효용 점수
U(P) = 가치(P) × 상태(P) − 0.25 × 거리(km, 소대→P) − 0.2 × 알려진 적 세력(P) + 집중(P)
가치: 기본 1.0 / FARP 딸린 거점 1.5 / 맵 지정 핵심 거점 1.2
상태: 적 소유 1.0 / 중립 1.3 / 아군 소유·공격받는 중 1.6 / 아군 소유·안전 0.3
집중: 플레이어가 지정한 거점 +0.8 (플레이어 진영만, D5)
- 배정은 탐욕법(쌍을 U 순으로). 한 거점에 보내는 세력은
알려진 적 세력 × 1.5 + 1까지.
- 예비 소대 1개. 아군 거점이 공격받으면 가장 가까운 예비가 방어.
- 흔들림 방지: U 차이 0.3 이상일 때만 바꾸고, 한 명령은 최소 60초.
- 안개: 지휘관은 자기 편이 최근 20초 안에 본 적만 안다(기존
MEMORY = 20).
- 항공 임무: 공격 헬기 — 가장 교전이 심한 거점 지원, 적 헬기가 발견돼 있으면 사냥. 수송 헬기 — 태울 분대가 있고 착륙 지점이 적 방공 추정 사거리 밖일 때 공중 강습. 전투기 — 적 항공기가 발견돼 있으면 제공, 아니면 집중·핵심 거점에 대지 공격(120초마다 1회, 전투기 15.8).
- 플레이어를 특별 취급하지 않는다: 적 지휘관은 플레이어가 탄 탈것을 같은 분류의 봇 탈것처럼 센다(세력·위협). 단 "플레이어 아파치/전투기가 발견됨"이면 적 공격 헬기·전투기의 표적 우선순위 +1(기존 게임의 긴장 유지).
소대 — 거점 공방 행동
| 상태 |
들어가는 조건 |
행동 |
나가는 조건 |
| 집결 |
스폰 직후 |
구성원 모두 스폰될 때까지(최대 20초) 대기 |
전원 준비 |
| 이동 |
공격·방어 명령 |
탈것이 있으면 탑승 → 도로 A로 거점 300~400m 앞 대기 지점까지 호송(간격 25m). 걸어서면 지형 A(보병 격자, 5.5) |
대기 지점 도착 / 매복 교전 |
| 하차 |
대기 지점, 탑승 보병 있음 |
차량 정지 → 5초 뒤 하차 |
하차 끝 |
| 공격 |
하차 끝 |
보병은 거점 원 안·주변 엄폐 슬롯(엄폐 소품·건물·나무 20m 안)으로 달려 들어가며 사격, 차량은 화력 지원 슬롯(거점 200~600m, 가시선 있음) |
거점 소유 / 세력 < 35% |
| 점령 유지·방어 |
거점 소유 또는 방어 명령 |
엄폐 슬롯에 앉은 자세로 정지, 차량은 접근로 쪽 |
새 명령 |
| 후퇴·재편 |
세력 < 35% |
가장 가까운 안전한 아군 거점·본진으로 |
세력 ≥ 70% |
- 이동 중 교전: 트럭은 계속 달림, 보병 소대는 정지해 응사, 기갑은 달리며 사격(명중 × 0.5).
구현 메모 (B1-10, sim/battle/platoon.ts·commander.ts·intel.ts, ai/movement.ts의 setGroupRoute)
- 소대 편성: 분대 2개씩 묶고 탈것을 하나씩 붙인다. 남는 탈것은 탈것만의 소대(빠른 점령전 진영당 7개 소대). 진영의 마지막 소대가 예비.
- 대기 지점은 도로 경로를 따라 거점 350m 앞(명령 받을 때 한 번 정한다). 전원이 60m 안에 모이거나 첫 도착 뒤 45초면 돌입. 보병은 거점 반경 절반의 원 위 슬롯, 탈것은 본진 쪽 250m 화력 지원 슬롯. 거점을 가지면 점령 유지, 세력 35% 아래면 본진으로 후퇴, 70%면 재집결.
- 경로: 150m 넘으면 도로 그래프 A* + 도로를 벗어나 목적지로. 도로 위(10m 안)에서 출발하면 뒤쪽 첫 노드를 건너뛰고, 목적지가 마지막 도로 구간의 앞쪽 90% 안(30m 안)이면 그 구간 끝 노드를 건너뛴다(거점을 지나쳐 돌아오지 않게).
- 오를 수 있는 경사: 보병 35°·궤도 30°·그 밖 20°(바퀴형 25°는 B4 조종 모델에서). 봇 이동 속도를 탈것 표(13.3)와 맞췄다: 전차·방공 16/11, 장갑차 20/14, 경차량 28/18, 트럭 22(도로 전용 취급), 보병 분대 4.5m/s(플레이어 달리기 5.0보다 조금 느림).
- 지휘관: 5초마다(연합 0초·VPA 2.5초), 효용 식 그대로, 탐욕 배정, 거점당
알려진 적 × 1.5 + 1개 소대까지. 흔들림 방지: 명령 60초 유지 + 효용 차 0.3(현재 거점의 효용은 지금 값으로 다시 계산). 예비는 아군 거점이 공격받으면(거점 안 적 세력 또는 알려진 적) 가장 가까운 곳으로 즉시 방어, 60초 뒤 위협이 없으면 예비로 복귀.
- B1-15 하니스로 고친 것: 거점당 한도를 소대 수가 아니라 세력 단위(분대 1·탈것 0.5)로 셈, 한도를 넘친 채 점령 유지 중인 소대는 60초 뒤 다른 곳으로(이동·돌입 중인 소대는 명령 유지 — 그 전엔 1분마다 목표가 바뀌어 아무도 도착하지 못했다), 남는 소대는 적·중립 거점 공격으로. 후퇴는 가장 가까운 안전한 아군 거점으로 하고 도착 20초 뒤 재집결(분대원은 회복되지 않으므로 70%를 기다리면 영영 못 돌아왔다). 대기 지점 대기 20초, 1km 밖 낙오자는 기다리지 않음. 보병은 돌입·유지 중엔 쏘면서 전진하고, 이동 중엔 200m 안 적에게만 멈춰 응사. 봇 분대 이동 5m/s.
- 스폰: 분대는 적 본진에 가장 가까운 아군 거점 두 곳에서 번갈아, 알려진 적이 400m 안에 있는 거점에서는 스폰하지 않는다(공격받는 거점이 무한 증원으로 버티지 않게). 스폰 자리는 사방 8m가 모두 물·급경사가 아니어야 한다.
- 사격 처리 순서는 틱마다 정방향·역방향을 번갈아(먼저 만든 쪽이 늘 먼저 쏘는 편향 제거).
- 안개: 표적 선택에서 가시선이 확인된 적을 진영별로 기록(
Intel), 20초 지나면 잊는다. 지휘관은 이 기록만 센다.
5.3 유닛 — 표적 선택과 교전
- 표적 후보: 공간 격자(기존
core/grid.ts, 셀 100m)로 자기 최대 사거리 안의 적 최대 4개(가까운 순) + 플레이어(적 진영이고 플레이어를 발견했을 때만, 5.6).
- 우선순위 = 상성 × 1/거리. 상성: 대전차 → 전차 3·장갑차 2·기타 차량 1 / 전차 → 전차·장갑차 2·대전차 분대 1.5·보병 1 / 방공·대공 → 공중 3·기타 0 / 보병 → 보병 1·경차량 0.7 / 저격 → 보병 2(플레이어 보병 3).
- 가시선: 후보마다
LosCache 쌍 키(0.5초·5m 캐시). 가려진 후보는 건너뜀 — 봇끼리도 능선·건물이 전투를 가른다(P2).
- 발견 지연: 기존 반응 시간표(보병 2s·차량 1.5s·방공 1s·SAM 3s).
- 봇 대 봇 발견은 "가시선 + 시각 거리 + 반응 시간"(쌍마다 인지도를 누적하면 N²).
구현 메모 (B1-4, sim/battle/targeting.ts)
- 상성표에 없던 역할을 채웠다(추정, 하니스로 조정): 장갑차 → 보병 2·경차량 1.5·장갑차 1.5·전차 1 / 경차량 → 보병 1.5·경차량 1·장갑차 0.3 / 공격 헬기 → 전차·장갑차 2·경차량 1.5·보병 1·공중 1 / 전투기 → 공중 3·지상 탈것 1. 방공·대공의 "기타 0"은 0.2로 두었다 — 자기 방어용 소총·대지 사격(
g_aaa)이 영영 안 쓰이지 않게.
- 표적 분류: 공중 / 전차(궤도·장갑 4) / 장갑차(궤도 또는 장갑 2 이상 방공 차량) / 경차량 / 보병 / 대전차 보병 / 구조물. 파괴 불가 유닛(FARP)은 후보에서 뺀다.
- 표적을 바꾸면 기존 반응 시간표만큼 조준 지연 뒤에 쏜다. 플레이어가 표적이면 기존
brain.ts(대 플레이어 상태 기계)가 쏘고, 유닛이 표적이면 그 유닛은 대 플레이어 두뇌를 건너뛴다.
- 가시선 쌍 키
pairKey(관측, 표적), 캐시 항목은 5초 지나면 정리(1초마다).
- 플레이어 진영
world.playerSide(기본 연합) — 적 AI·인지·RWR이 모두 "플레이어의 반대 진영"으로 일반화됐다.
5.4 플레이어 탈것에 봇이 타는 규칙 (D5 안의 자동 규칙)
D5는 플레이어의 지휘를 "지원 요청 수락 + 집중 거점 1개"로 제한한다. 그래서 탑승은 명령이 아니라 자동 규칙이다.
- 탑승: 플레이어가 좌석이 빈 수송 트럭·장갑차·수송 헬기를 아군 거점 또는 본진 100m 안에 5초 이상 세우면, 공격 명령을 받고 아직 출발 전이거나 걸어서 이동 중인 가장 가까운 봇 분대(300m 안)가 달려와 탄다. 무전 자막: "스틸 3, 탑승한다."
- 하차: 플레이어 탈것이 그 분대의 목표 거점 300m 안에서 3초 이상 멈추면(헬기는 착지) 자동 하차. 목표에서 먼 곳에서 멈추면 내리지 않는다(10초 뒤 "여기가 아니다" 무전 한 번).
- 플레이어 탈것이 파괴되면 탑승 분대에 진영·병과·탈것 4.4 규칙 적용.
- 이 규칙으로 플레이어는 "운전사"가 되어 봇 분대를 전선에 나를 수 있다 — 수송 탈것의 존재 이유(2.4 최소 손맛).
5.5 보병 이동 — 걸어서 가는 경로
- 지금 A*는 도로 그래프뿐이다. 걸어서 이동하는 분대를 위해 보병 격자(50m 셀, 12km → 240² = 57,600칸, 경사 35° 이상·물 셀 차단)를 맵 생성 때 만든다. 경로는 명령이 바뀔 때만 계산(최악 약 2ms 추정, 이진 힙 사용).
- 거점 400m 안에서는 격자 대신 엄폐 슬롯까지 직선 + 장애물(나무·건물·소품) 옆으로 비껴가기.
5.6 봇이 플레이어를 발견하는 방식 (탈것별)
| 플레이어 상태 |
발견 모델 |
근거 |
| 아파치·공격 헬기·수송 헬기 |
기존 awareness.ts(10Hz, 노출·소음·스카이라인·나무 차폐) 그대로 |
P2 유지 |
| 보병 |
신규 soldierAwareness: 기본 시각 거리 × 자세(선 400m / 앉음 250m / 엎드림 120m) × 이동(정지 0.7 / 걷기 1 / 질주 1.3), 사격 중이면 600m 안 모든 적이 1초 안에 방향을 안다, 가시선(부분 가림이면 거리 ×0.5) |
자세·엄폐가 생존을 가른다(P2, 1.3 성공 기준) |
| 지상 탈것 |
시각 1,500m(가시선) + 엔진 소음 400m(가시선 무관) |
탈것은 크고 시끄럽다 |
| 전투기 |
고도 지면 100m 이상이면 방공 레이더·시각 6km, 100m 이하면 기존 헬기 모델 적용 |
저공 침투가 전투기에게도 의미 |
- 경계 전파(1,000m 안 같은 편 인지도 0.5)는 모든 경우에 적용.
5.7 기존 src/sim/ai 확장 계획 (캠페인 제거 뒤 기준)
| 파일 |
지금 |
전장에서 |
awareness.ts |
VPA가 플레이어 헬기를 인지 |
양 진영 → 플레이어로 일반화(플레이어 진영 반대편만), soldierAwareness 추가, 봇 대 봇 spot() |
brain.ts |
VPA만, 표적은 플레이어 |
표적 TargetRef = { kind: 'player' } | { kind: 'unit'; id }, 양 진영 stepBattleBrains, fire()가 유닛 표적이면 world.damageUnit + 관통 배율 |
movement.ts |
그룹 경로 |
setGroupRoute(명령마다 재계산), 보병 격자 A*, 열린 목록 이진 힙 |
air.ts |
VPA 헬기가 플레이어 주위 선회 |
표적 일반화 + 수송 헬기 비행 + 대리 조종사(하니스용, 플레이어 슬롯의 아파치를 봇으로) |
wingman.ts |
— |
제거됨(D13). 연합 봇 아파치는 air.ts의 공격 헬기 행동을 쓴다 |
신규 sim/battle/* |
— |
conquest.ts, roster.ts, spawner.ts, commander.ts, platoon.ts, squad.ts(분대원 슬롯·체력), targeting.ts, combat.ts, cargo.ts, requests.ts, jetAi.ts |
5.8 지원 요청 (D5)
- 플레이어 진영 소대가 공격·방어 중이고 1.5km 안 적 차량 세력이 자기 세력의 0.7배를 넘으면 지원 요청(소대당 쿨다운 120초, 동시 활성 1개).
- 요청 = 무전 한 줄 + 연막(90초) + 미니맵·HUD 표식(◇).
- 플레이어가 무전 메뉴에서 수락하면 표식이 항법 목표가 되고, 3분 안에 표시된 적 차량의 70%를 파괴(플레이어가 직접 또는 플레이어가 태워 간 분대·레이저로 유도한 봇 공격이 파괴해도 인정)하면 보너스 +300.
- 플레이어가 무엇을 타고 있든 받는다(보병이면 공병·지원이 대응, 헬기·전투기면 공습).
5.9 AI 테스트 목록 (각 항목이 테스트 하나)
- 평지 1.5km 전차 1 대 전차 1: 120초 안에 한쪽 파괴.
- 능선을 사이에 둔 전차 둘은 서로 쏘지 않는다.
- 5인 분대 하나가 중립 거점에 들어가면 25초(±1초)에 점령, 양쪽 1개씩이면 값 고정.
- 지휘관은 같은 소대 명령을 60초 안에 바꾸지 않는다.
- 공격받는 아군 거점이 생기면 가장 가까운 예비가 10초 안에 방어 명령.
- 수송 헬기 파괴 → 탑승 분대 전멸, 티켓 4 + 탑승 인원.
- 적 봇은 플레이어를 발견하기 전에는 표적 후보에 넣지 않는다(P2).
- 엎드린 플레이어 보병은 200m에서 발견되지 않고, 선 채로 달리면 400m에서 발견된다.
- 플레이어가 수송 트럭을 아군 거점 100m 안에 5초 세우면 분대가 탑승하고, 목표 거점 300m 안에서 멈추면 하차한다.
- 분대원 5명 중 플레이어가 2명을 쓰러뜨리면 분대 HP가 24가 되고 인형이 3개 남는다.
- 같은 시드·같은 입력 → 10분 뒤 티켓·유닛 상태 해시가 같다(결정론).
- 진영을 바꿔도(플레이어 VPA) 1~11이 대칭으로 통과한다.