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. 평지 1.5km 전차 1 대 전차 1: 120초 안에 한쪽 파괴.
  2. 능선을 사이에 둔 전차 둘은 서로 쏘지 않는다.
  3. 5인 분대 하나가 중립 거점에 들어가면 25초(±1초)에 점령, 양쪽 1개씩이면 값 고정.
  4. 지휘관은 같은 소대 명령을 60초 안에 바꾸지 않는다.
  5. 공격받는 아군 거점이 생기면 가장 가까운 예비가 10초 안에 방어 명령.
  6. 수송 헬기 파괴 → 탑승 분대 전멸, 티켓 4 + 탑승 인원.
  7. 적 봇은 플레이어를 발견하기 전에는 표적 후보에 넣지 않는다(P2).
  8. 엎드린 플레이어 보병은 200m에서 발견되지 않고, 선 채로 달리면 400m에서 발견된다.
  9. 플레이어가 수송 트럭을 아군 거점 100m 안에 5초 세우면 분대가 탑승하고, 목표 거점 300m 안에서 멈추면 하차한다.
  10. 분대원 5명 중 플레이어가 2명을 쓰러뜨리면 분대 HP가 24가 되고 인형이 3개 남는다.
  11. 같은 시드·같은 입력 → 10분 뒤 티켓·유닛 상태 해시가 같다(결정론).
  12. 진영을 바꿔도(플레이어 VPA) 1~11이 대칭으로 통과한다.