본문으로 건너뛰기
Server Changelog

서버 업데이트 기록

플러그인, 맵, 규칙 변경 기록입니다. GitHub에서 가져오고, 실패하면 로컬 캐시를 씁니다.

캐시 시간: 2026-10-15 02:44 Using cache GitHub에서 원본 텍스트 보기

2026년 10월 5일 Tank 투척 시점과 돌 보호 (ai_tank3 2.3.2 / l4d_rock_lagcomp 2.2-anne)

  • 돌 체력은 100으로 고정합니다. 모든 난이도의 투척 후 보호 시간을 0.1초 늘리고 음리를 제외한 1~5단계는 0.05초 더 늘려, 6단계별 0.15 / 0.20 / 0.25 / 0.30 / 0.35 / 0.35초로 변경했습니다. AI와 플레이어 Tank에 공통 적용되며 플러그인 기본값은 0.25초입니다. 체력 또는 거리에 따른 추가 보호는 없습니다. 1~5단계 투척 속도 800, 음리 1000을 가정하면 낮은 투사각의 보호 중 수평 이동은 약 120 / 160 / 200 / 240 / 280 / 350 단위입니다. 실제 거리는 속도와 각도에 따라 달라집니다.
  • 일반 AI 투척은 200~450 단위(양 끝 포함) 안에서 가장 가까운 유효한 가시 대상을 선택합니다. 추적 대상이 너무 가깝거나 멀면 범위 안의 다른 대상으로 바꿀 수 있습니다. 적합한 후보가 없으면 새 투척을 시작하지 않습니다. 돌을 놓을 때도 범위 안의 대상을 다시 선택하며, 후보가 사라졌다면 원래 궤적을 유지해 이미 시작한 투척을 완료합니다. 머리 위 방해와 올라탄 생존자에 대한 강제 투척은 기존 전용 규칙을 유지합니다. 일반 연속 점프 능력, 투척 간격, 생존자 피해량은 변경하지 않았습니다.
  • 투척 시작 시 위쪽 속도를 추가하던 점프 동작과 ai_tank3_jump_rock, 6단계의 관련 설정을 삭제했습니다. 일반 연속 점프는 기존 동작을 유지하므로 점프 중 투척은 가능하지만, 투척 애니메이션이 추가 점프를 발생시키지는 않습니다.
  • 돌 보호와 과거 위치 명중을 같은 사격 시각으로 판정합니다. 되감기는 outgoing 지연과 보간 시간을 사용하며 sv_maxunlag 및 유효 기록으로 제한합니다. 조준선 근사 판정에 장애물 검사를 추가하고 가장 가까운 돌 하나만 피해를 받습니다.
  • 기본값 sm_rock_native_shotgun 1은 모든 산탄 피해를 조준선 하나에 합산하지 않고 엔진의 실제 산탄 명중 피해를 사용합니다. 산탄총에는 이 플러그인의 위치 되감기를 적용하지 않습니다. 0이면 기존 근사 방식으로 돌아갑니다.
  • ai_tank3.smx와 l4d_rock_lagcomp.smx를 다시 컴파일하고 6단계 설정과 문서를 갱신했습니다. 보호 경계, 되감기 기록, 산탄 피해 경로, 투척 시점 회귀 검사를 통과했습니다. 실제 서버에는 배포하지 않았으며 명중률과 높은 지연 상황은 추가 검증이 필요합니다.

2026년 10월 3일 캠페인 맵의 대전 모드 Boomer 모델 초기화 수정

  • 맵 투표를 0.9.4-custom으로 업데이트했습니다. OnMapInit에서 이전 맵의 주입 캐시를 지우고 versus 임무 정보를 다시 주입합니다. Utopia 첫 챕터의 모델 초기화 시 임무 정보를 찾지 못해 Boomer 목록이 불완전해지고 생성 중 서버가 충돌하던 문제를 수정합니다. 남성·여성 모델을 모두 유지합니다.
  • 기존 l4d2_mapvote_versus_from_coop 설정을 그대로 사용하며 맵 로딩 전에 활성화해야 합니다. 플러그인 업데이트 후 맵을 다시 로드하세요. no_female_boomers 재정의나 NAV 수정은 필요하지 않습니다.

2026년 10월 3일 캠페인 전용 맵의 대전 I/O 호환

  • l4d2_coop_versus_compat.smx를 추가했습니다. 맵 엔티티가 로드되기 전에 캠페인 전용 커스텀 맵의 info_gamemode에서 OnCoop / OnCoopPostIO 연결을 해당 대전 출력으로 옮깁니다. 매개변수, 지연, 실행 횟수를 보존하며 게임 모드를 위장하거나 전역 OnCoop 이벤트를 발생시키지 않습니다.
  • 원래 versus 선언이 없는 coop 맵을 자동 판별하고 공식 맵과 대전 지원 맵을 보호합니다. configs/l4d2_coop_versus_compat.cfg에서 맵별 활성화·제외가 가능하며 콘솔의 sm_coop_versus_status로 결과를 확인할 수 있습니다. 플러그인 로드 또는 설정 변경 후 맵을 다시 로드해야 합니다.
  • 맵 투표를 0.9.3-custom으로 업데이트하고 coop 챕터를 versus에 추가하는 기본 범위를 모든 설정으로 확대했습니다(l4d2_mapvote_versus_from_coop 2). NAV, 스크립트 내부 모드 판정, 커스텀 피날레는 수정하지 않습니다. 팀 교대, 챕터 전환, 점수 정산은 맵별 검증이 필요합니다.
  • 실제 커스텀 캠페인 메뉴 항목을 읽는 서버 콘솔 전용 명령 sm_mapvote_list를 추가했습니다. cloud35에서 Utopia는 versus 주입을 끄면 표시되지 않고 켜면 표시되었으며, 3개 챕터 모두 로드되고 챕터·피날레가 올바르게 판정되었습니다. 실제 플레이어 조작, 자연스러운 팀 교대, 구출 완료는 검증하지 않았습니다. 이 서버에서는 확장자 앞의 추가 마침표가 VPK 마운트를 막았으며, 추가 마침표 없는 임시 링크로 로드할 수 있었습니다. 테스트 후 서버를 원상 복구했습니다. 자세한 내용은 docs/coop_versus_cloud35_test_2026-10-03.md를 참고하세요.

2026년 10월 1일 네트워크 품질 알림: choke 감지 누락 및 손실 방향 수정

  • network_quality_hint를 1.1.7로 올렸습니다. 엔진의 loss/choke 값은 최근 약 1초의 이동 평균일 뿐인데, 이전 버전은 5초마다 한 번만 읽어 사실상 임의의 1초를 뽑았습니다. 호드나 엔티티가 많은 상황의 1~2초짜리 choke는 거의 잡히지 않았고, 채팅 경고는 3회 연속 초과도 필요해 choke가 사실상 발동하지 않았습니다.
  • 이제 매초 측정하고, 각 판정 주기(nqh_check_interval, 기본 5초)의 평균으로 ping/loss/choke를 판단합니다. 타임아웃은 주기 안에 한 번이라도 있으면 포함합니다. 임계값은 그대로입니다: 채팅 경고 choke 5%, 웹사이트 이상 이벤트 20%.
  • 웹사이트 보고는 avg/p95에 각 주기의 평균, max에 주기 내 가장 높은 1초 값을 사용해 짧은 스파이크도 남습니다.
  • 패킷 손실은 플레이어가 서버로 보내는 방향으로 측정합니다(status의 loss와 같은 값). 서버는 자신이 보낸 패킷이 손실됐는지 알 수 없어, 이전 버전이 보던 「서버→플레이어」 방향 손실은 항상 0이었고 손실 경고와 웹사이트 이상 이벤트가 한 번도 발동하지 않았습니다.
  • nqh_report_loss_limit(기본 5%)를 추가했습니다. 주기 평균 손실이 이 값을 넘을 때만 웹사이트 이상 이벤트를 기록합니다. 채팅 경고는 그대로 nqh_loss_limit(2%, 3주기 연속)를 씁니다.
  • !net의 측정 방향 안내에 손실 방향을 추가했습니다. 경고 사유와 고정된 「[网络]」 태그는 번역 파일에서 가져와 6개 언어 모두 플레이어 언어로 표시됩니다.
  • network_quality_hint.smx를 다시 컴파일했습니다. NewAnneWeb도 함께 수정했습니다: 관리 화면 손실 열은 플레이어→서버 방향을 표시하고, 연결 이상 기록에는 사유(loss / ping / choke / timing_out)를 표시합니다.

2026년 9월 26일 RPG 메뉴에 아이템 빔 설정 추가

  • !rpg / !buy 메인 메뉴의 명중 피드백 메뉴 뒤에 '아이템 빔 설정'을 추가했습니다. 선택하면 !beam 빔 설정 메뉴가 열립니다. l4d_random_beam_item을 불러오는 모드에서만 표시됩니다(이 플러그인이 같은 이름의 라이브러리를 등록하고 rpg.smx가 이를 확인합니다).
  • rpg.smx와 l4d_random_beam_item.smx를 다시 컴파일했습니다.

2026년 9월 26일 진통제 빔 축소

  • data/l4d_random_beam_item.cfg에서 진통제 빔의 높이를 250에서 100으로, 너비를 15에서 10으로 줄였습니다. 색상(초록색), HDR 밝기, 벽 너머로 보이는 광륜은 그대로입니다. 서버 기본값이므로 !beam으로 길이나 너비를 바꾼 플레이어는 새 값을 기준으로 비율이 적용됩니다.

2026년 9월 25일 데이터베이스 연결 축소: anne_db 연결 허브 추가

  • extend/anne_db.smx를 추가하고 cfg/generalfixes.cfg 첫 줄에서 불러옵니다. 같은 데이터베이스에는 MySQL 연결을 하나만 두고 공유하며, 각 플러그인은 그 연결의 복사본을 사용합니다. 서버 한 대의 데이터베이스 연결이 최대 약 22개에서 3개(Anne 모드는 5개)로 줄었습니다. SourceMod의 스레드 쿼리는 원래 하나의 데이터베이스 스레드에서 차례로 처리되므로, 연결을 공유해도 속도는 떨어지지 않습니다.
  • 적용 대상: l4d_stats, rpg, l4d2_hitsound, l4d2_damage_show, l4d2_scripted_hud, l4d2_blacklist, global_chat, chatlog, lilac, l4d_player_count_unload_mode, l4d_random_beam_item, annehappy_dynamic_ai_difficulty, anne_traitor_quota, spawn_vote_menu, sbpp_main, sbpp_comms, sbpp_checker, sbpp_sleuth. anne_db가 로드되지 않았으면 각 플러그인은 예전처럼 자체 연결을 사용합니다.
  • 데이터베이스가 끊겨도 서버가 멈추지 않습니다. 연결에 실패한 뒤 30초 동안은 기다리지 않고 바로 반환하며, 게임 중 재연결도 메인 스레드를 막지 않습니다. databases.cfg의 MySQL 연결 제한 시간을 기본 60초에서 15초로 줄였습니다.
  • 연결 허브가 120초마다 연결 유지 쿼리를 보내므로(MySQL wait_timeout은 600초), 한동안 쓰지 않은 뒤 첫 쿼리가 실패하지 않습니다. 플러그인마다 따로 하던 연결 유지와 맵마다 끊고 다시 연결하던 동작은 제거했습니다.
  • 수정: l4d_player_count_unload_mode에서 여러 cvar가 연달아 바뀌면 데이터베이스 연결이 누수되던 문제. l4d_stats 통계 초기화를 SourceMod 트랜잭션으로 바꿔 다른 플러그인의 쿼리가 같은 트랜잭션에 섞이지 않도록 했습니다. l4d_stats에서 영향받은 행 수를 읽을 때의 경쟁 상태도 수정했습니다.
  • 관리자 콘솔 명령 sm_annedb_status로 각 데이터베이스 연결 상태를 확인할 수 있습니다.
  • 데이터베이스 플러그인 작성 규칙을 AGENTS.md에 추가했습니다.

2026년 9월 25일 아이템 빔을 플레이어가 직접 설정 (l4d_random_beam_item 1.1.0)

  • 표준 빔: data/l4d_random_beam_item.cfg의 default 섹션을 standard(표준 빔: 길이 100, 너비 6, HDR 2.0, 흰색, 광륜 없음)로 바꾸고, 각 아이템에는 표준과 다른 값만 적도록 했습니다. 모든 아이템은 무작위 색 대신 카테고리별 고정 색을 씁니다. 이 파일은 이제 서버 기본값일 뿐이며, 기본으로 표시되는 것은 이전과 같이 진통제(초록색, 길이 250, 너비 15, 광륜)와 노움(흰색, 길이 30, 너비 5)뿐입니다.
  • 아이템 그룹: 아이템을 14개 그룹(진통제, 아드레날린, 구급상자, 제세동기, 투척 무기, 업그레이드 팩, T1 무기, T2 무기, 특수 무기, 권총, 근접 무기, 탄약과 업그레이드, 운반 물품, 기타)으로 나눴습니다. 그룹과 메뉴 순서는 데이터 파일의 group / groups로 설정합니다.
  • 플레이어 설정: !beam으로 메뉴를 엽니다. 바뀌는 것은 내 화면뿐입니다:
  • 모든 아이템: 표시(기본 / 모두 숨김), 길이와 너비(표준의 50%~300%), 색상(9가지), 광륜(켜기 / 끄기).
  • 아이템별 설정: 그룹마다 표시(표시 / 숨김), 길이, 너비, 색상, 광륜을 따로 설정합니다. 설정하지 않은 항목은 '모든 아이템'을 따르고, 따로 설정한 그룹은 메뉴에 [사용자 지정]으로 표시됩니다.
  • 빠른 스타일: 선명하게(길이·너비 150%, 광륜 켜기), 은은하게(길이 50%, 너비 75%, 광륜 끄기), 모두 끄기, 기본. '모든 아이템'만 바꾸며, 배율은 데이터 파일의 styles 섹션에서 조정합니다. !beam bright/subtle/off/default로 바로 바꿀 수 있고 !beam reset은 모두 기본값으로 되돌립니다.
  • 기본적으로 숨겨진 아이템도 켤 수 있음: 예를 들어 누군가 아드레날린을 표시로 설정하면 서버가 아드레날린 빔을 만들지만, 표시를 선택한 플레이어에게만 켜지고 다른 사람에게는 보이지 않습니다. 필요한 플레이어가 없어지면 빔은 삭제됩니다. 서버 에딕트 수가 l4d_random_beam_item_player_edict_limit(기본 1900)에 도달하면 더 만들지 않고 플레이어에게 알립니다. 0으로 설정하면 이 기능이 꺼집니다. 물리 프롭(prop_physics)은 수가 너무 많아 제외했습니다.
  • MySQL에 저장: 설정은 databases.cfg의 rpg 데이터베이스에 새 테이블 beam_item_prefs(Steam2 ID 기준)로 저장됩니다. 플러그인이 테이블을 자동으로 만들며 database.sql에도 추가했습니다. 접속 시 불러오고, 변경 후 3초 뒤에 저장하며, 접속을 끊을 때 한 번 더 저장합니다. 데이터베이스를 사용할 수 없으면 설정은 이번 접속 동안만 유지되고 플레이어에게 알립니다.
  • 동작 방식: SendProxy 확장(sendproxy2)으로 각 빔의 켜짐/꺼짐, 길이, 너비, 색상, 광륜을 클라이언트에 보낼 때 플레이어별로 바꿔 보냅니다. 서버의 빔 자체는 바뀌지 않습니다. 클라이언트는 색상과 광륜을 빔을 만들 때만 읽기 때문에, 설정을 바꾸면 해당 플레이어에게 빔을 0.3초 동안 껐다가 다시 켭니다. 서버 값과 다른 항목만 바꾸므로 기본 설정인 플레이어는 추가 패킹 부담이 없습니다. SendProxy 확장이 없으면 모두가 데이터 파일대로 보게 되고 !beam은 사용할 수 없다고 안내합니다.
  • sm_beamreload로 데이터 파일을 다시 불러와도 접속 중인 플레이어의 설정은 그룹 이름 기준으로 유지됩니다.
  • 채팅 메시지와 메뉴는 중국어 간체, 중국어 번체, 영어, 일본어, 한국어, 베트남어를 지원합니다.
  • docs/plugin_commands.md에 sm_beam을 추가하고 l4d_random_beam_item.smx를 다시 컴파일했습니다.

2026년 9월 23일 Tank 목표가 등 뒤로 가면 뒤로 계속 뛰지 않고 되돌아 점프(ai_tank3 2.3.0)

  • 문제: 직접 추격 중에 생존자가 옆으로 피하거나 등 뒤의 목표로 바꾸면, Tank의 속도 방향과 목표 방향 사이 각도가 89°를 넘는 순간 공중 보정이 더 이상 방향을 틀지 않습니다(공용 이동 코드의 상한). 착지할 때는 1.x의 누적 규칙으로 다시 점프해서, 원래 속도를 그대로 두고 목표 쪽으로 가속을 한 번 더할 뿐입니다. 그래서 같은 방향으로 계속 뛰고, 한 번 뛸 때마다 수십씩만 느려집니다. 사람을 바라보면서 뒤로 여러 번 연속으로 뛰는 것처럼 보였고, 속도가 200 아래로 떨어지거나 벽에 부딪혀야 돌아섰습니다.
  • 공중 제동: 직접 추격으로 시작한 점프에서 가려는 방향이 등 뒤로 넘어가면, ai_tank3_bhop_reverse_brake(기본 1500 유닛/초²)로 수평 속도를 달리기 속도까지 줄입니다. 속도만 줄이고 방향은 바꾸지 않습니다. 제동 전에 줄어든 속도로 남은 경로를 다시 확인하고, 착지 지점이 안전하지 않으면(단차, 구덩이 가장자리) 제동하지 않습니다.
  • 착지 시 되돌아 점프: 착지했을 때 목표가 여전히 등 뒤 89° 밖에 있으면 ai_tank3_bhop_reverse_hop(기본 1)이 되돌아 점프로 바꿉니다. 목표를 곧바로 겨냥해 달리기 속도로 뛰고, 이번 점프에는 가속도 좌우 점프도 없습니다. 연속 점프는 끊기지 않지만 쌓아 둔 속도는 초기화되고, 이후 평소처럼 한 번 뛸 때마다 가속합니다. 피했을 때의 대가가 "반대 방향으로 몇 초씩 달려 나감"에서 "쌓아 둔 연속 점프 속도를 잃음"으로 바뀝니다.
  • 반대 방향으로 뛰는 것은 지금 점프의 남은 부분뿐이고(한 번 점프는 최대 약 0.77초), 더 이상 뒤로 계속 뛰지 않습니다. 목표가 앞쪽 89° 안에 있는 직접 추격(좌우 점프, 45°~60° 보정 문턱 포함)과 경로를 따라가는 연속 점프는 바뀌지 않습니다.
  • 두 설정 모두 동적 난이도 단계에 넣지 않았습니다. ai_tank3_bhop_reverse_hop 0, ai_tank3_bhop_reverse_brake 0으로 이전 동작으로 되돌릴 수 있습니다. ai_tank3_log_level 2이면 콘솔에 reverse hop 로그가 출력되어 실제 테스트에 쓸 수 있습니다.
  • docs/annehappy_dynamic_ai_difficulty.md와 Tank 상황 도감(docs/ai_tank3_scenarios_2026-09-14.html)을 동기화하고 ai_tank3.smx를 다시 컴파일했습니다.

2026년 9월 23일 약역 모드 약 생성 위치 제어 (confoglcompmod ItemTracking)

  • 일반 / 하드코어 / 샷건 약역 모드(일반 약역 설정을 같이 쓰는 WitchParty, 차저 돌진 모드 포함)는 이제 맵 진행도 30%~90% 사이에만 약이 나옵니다: confogl_pills_flow_min 0.3, confogl_pills_flow_max 0.9. 상위 ItemTracking에 원래 있던 진행도 구간 기능이며, 그동안 약역 모드에서는 켜지 않았습니다. 약 개수는 여전히 confogl_pills_limit / mapinfo pain_pills를 따르고, 30%~90% 사이에서 진행도에 맞춰 고르게 고릅니다.
  • confogl_pills_flow_max_detour 추가(약역 모드는 300): 시작점에서 끝까지 이어지는 메인 루트 위의 약만 남깁니다. 먼저 시작점부터 도착 세이프룸까지 걸어갈 수 있는 최단 루트 길이를 재고, 각 약을 주우러 가면 얼마나 더 걸어야 하는지 확인합니다. 메인 루트를 지나가며 주울 수 있는 약은 추가 거리가 거의 0이고, 깊이 d인 옆방·막다른 길·갈림길에 있는 약은 약 d입니다. 300을 넘는 약은 삭제합니다. 이 단계는 약 간격과 개수 선별보다 먼저 실행되므로, 남는 약은 모두 메인 루트 위 자리에서 고릅니다.
  • confogl_pills_flow_fill 추가(약역 모드는 1): 선별 후 약이 상한보다 적으면, 옆방 약으로 개수를 채우지 않고 모자란 만큼 진행도 40%~80%의 메인 루트 위에 새로 생성합니다(confogl_pills_flow_fill_min 0.4, confogl_pills_flow_fill_max 0.8, 위의 30%~90%를 벗어나지 않음). 모자란 개수만큼 40%~80%를 균등하게 나누고, 구간마다 진행도를 무작위로 하나 고릅니다. 그 진행도에 가장 가까운 메인 루트 내비 영역(ESCAPE_ROUTE 표시, 시작 지점·세이프룸·구조 영역 제외) 안에서 무작위 자리를 찾는데, 바닥이 있고 약을 놓을 공간이 있으며 다른 약과 128유닛 이상 떨어져 있고 mapinfo의 시작 / 도착 세이프룸 범위 밖이어야 합니다. 진행도 하나당 내비 영역을 최대 32개까지 시도하고, 그래도 모자라면 40%~80% 안에서 몇 번 더 무작위로 시도합니다. 원래 맵에 약이 하나도 없어도 채웁니다. 이 단계는 길찾기를 하지 않습니다. 새로 생성한 약은 ItemTracking에 기록되므로 전멸 후 다시 시작해도 같은 자리에 나옵니다. 상한까지 채우지 못하면 몇 병을 생성했는지 로그에 남깁니다.
  • 약 빔을 눈에 띄게 변경: data/l4d_random_beam_item.cfg의 진통제 빔을 흰색·높이 20에서 초록색·높이 250·폭 15로 바꾸고, 바닥 후광(벽 너머로도 보임)을 켰습니다. 이 파일은 Anne 캠페인과 Anne 절경에서도 쓰므로, 그쪽 약 빔도 같이 바뀝니다.
  • anne_pill_hint.smx 추가(일반 / 하드코어 / 샷건 약역, WitchParty, 차저 돌진 모드에서 로드): 첫 생존자가 세이프룸을 나설 때, 이번 맵 길목에 약이 몇 병 있고 각각 진행도 몇 %에 있는지 채팅으로 모두에게 알려 줍니다(예: "38%, 52%×2, 71%"). 세이프룸 안의 약은 세지 않으며, 진행도는 약이 있는 내비 영역의 진행도로 약을 삭제할 때와 같은 계산입니다. 메시지는 중국어 간체·번체, 영어, 일본어, 한국어를 지원합니다.
  • 맵마다 mapinfo의 pillflow_min, pillflow_max, pillflow_max_detour, pillflow_fill, pillflow_fill_min, pillflow_fill_max로 따로 조정할 수 있습니다. confogl_pills_flow_visualize 1이면 약을 지우지 않고 결과만 발광으로 표시합니다(흰색 = 유지, 초록 = 보충 생성, 빨강 = 진행도 구간 밖, 마젠타 = 메인 루트 밖, 주황 = 직전 약과 너무 가까움, 청록 = 개수 초과). 피날레 맵은 기본적으로 이 선별을 하지 않습니다(confogl_pills_flow_finale 0).
  • confoglcompmod.smx를 다시 컴파일하고, 새 anne_pill_hint.smx를 컴파일했습니다.

2026년 9월 23일 Charger 공중 방향 전환 제한 (ai_charger3 1.0.1.18)

  • 공중 방향 전환 예산: ai_charger3_air_turn_budget을 추가했습니다. Charger는 땅을 떠날 때마다 도약 방향을 기록하고, 그 점프 동안의 공중 보정은 속도 방향을 도약 방향에서 이 각도까지만 틀 수 있습니다. 예산을 다 쓰면 관성대로 착지합니다. 접근 중 공중 보정과 돌진 직전의 공중 추적이 같은 예산을 나눠 씁니다. 전에는 돌진 직전 점프가 0.05초마다 예측 위치로 방향을 다시 잡았고 최소 임계값도 1도뿐이라, 점프하는 걸 보고 옆으로 피해도 거의 소용이 없었습니다. 이제 예산 밖으로 옆걸음하면 헛돌진하게 만들 수 있습니다. 여섯 단계는 12 / 16 / 20 / 30 / 40 / 45도이며, 좌우 연속 점프(S자)의 흔들림 폭도 이 예산에 막힙니다.
  • 많이 꺾을수록 가속이 줄어듦: 모든 점프의 가속량을 가속량 × (1 − θ / 90)으로 깎으며, θ가 90을 넘으면 90으로 봅니다(가속 없음). θ는 도약 방향 전환 각도, 즉 뛰기 전 땅에서의 속도 방향과 이번 점프 방향 사이의 각도입니다. 착지 직후 점프라면 직전 점프의 공중 보정이 도약 방향에서 벗어난 최대 각도도 더합니다. 지그재그·추적·가속을 한꺼번에 하는 것도, 착지하는 순간 급히 방향을 트는 것도 더 이상 공짜가 아닙니다. 근접 견제 중 뒤로 뛰기는 사실상 U턴이라, 깎고 나면 대부분 달리기 속도만 남습니다.
  • 돌진 직전 마지막 점프: 전에는 가속량을 전액 그대로 더해서, 달리다가 바로 뛰어도 가속 한 번분을 통째로 얻었습니다. 이제 다른 점프와 같은 규칙을 따릅니다(첫 점프는 ai_charger3_bhop_first_hop_ratio로 깎고, 다시 방향 전환 각도로 깎음). 한 번에 돌진 거리까지 닿는 경우가 줄고, 마지막 구간을 달려오는 경우가 많아집니다.
  • 높은 난이도 속도 상한: ai_charger3_bhop_max_speed를 극한(level5)에서 600 → 500, 음리(level6)에서 700 → 600으로 낮췄습니다.
  • 디버그 로그: Path / Direct 도약 로그의 landing hop이 항상 0이었습니다(착지 표시를 소비한 뒤에 읽었기 때문). 수정했고, 네 가지 점프 모두 이번 점프에 계산된 방향 전환 각도와 가속 할인율을 함께 출력합니다.
  • ai_charger3.smx를 다시 컴파일했습니다.

2026년 9월 22일 모든 난이도에서 Smoker의 사다리 위 끌어당기기 금지

  • Neri 난이도에서 Smoker가 사다리 위에서 혀를 쏠 수 있고, 붙잡은 뒤에도 생존자를 끌면서 계속 올라가던 문제를 수정했습니다. Neri에서 켜지는 l4d_air_abilities_patch_neri는 공중에서 혀를 쏘고 끌어당길 수 있도록 혀의 원래 "땅에 서 있어야 함" 판정 두 곳을 없앱니다. CTongue::IsAbilityReadyToFire의 발사 조건과 CTongue::OnUpdateAttachedToTargetState의 "땅을 벗어나면 혀가 끊김" 처리입니다. 사다리를 오르는 동안에도 땅에 서 있지 않은 것으로 취급되므로, 사다리 위에서도 혀를 쏠 수 있었고 붙잡은 뒤에도 혀가 끊기지 않았습니다. 원래 끌어당기는 중인 Smoker는 GetRunTopSpeed를 1로 눌러 움직이지 못하게 하지만, 사다리 이동은 고정된 등반 속도를 써서 이 제한을 받지 않습니다. 그래서 AI의 사다리 오르기가 그대로 이어지며 생존자를 위로 끌고 갔고, l4d2_ai_ladder_boost의 사다리 가속이 그 속도를 몇 배로 키웠습니다.
  • 공중 끌기는 공중 끌기일 뿐 사다리 끌기가 아닙니다. l4d_air_abilities_patch는 이제 난이도나 Neri 패치 여부와 관계없이, Smoker가 사다리를 타고 있는 동안에는 혀를 항상 준비되지 않은 것으로 처리해 AI도 사람도 혀를 쏠 수 없습니다. 붙잡은 채로 사다리에 오른 Smoker는 즉시 혀를 놓습니다(원래 혀가 끊길 때와 같은 ReleaseTongueVictim 흐름이며, 평소대로 쿨다운에 들어갑니다). 원래도 혀를 쏘려면 땅에 서 있어야 하지만, 사다리 아래쪽에 막 붙어 발이 아직 바닥에 닿아 있으면 땅에 선 것으로 취급되므로 이 경우도 함께 막았습니다. Neri의 공중 발사와 공중 끌어당기기는 영향이 없습니다.
  • 끌어당기는 동안 정말로 움직이지 못하게 했습니다. 원래의 "붙잡으면 못 움직임"은 혀가 나가 있는 동안 Smoker의 달리기 속도 상한을 1로 누르고 점프와 앉기를 빼는 것뿐이라(CTerrorGameMovement::CheckParameters), 전진 키와 이동 입력은 그대로 남습니다. 사다리에 붙는 데에는 사다리 쪽으로 향하는 이동 입력과 사다리까지 2단위 이내라는 조건만 필요하므로, AI가 사다리 아래에 막 도착해 땅에서 생존자를 붙잡으면 경로 추종이 계속 사다리 쪽으로 밀어 그대로 사다리에 붙고, 고정 등반 속도로 올라갔습니다(원래는 땅을 벗어나는 순간 혀가 끊기고, Neri에서는 끌어당긴 채 끝까지 올라갔습니다). 이제 혀가 나가서 돌아올 때까지(m_tongueState가 0이 아님, 원래 고정에 쓰는 판정과 같음) 모든 Smoker의 이동 키와 이동 입력을 지우므로 정말로 움직이지 못하고 사다리에도 붙지 않으며, 강제로 혀를 놓아 이번 공격을 버리는 일도 없습니다. "붙잡은 채 사다리에 있으면 혀를 놓음" 처리는 안전장치로 남겨 두었습니다. ai_smoker3도 이 동안에는 연속 점프와 공중 속도 보정을 하지 않습니다. 둘 다 속도를 직접 바꿔서 고정을 우회하기 때문입니다.
  • ai_smoker3_jump_pull은 "지면 엔티티가 없음"으로 공중 여부를 판단했는데 사다리 위에서도 이 조건이 성립해, AI가 사다리를 오르며 혀 버튼을 계속 누르고 있었습니다. 이제 사다리 위는 제외합니다.
  • l4d2_air_data.txt에 CTongue::IsAbilityReadyToFire의 DHooks 함수 정의를 추가하고, l4d_air_abilities_patch.smx와 ai_smoker3.smx(1.0.1.5)를 다시 컴파일했습니다.

2026년 9월 22일 Tank 목표 전환 완화, 담즙 쓴 생존자 후순위, 통배권이 쓰러진 사람을 추가타하지 않도록 변경(ai_tank3 2.2.0)

  • Tank가 한 사람만 계속 쫓고 목표를 잘 바꾸지 않던 문제를 수정했습니다. 원인이 세 겹이었습니다. 첫째, 경로 거리 추정이 게임이 ChooseVictim을 호출할 때만 덤으로 갱신되고 한 번에 한 명만 갱신되어, Tank 쪽으로 다가온 생존자가 예전의 더 먼 추정값을 그대로 달고 있었습니다. 둘째, 추정값으로 맞은 단계의 상한을 그대로 써서 1.15배 단계와 1.5배 단계 사이에서만 30%가 튀었습니다. 셋째, 이 노이즈를 누르려고 전환 문턱을 높게 잡아(점수 25% 낮고 최소 150 낮으며 간격 2초), 생존자들이 100~200 단위 안에 몰려 있으면 사실상 바뀌지 않았습니다.
  • 추정은 이제 OnPlayerRunCmd에서 독립적으로 갱신합니다. 각 Tank는 0.5초마다 생존자마다 한 번씩 다시 추정하고(도달할 수 없는 상대는 1초), 플러그인 전체로 tick당 최대 한 명입니다. 단계가 맞으면 "이전 단계는 안 되고 이번 단계는 되는" 구간을 64 단위 이내까지 이분해 중간값을 씁니다. 점수 = 현재 직선 거리 × 우회 계수이므로, 갱신 사이에도 실제 거리를 따라갑니다.
  • 같은 층에서의 전환 조건은 새 점수 ≤ 현재 × 0.85(ai_tank3_target_switch_ratio), 최소 75 낮음(신규 ai_tank3_target_switch_gain), 지난 전환 후 1초(ai_tank3_target_commit_time)입니다. 새 점수가 현재의 절반 이하이면 1초를 기다리지 않습니다(신규 ai_tank3_target_decisive_ratio, 0이면 끔). 75는 두 사람 추정 오차 합의 상한 64보다 커서, 추정 노이즈만으로는 전환되지 않습니다. 사다리 근처와 사다리 위에서의 고정은 그대로입니다.
  • Boomer 담즙을 뒤집어쓴 생존자의 목표 점수 배율을 1.25에서 2.0으로 올렸습니다. 경로 거리가 같다면 Tank는 담즙을 쓴 생존자를 피해 가고, 주변에 다른 사람이 없을 때만 쫓아갑니다.
  • 설정과의 차이를 적어 둡니다. l4d_target_override의 Tank 항목은 order 1,2에 voms2 0이라, 담즙을 쓴 생존자는 후보 단계에서 통째로 빠지므로 order 2는 실제로 일치하지 않습니다. 플러그인 쪽은 감점만 주는 방식을 유지해, 주위에 담즙을 쓴 사람만 남아도 Tank가 멍하니 서 있지 않도록 했습니다.
  • docs/ai_tank3_scenarios_2026-09-14.html은 더 이상 기록 비교로 표시하지 않습니다. "고지대의 사람 vs 지상의 사람" 상황과 목표 선택 계산기는 2.2.0 규칙으로 계산하고(추정 이분, 담즙 ×2, 전환용 cvar 4개를 페이지에서 바로 변경 가능), 나머지 상황은 계속 2.0 판정으로 그립니다. "같은 층 빈 땅에서 추격"과 "앞이 고지대 가장자리" 두 장에는 2.1의 변경을 덧붙였습니다. 담즙을 쓴 생존자에 대한 1.x 비교 설명도 바로잡았습니다(voms2 0이 후보 단계에서 빼므로 order 2까지 가지 않습니다). 현재 값은 docs/annehappy_dynamic_ai_difficulty.md에 있습니다.
  • 통배권이 쓰러진 사람이나 난간에 매달린 사람에게 추가타를 넣지 않습니다. 사거리 안에 아직 서 있는 사람이 있으면 추가 SweepFist는 서 있는 사람만 때리고, 주변이 전부 쓰러졌을 때만 종전처럼 쓰러진 사람도 때립니다. 기존 루프는 "살아 있음·보임·128 이내"만 확인해서, Tank 발밑에 쓰러진 동료가 휘두를 때마다 추가로 한 대씩 맞았습니다(1.x도 같은 구현입니다).
  • ai_tank3.smx를 다시 컴파일했습니다.

2026년 9월 19일 춤추는 중 누군가 안전실 문을 열면 멈추는 문제 수정

  • 춤추는 동안 다른 플레이어가 안전 구역을 벗어나거나 안전실 문을 열면, 춤추던 플레이어가 사격도 어떤 조작도 할 수 없게 되던 문제를 수정했습니다. 춤이 시작되면 fornite_l4d는 m_hActiveWeapon을 -1로 만들고 원래 무기 엔티티를 기억하는데, l4d2_med_dynamic은 player_left_start_area에서 모든 생존자의 기존 구급상자를 지우고 새로 나눠 줍니다. 지워진 것이 바로 춤추는 사람이 숨겨 둔 무기였다면 저장해 둔 엔티티 참조가 이미 무효라 WeaponUnblock()이 복원을 건너뛰었고, m_hActiveWeapon은 계속 -1로 남았습니다.
  • 이제 fornite_l4d는 player_left_start_area와 door_open(안전실 문만)을 EventHookMode_Pre로 후킹해, 아이템을 나눠 주는 Post 처리보다 먼저 모두의 춤을 끝냅니다.
  • 춤을 시작할 때 무기 엔티티뿐 아니라 그 무기가 있던 슬롯도 기록합니다. WeaponUnblock()의 대체 순서는 원래 무기 엔티티 → 같은 슬롯에 지금 있는 무기 → 몸에 있는 아무 무기(실제로 빈손일 때만)입니다. 구급상자를 들고 춤춘 사람은 주무기로 바뀌지 않고 다시 구급상자를 들게 됩니다. 복원할 때 m_flNextAttack, m_flNextPrimaryAttack, m_bInReload도 함께 정리해, m_hActiveWeapon을 직접 써 넣은 뒤에도 총이 나가지 않던 상황을 막습니다.
  • StopEmote()는 춤 엔티티가 유효할 때만 시점·무기·MOVETYPE_WALK를 되돌렸고, 엔티티가 먼저 사라지면 플래그만 지우고 아무것도 복원하지 않았습니다. TerminateEmote()(round_start, 입장, 접속 종료에서 호출)는 이동 방식을 전혀 되돌리지 않았습니다. 이제 둘 다 새로 추가한 RestoreEmoteState()를 호출해, 춤 엔티티의 존재 여부와 상관없이 부모 관계·시점·무기·이동 방식을 복원합니다.
  • 자가 복구도 추가했습니다. OnPlayerRunCmd가 춤은 끝났는데 상태가 돌아오지 않은 플레이어를 발견하면 즉시 복원하므로, 멈춘 플레이어가 사망이나 맵 변경을 기다릴 필요가 없습니다.
  • fornite_l4d.smx를 다시 컴파일했습니다.

2026년 9월 19일 1vht 명중률 통계가 나왔다 안 나왔다 하는 문제 수정

  • 1v1_skeetstats의 발사 카운트는 player_left_start_area 이벤트가 세우는 bPlayerLeftStartArea에 의존했지만, round_start마다 이 플래그가 다시 꺼졌습니다. 이벤트가 발생하지 않거나 안전실을 나온 뒤 round_start가 한 번 더 오면 그 라운드의 발사 수는 계속 0이었고, 정확도 줄은 "발사 없음"만 출력한 반면 이 제한을 받지 않는 킬 수와 스키트 수는 정상으로 보였습니다. 이제 발사 카운트는 left4dhooks의 L4D_HasAnySurvivorLeftSafeArea()로 director에 직접 묻고, L4D_OnFirstSurvivorLeftSafeArea_Post도 추가했습니다.
  • 명중률이 100%를 넘을 수 있던 문제를 수정했습니다. player_hurt의 명중은 아무 조건 없이 세면서 weapon_fire의 발사만 제한되고 있었습니다. 이제 양쪽이 같은 조건을 씁니다.
  • iClientPlaying은 round_start와 player_left_start_area에서만 설정되어, 라운드 도중 생존자 자리를 이어받은 플레이어는 집계되지 않았고 GetCurrentSurvivor()가 자리만 채우는 bot을 고를 수도 있었습니다. 이제 사용 시점에 다시 해석하고 bot은 건너뜁니다.
  • 1vHunters는 versus로 돌아가며 versus_round_restarttimer 2이므로, 3초 지연 보고가 나오기 전에 round_start가 모든 카운터를 지워 통계 전체가 0으로 찍히거나 아예 나오지 않았습니다. 이제 round_end 시점에 스냅샷을 찍어 출력하고 지연도 2초로 줄였습니다.
  • 1v1_skeetstats.smx를 다시 컴파일했습니다.

2026년 9월 19일 Hunter가 공중에서 방향을 바꾸는 문제 수정

  • 덮치기 도중 반복해서 다시 덮쳐 공중에서 방향을 바꿀 수 있던 문제를 수정했습니다. 고난도 단계에서는 z_lunge_interval, z_lunge_cooldown, z_pounce_crouch_delay가 0이고 l4d2_hunter_patch_crouch_pounce 2가 켜져 있어, AI가 공중에서 매 틱 다시 덮치고 엔진이 현재 속도를 새 벡터로 덮어썼습니다. 이제 ai_hunter_2와 ai_hunter_new 모두 덮치기 비행 중에는 공격 키를 차단합니다.
  • ai_hunter_2의 angleLunge()는 "현재 위치 → 대상의 현재 위치"로 덮치기 벡터를 통째로 다시 계산하므로, 비행 중에 호출되면 Hunter가 다시 조준해 최고 속도의 새 벡터를 받았습니다. 이제 지면에서 막 튀어 오른 덮치기(지면에서 떨어진 뒤 0.25초 이내)만 m_queuedLunge 덮어쓰기를 허용합니다. ai_hunter_new도 동일하게 처리했습니다.
  • ai_hunter_2가 ignoreCrouch를 계산해 놓고 전혀 쓰지 않던 문제를 수정했습니다. l4d2_hunter_patch_crouch_pounce 2에서는 Hunter가 앉지 않으므로 시야 밖 덮치기 대기와 덮치기 전 할퀴기가 전혀 동작하지 않았습니다.
  • ai_hunter_2.smx와 ai_hunter_new.smx를 다시 컴파일했습니다.

2026년 9월 17일 Tank 추적 회전 기준 복원

  • ai_tank3_airvec_modify_degree를 6단계별 60 / 55 / 50 / 45 / 45 / 45°, ai_tank3_airvec_modify_degree_max를 모든 단계에서 135°로 복원했습니다. 플러그인 기본값도 45° / 135°로 되돌렸습니다.
  • 공유 이동 코드의 실제 방향 차이 상한 89°는 유지합니다. 1°부터의 경로 추종, 공중 경로 갱신, 좌우 연속 점프, 안전한 낙차 이동 및 난이도별 바위 보호도 유지합니다.
  • 3D 데모와 매개변수 설명을 갱신하고 ai_tank3.smx를 다시 컴파일했습니다.

2026년 9월 17일 Charger 문 파괴(ai_charger3 1.0.1.16)

  • 일반 문이 목표에 대한 시야를 가리면 Charger가 주먹질하지 않고 문에 걸리는 문제를 수정했습니다. 지상에서 장애물에 막혀 느려졌을 때 이동 방향의 발톱 공격 사거리 안에 일반 문이 있으면 정상 주먹 공격을 우선합니다.
  • 벽, 플레이어 및 다른 엔티티의 가림을 유지합니다. 안전실 문, 파괴 불가능한 문, 공중·사다리·비틀거림·돌진·생존자 제압 상태는 제외합니다.
  • 문 처리 회귀 검사를 추가하고 ai_charger3.smx를 다시 컴파일했습니다.

2026년 9월 16일 Tank 바위 투척 직후 보호(l4d_rock_lagcomp 2.1-anne)

  • 바위가 실제로 손을 떠난 뒤의 무적 시간을 쉬움 / 보통 / 어려움 / 전문가 / 극한 / 음리 6단계별 0 / 0.05 / 0.10 / 0.15 / 0.20 / 0.25초로 설정하고 동적 난이도 설정과 함께 적용합니다. 보호 대상은 바위 엔티티이며 AI와 플레이어 Tank에 공통으로 적용됩니다.
  • sm_rock_release_godframes를 추가했습니다(기본 0.15, 0이면 투척 직후부터 파괴 가능). 기존 sm_rock_godframes 1.7은 투척 콜백이 누락될 때 엔티티 생성부터 계산하는 예비 제한 시간으로 유지합니다. 두 타이머는 독립적이며 반투명 표시도 실제 보호 시간에 맞춰집니다.
  • 시간 경계 회귀 검사를 통과했으며 Anne 버전 l4d_rock_lagcomp.smx를 다시 컴파일했습니다.

2026년 9월 16일 Tank의 안전한 낙차 연속 점프

  • 경로가 확인되고 낙차가 256 단위 이하인 일반 하강 구간에서는 안전 검사를 통과하면 미리 감속하거나 점프를 멈추지 않습니다. 속도를 유지해 뛰어내리고 관성으로 낙하한 뒤, 착지 즉시 온전한 착지 추진력으로 다음 점프를 이어 갑니다.
  • 실제 낙차에 따라 길어진 체공 시간을 계산하여 전체 궤적의 벽·천장과 착지 지점 네 모서리의 지면 지지를 검사합니다. 가장자리에서는 능동적인 측면 점프와 점프 투석을 하지 않으며, 더 깊은 낙차·사다리·점프 간격·안전을 확인할 수 없는 경로에는 제한을 유지합니다.
  • 3D 비교를 갱신하고 2.0의 사전 감속도 비교용으로 남겼습니다. 이동 회귀 검사를 통과했으며 ai_tank3.smx를 다시 컴파일했습니다.

2026년 9월 15일 Tank 공중 추적과 좌우 연속 점프(ai_tank3 2.1.0)

  • 공중에서 0.05초마다 안전한 경로의 전방 목표를 갱신하여 이전 지점을 지나도 다음 구간을 따라갑니다. 추적 보정 시작 각도는 6단계별 10 / 8 / 6 / 5 / 4 / 3°, 경로 추종은 1°부터이며 부드러운 회전과 89° 상한을 유지합니다.
  • 원래 좌우 입력의 측면 추진력을 복원했습니다. 600 단위보다 먼 안전한 직선 추격에서는 단계별 8 / 10 / 12 / 15 / 18 / 20°로 좌우 점프합니다. 첫 방향은 무작위이며 착지가 확인된 다음 점프에서 반대쪽으로 바뀝니다. 각도를 0으로 설정하면 꺼집니다.
  • 측면 점프는 같은 높이의 목표가 보이고 경로도 직접 추격을 지원할 때만 작동합니다. 근거리, 경로 회전, 측면 장애물에서는 정상 추적으로 돌아옵니다. 회전 전에 발밑 지면과 남은 체공 시간을 계산하고 실제 충돌 크기로 상승 구간을 포함한 포물선을 나누어 벽·천장을 검사하며, 단절·특수 경로 구간도 확인하고 사다리 처리, 속도 상한, 착지 가속 규칙을 유지합니다.
  • ai_tank3.smx를 다시 컴파일하고 작은 목표 이동, 경로 갱신, 측면 장애물 시 복귀, 좌우 교대, 위험 지형 거부를 회귀 검사에 추가했습니다.

2026년 9월 15일 AI 동작 검토 수정

  • Spitter의 보충 침 뱉기 기록은 처음 착지한 프레임에 반드시 소비합니다. 시야를 잃었거나 능력이 준비되지 않았거나 다른 분기에서 일찍 반환해도 기록을 버리므로, 다음 점프에서 오래된 기록으로 자동 발사하지 않습니다.
  • Tank마다 tick당 최대 3회의 독립적인 경로 탐색 예산을 부여하고 경로의 가까운 지점부터 먼 지점 순으로 확인합니다. 장애물이나 예산 소진 시에도 이미 도달 가능하다고 확인한 지점을 유지하여, 먼 모퉁이나 다른 Tank의 예산 사용 때문에 연속 점프 경로를 잃지 않게 했습니다.
  • 재컴파일: ai_spitter_3.smx, ai_tank3.smx.

2026년 9월 15일 Spitter 착지 침 뱉기 보정(ai_spitter_3 3.0.11)

  • 증상: 쉬움~전문가 네 단계에서 AI Spitter가 침을 거의 뱉지 못해, 능력을 쓸 줄 모르는 것처럼 보였습니다.
  • 원인: ai_spitter3_air_spit가 0이면 공중의 IN_ATTACK은 그 자리에서 버려집니다(대기열에 넣지도, 미루지도 않습니다). 뱉을 수 있는 프레임은 FL_ONGROUND가 켜진 프레임뿐입니다. 그런데 플러그인 자신이 ai_SpitterBhopStartDistance(Anne 세 모드 설정에서 모두 2500) 안에서 착지할 때마다 곧바로 다시 점프하고, z_spit_range는 그보다 훨씬 작아서 침 사거리 전체가 연속 점프 반경 안에 들어 있습니다. 한 번의 체공은 sqrt(2 × 56 / sv_gravity) × 2이고 TickrateFixes가 sv_gravity를 750으로 고정하므로 약 0.77초입니다. 100 tick이면 체공 77프레임 + 착지 1프레임, 지상 프레임 비율은 약 1/78입니다. 98%가 넘는 프레임에서 공격 버튼이 지워졌고, 남은 2% 미만도 AI의 발사 시점과 정확히 맞아야 했습니다. 극한/음리 단계가 멀쩡했던 이유는 공중 뱉기가 켜져 있어 지상 판정을 전혀 보지 않는 buttons |= IN_JUMP 점프 뱉기 경로를 타기 때문입니다.
  • 착지에서 대신 눌러 주기: 공중에서 지워진 한 번에 시각을 기록해 두고(SPIT_OWE_TTL 1초, 한 번의 체공보다 조금 길어 현재 점프에서 밀린 것만 인정), 착지 첫 프레임에 목표가 여전히 보이고 능력도 나갈 수 있으면 플러그인이 대신 공격을 눌러 주며 그 프레임의 점프만 건너뜁니다. 멈추는 것은 그 한 프레임뿐이고 다음 프레임은 평소대로 점프합니다. 착지로부터 1 tick밖에 지나지 않아 0.15초 착지 추력 창 안에 들어가므로 연속 점프 사슬이 끊기지 않고 추력도 그대로입니다. 누를 때 쓰는 시점은 AI 자신의 것입니다. 함수 첫머리의 if (buttons & (IN_ATTACK | IN_ATTACK2)) AIPathMovement_Reset(spitter);가 그 점프의 경로 보정을 이미 껐기 때문에 AlignFacing이 방향을 덮어쓰지 않습니다. 플러그인은 버튼만 누르고 조준은 대신하지 않습니다.
  • 뱉은 직후 다시 점프 가능: 지상 공격 분기는 원래 IN_ATTACK이 눌려 있기만 하면 IN_JUMP를 지워서, AI가 버튼을 계속 누르면 Spitter가 땅에 붙어 있었습니다. 이제 spitReadyToFire()(m_nextActivationTimer 시각이 지남)를 조건으로 씁니다. 침이 아직 나가지 않은 동안에만 점프를 막고, 선딜이나 쿨다운에 들어간 뒤에는 눌린 버튼을 무시하고 연속 점프를 이어 갑니다.
  • 새 cvar를 추가하지 않고 단계별 값도 두지 않습니다. 이것은 버그 수정이지 약화가 아닙니다. 극한과 음리는 ai_spitter3_air_spit가 1이라 기존 점프 뱉기 경로 그대로입니다.
  • 재컴파일: ai_spitter_3.smx.

2026년 9월 9일 특감을 막는 신규 zonemod_anne stripper 엔티티 제거

  • 업스트림 zonemod는 stripper add:로 clip, blocker, 솔리드 프롭, 사다리를 넣습니다. Anne는 대응하는 nav를 고치지 않았기 때문에 특감은 예전 메시로 길을 찾고, 새로 생긴 엔티티에 막혀 끼입니다.
  • 커스텀 맵만 손댔습니다. 2026년 5월 zonemod 동기화 이후에 들어온, 특감을 막는 사다리/솔리드 프롭을 cfg/stripper/zonemod_anne에서 뺐습니다(City 17, No Echo m3, Carried Off, Dark Carnival Remix, Parish Overgrowth 등). 공식 c1–c14 nav는 이미 검토가 끝났으므로 그쪽 기하 add:는 전부 원래대로 되돌렸습니다. env_physics_blocker와 env_player_blocker는 남기되 BlockType을 생존자만(1)으로 바꿨습니다.
  • 커스텀 맵의 filter/modify, 아이템/진통제 add:, nav_fixes logic_auto, 그 이전부터 anne에 있던 clip은 그대로 둡니다. 이후 커스텀 맵 동기화에서는 대응하는 nav 없이 특감을 막는 사다리/솔리드 프롭 add:를 복사하지 않습니다.

2026년 9월 9일 Charger 연점프 단계별 하향 (설정만, 플러그인 변경 없음)

  • 왜 연점프만 건드리나: Charger의 달리기 속도는 생존자와 똑같은 220이라, 실제로 사람에게 닿을 수 있는지는 전적으로 연점프가 결정한다. 즉 연점프 파라미터가 곧 Charger의 압박력이다. 이번 변경은 전부 "어떻게 뛰는가"만 바꾸고 "뛰느냐 마느냐"는 바꾸지 않는다. Charger는 여전히 능동적으로 달려들고, 다만 쏘기 쉽고 피하기 쉬워질 뿐 멍하니 서 있는 바보가 되지는 않는다. 모든 변경은 dynamic_ai_difficulty.cfg의 6단계 안에 있고 플러그인 소스와 동작은 그대로이며, 음리 단계(level6) 값은 플러그인 기본값과 완전히 같아 이전과 동일하다.
  • 측면 연점프 (가성비 최고): ai_charger3_bhop_strafe_mindeg / maxdeg가 6단계로 8~18 / 14~28 / 20~38 / 25~46 / 28~52 / 30~55도. 진폭은 tan(random(mindeg, maxdeg))이라 30~55도는 0.58~1.43, 8~18도면 0.14~0.32만 남아 접근 경로가 거의 직선이 된다. 속도는 전혀 줄지 않는다. 사격선을 피하는 뱀 궤적만 사라지므로 에임이 좋으면 들어오는 길에 잡을 수 있다. ai_charger3_bhop_strafe_mindist 800 / 700 / 600 / 500 / 420 / 400과 합치면 낮은 단계는 "멀리서만 흔들고 800 안으로 들어오면 직선"이 된다.
  • 연점프를 멈추는 거리: ai_charger3_bhop_min_dist 170 / 150 / 130 / 115 / 105 / 100. 이보다 가까우면 APPROACH에서 BAIT로 넘어가며 연점프를 멈춘다. CHARGE_GUARANTEED_MAX_DIST(110)가 이미 명중 보장 하한을 막고 있어서, 이 값을 키워도 170에서 돌진하게 되지는 않는다. 마지막 60 단위를 220 달리기 속도로 오게 될 뿐이고, 플레이어는 얼굴로 한 번에 날아오는 대신 눈에 보이는 도움닫기 시간을 얻는다.
  • 시야 없는 연점프: ai_charger3_bhop_no_vision 0 / 1 / 1 / 1 / 1 / 1, ai_charger3_bhop_nvis_maxang 35 / 35 / 50 / 65 / 80 / 90. 쉬움 단계는 가속 전에 시야를 요구한다. 시야가 없으면 executeGroundBhop이 곧바로 반환해 원래의 지상 이동으로 돌아가므로 상태 기계가 막히지 않는다. 없어지는 것은 "보이지 않는 모퉁이 뒤에서 400 속도로 튀어나오는" 그림이고, 폭발력은 이제 눈에 띈 다음부터 시작된다.
  • 공중 선회: ai_charger3_airvec_modify_min_deg / max_deg 65~70 / 60~74 / 55~78 / 50~83 / 47~89 / 45~89도. 방향 보정은 이 각도 띠 안에서만 작동한다. min을 올리면 작은 회피는 따라가지 않고, max를 낮추면 큰 편차는 보정을 포기한다. 쉬움 단계는 65~70도라는 좁은 띠만 남아 뛴 방향으로 거의 그대로 날아가므로 착지 지점을 옆으로 비켜 피하기 쉽다. S자가 경로로 복귀하는 처리는 이 min이 아니라 AIR_CORRECTION_MIN_TARGET_DEG(1.0)을 쓰므로 곡선 자체는 영향을 받지 않는다.
  • 속도 축도 한 칸 더: ai_ChagrerBhopSpeed 30/40/50/60/80 → 20/30/42/55/70, ai_charger3_bhop_max_speed 400/400/400/500/700 → 330/360/390/450/600, ai_charger3_bhop_first_hop_ratio 0.2/0.3/0.4/0.5/0.6 → 0.0/0.15/0.3/0.45/0.55. 쉬움 단계의 첫 점프 0은 달리기에서 바로 뛴 첫 점프가 방향만 바꾸고 가속은 받지 않는다는 뜻이다. 음리 단계는 세 항목 모두 유지.
  • 건드리지 않은 것: ai_charger3_bhop_before_charge는 전 단계 1 유지. 끄면 CHARGING이 (commitDist, bhopMinDist + 50] 허용 구간에서 APPROACH로 되돌아가 APPROACH ↔ CHARGING이 눈에 띄게 왔다 갔다 할 위험이 있는데, 얻는 것은 돌진 시작 거리가 약 150에서 약 110으로 줄어드는 것뿐이라 수지가 맞지 않는다. 한 단계 더 깎고 싶다면 이것부터 시험해 보면 된다.
  • 재컴파일 불필요.

2026년 9월 8일 Charger 근접 후퇴 점프 정상화 (ai_charger3 1.0.1.15)

  • 증상: 근접 무기를 든 생존자에게 연점프하며 다가가면, 공중에서 수평 속도가 갑자기 사라지고 수직으로 떨어졌다. 착지 후 후퇴 점프 속도도 일반 플레이어가 낼 수 없는 값이었다.
  • 공중 급정지: Approach에서 근접 대치 구간으로 들어가거나, Bait에서 후퇴 점프가 아닌 공중 프레임이 TeleportEntity로 수평 속도를 0으로 만들었다. 플레이어는 공중에서 400 속을 한순간에 버릴 수 없어서 물리가 사라진 것처럼 보였다. 이제 그 쓰기를 없애고, 현재 점프의 운동량을 유지한 채 착지한 뒤에 후퇴 점프한다.
  • 후퇴 점프 속도: 생존자 추격 속도 × 1.2를 달리기 속도에 더해 한 번에 400 이상이 나왔다. Path / Direct 연점프와 같은 식으로 맞춘다. 이미 멀어지는 성분은 남기고, 대상 쪽으로 날아오던 속도는 방향 전환 때 버리며, 착지 가속도를 더한 뒤 연점프 상한으로 자른다.
  • 재컴파일: ai_charger3.smx.

2026년 9월 8일 Charger 근접 대치 시 경련과 무한 교착 수정 (ai_charger3 1.0.1.14)

  • 증상: Charger 체력이 낮고 생존자가 근접 무기를 든 상황에서, Charger가 대치 구간 안에서 "한 번 움직이고 즉시 정지"를 반복하며 경련하고, 대치가 끝없이 이어졌다.
  • 경련의 직접 원인: maintainMeleeBaitDistance가 bang-bang 제어였다 — baitZoneMin보다 가까우면 전속 후퇴, baitZoneMax보다 멀면 전속 전진, 그 사이 35 유닛 불감대에서는 매 틱 TeleportEntity로 수평 속도를 0으로 만들었다. 오차가 1 유닛이든 30 유닛이든 runTopSpeed를 쓰기 때문에 3~5틱이면 구간 전체를 가로지르고, "전속으로 튀어나감 → 속도 즉시 0 → 생존자가 한 발 압박 → 다시 튀어나감"으로 수렴했다.
  • 거리 제어를 히스테리시스 + 비례 속도 + 최소 유지로 변경: 일단 움직이기 시작하면 구간 중심까지 간 뒤에야 손을 놓는다(방향 전환에는 구간 절반을 가로질러야 한다). 속도는 중심까지의 오차에 비례하며 BAIT_MOVE_SPEED_RAMP에 도달해야 전속, 하한은 35%다. 같은 방향을 최소 BAIT_MOVE_HOLD_TIME(0.25초) 유지한다.
  • 불감대에서 속도를 0으로 만들지 않고 매 틱 0.7배로 감쇠시켜, 급정거가 아니라 미끄러지듯 멈추게 했다.
  • 후퇴 점프 착지 후 0.45초 동안 잠가 거리 제어가 곧바로 전진으로 돌아서지 못하게 해서, "튀어나감 → 착지 즉시 전진 → 다시 너무 가까움 → 다시 튀어나감"의 매 프레임 충돌을 끊었다.
  • 긴급 임계값 역전: 긴급 후퇴선이 melee_range + 20(90)으로 고정되어 대치 구간(85~120) 안쪽에 들어와 있었다. 구간의 가장 안쪽 5유닛은 항상 "긴급" 상태여서 그 구간에서는 후퇴 점프가 불감대를 덮어썼고, 85까지 물러난 순간 다시 후퇴하라는 판정을 받았다. 이제 긴급선을 구간 안쪽(melee_range + min(20, baitZoneMinRange × 0.5) = 77.5)으로 내려, 35유닛 구간 전체에서 실제로 버틸 수 있게 했다.
  • 최후 보루인 강제 돌진이 한 번도 작동하지 않았음: isTargetVulnerable의 ai_charger3_bait_max_duration(7초) 보루가 Bait 상태의 getStateDuration에 걸려 있는데, 상태 startTime은 transitionTo마다 초기화된다 — 후퇴 점프 한 번이면 Bait 이탈선을 넘고, Bait → Approach → Bait를 한 바퀴 도는 것만으로 타이머가 0이 되어 교착이 무한히 지속됐다. 이제 교착 타이머를 "대상 + 연속된 근접 대치"에 묶어, 두 번의 근접 대치 간격이 2초 미만이면 계속 누적되므로 Approach ↔ Bait 왕복으로는 초기화되지 않는다.
  • 저체력 Charger의 선택지: shouldChargeInMeleeBait의 세 경로 중 "체력이 근접 즉사선보다 높음"은 너프 이후 항상 거짓이고, isTargetVulnerable도 근접 대상에 대해 같은 체력 판정을 품고 있어 남는 건 "생존자가 시선을 돌림"뿐이었다. isTargetVulnerable에 범용 공격 간격 판정이 있기는 하지만 모든 무기에 "다음 공격 > 현재 + 0.5초"라는 하나의 임계값을 쓰므로, 사용 가능한 창은 (무기 스윙 간격 − 0.5초)가 된다. 스윙 간격이 짧을수록 창은 좁아지고, 빠른 근접(스윙 간격 0.5초 이하)에서는 창 너비가 0이라 한 번도 발동하지 않는다. 이제 헛스윙 후딜 응징 경로를 추가해 근접 대상에는 0.2초 임계값을 써서, 모든 근접 무기가 실질적인 창을 남기고 저체력 Charger에게도 실력으로 들어갈 기회를 준다.
  • 교착 단계적 대응: ai_charger3_bait_max_duration을 넘기면 페인트 → 강제 돌진 → 타깃 교체 순으로 올린다. 페인트는 안쪽으로 밀고 들어가 공격을 유도하고(긴급선까지 붙으면 일반 로직에 돌려줘 후퇴 점프로 빠진다), 목적 없는 들락날락을 리듬 있는 의도적 속임수로 바꾼다. 헛스윙은 위의 응징 경로가 받아친다. 페인트 횟수를 다 쓰면 그냥 돌진한다 — 한 명 넘어뜨리고 죽는 편이 제자리에서 떠는 것보다 이득이다. 능력이 쿨다운인 것은 "아직 못 한다"일 뿐 막힌 게 아니므로, 쿨다운을 이유로 타깃을 바꾸지 않는다.
  • 신규 cvar:
  • ai_charger3_melee_bait_orbit(기본값 1): 구간 안에서 제자리에 서 있는 대신 대상 주위를 접선 방향으로 돌아, "제자리 경련"을 "빈틈을 노리며 주위를 도는" 움직임으로 바꾼다. 도는 방향이 벽에 막히면 방향을 뒤집어 재시도하고, 양쪽 다 위험하면 제어를 네이티브 경로 탐색에 넘기지 않고 그 자리를 지킨다(넘기면 PathFollower가 Charger를 생존자에게 직행시켜 오히려 더 나빠진다). 0으로 두면 기존의 제자리 정지로 돌아간다.
  • ai_charger3_melee_bait_stalemate_switch(기본값 0): 마지막 단계로, 능력은 준비됐는데도 돌진이 통하지 않을 때(높이 도달 불가 / 경로 차단) 다른 생존자로 바꾼다. 기본은 꺼져 있으니 앞의 항목들 체감을 먼저 확인하고 결정하면 된다.
  • ai_charger3_melee_bait_blacklist_dur(기본값 10.0): 교착으로 포기한 대상을 타깃 선택에서 제외하는 시간. 이게 없으면 L4D2_OnChooseVictim이 거리순으로 같은 근접 생존자(대개 가장 가까운 사람)를 곧바로 다시 고르고, 타깃 교체 자체가 새로운 떨림이 된다. 블랙리스트는 선호도일 뿐이며 최후 수단인 getClosestSurvivorAndValid는 무시하므로 대상이 없어지는 일은 없다.
  • 재컴파일: ai_charger3.smx.

2026년 9월 4일 스폰이 Director 폴백으로 빠지는 문제 수정 (infected_control + anne_spawn_accel 1.5.0)

  • 문제: 2026-09-02.1 이후 다수의 특수 감염자가 Director API 폴백으로 스폰되고 위치가 흩어졌다. 빈 클라우드 서버 2대에서 5% 매트릭스 A/B로 재현·특정: 기본 한도에서는 차이가 작았지만(클라우드8 1.24% vs 0.88%, 클라우드4 4/480 vs 0/480), 이전 웨이브의 생존 특수가 장부를 차지한 실서버 상태에 맞춰 한도를 조이면 폭증——클라우드8 SI_target_limit_manual 2에서 12.78% vs 기준 1.94%, 클라우드4 같은 설정 80/480(16.7%) vs 기준 0/480. 단일 변수 분리: target_override를 2.27로 되돌리면 오히려 악화(14.44%), infected_control만 되돌리면 2.22%로 회복. 원인은 infected_control의 새 "잠금 대기 예약"이 한도를 즉시 소진하고, SpawnAccel_CollectEligibleTeamTargets()가 용량 기준으로 owner를 빌 때까지 제외하며, spawn_core.inc가 owner 집합이 비면 SpawnSearch_Exhausted를 조용히 반환(로그 없음, ownerCapacity 항상 0)하는 것. TryNormalSpawnOnce는 매 프레임 band++하여 10프레임 내 Director 단계 도달. 3초 inf_spawn_nav_band_timeout 경로는 관여하지 않았다.
  • 확장 anne_spawn_accel 1.5.0: 팀 스냅샷은 여전히 전원 기준으로 구축·캐시하되 생존자별 유향 거리 레이어를 추가 보관. 새 native AnneSpawn_NavCandidatesCollectTeamEx(..., excludedClients, excludedCount)는 한도에 도달한 생존자를 넘기면 각 행을 나머지 생존자 중 최근접으로 재계산하고 제외자만 도달 가능한 행은 건너뜀. 제외 집합 변화로 스냅샷 재구축이나 Pending은 발생하지 않음. 전원 제외 시 제외 없음으로 처리. Linux 32비트 .so는 pve의 Docker(Ubuntu 22.04 + hl2sdk-l4d2 + SM 1.12 + AMBuild)로 빌드, nav_graph_test 통과. 첫 빌드는 새 GCC의 libgcc를 동적 링크해(GCC_7.0.0 심볼 버전 필요) Valve 번들 구형 bin/libgcc_s.so.1을 강제하는 srcds에서 dlopen이 실패했고 sm exts unload/load는 상주 중인 1.4.4 이미지를 조용히 재사용했다. AMBuilder에 -static-libgcc를 추가해 재빌드(libgcc_s 의존 없음, readelf -V에 GCC_*/GLIBCXX_* 요구 없음); 확장 교체 후에는 반드시 sm exts info로 버전을 확인하고 필요하면 컨테이너를 재시작. Windows .dll은 재빌드하지 않음(구 확장에서는 플러그인이 자동으로 "제외 없음"으로 복귀).
  • infected_control.smx 2026-09-04.1: 팀 owner 집합은 생존/이동 가능만 보고 용량은 무관; 제외 목록은 EnsureCursor에서 계산하고 변화 시 페이징만 재시작; 핫 루프는 명시적으로 제외된 owner만 건너뛰고 IsAtCapacity를 직접 보지 않음; IsAtCapacity(하드)는 target_override 실제 장부만 참조, 예약은 ChooseTargetSurvivor 가중치에만 반영(TTL 2.5→1.5초, 제어 클래스만 기록); commit이 팀 집합 캐시를 무효화하지 않음; 조기 종료 경로에 [FIND EARLY-EXHAUSTED] 스로틀 로그와 카운터 추가.
  • 검증: 클라우드4(4맵 × 10 진행 지점, 12 SI)는 기본/조인 한도 모두 480/480 유향 Nav 직접 스폰, Director 0회, 평균 245ms(기준 249ms), 라이브 버전은 같은 설정에서 Director 80회; 클라우드8 전량 1083격(docs/infected_control_refactor/NAV_WAVE_MATRIX_20260904_TEAM_EXCLUDE_FULL.md)은 12984/12984 누락 0, Nav 99.445%, Director Range 118→72, [FIND EARLY-EXHAUSTED]와 ownerCapacity 전 구간 0, 평균 364.6→332.4ms. 위 로딩 문제로 두 라운드는 실제로 확장 1.4.4에서 실행되어 구 확장의 "제외 없음" 폴백 경로에서 플러그인 측 수정을 검증한 것이다. 재빌드한 확장 1.5.0은 클라우드8에서 검증 완료(docs/infected_control_refactor/NAV_WAVE_MATRIX_20260904_TEAM_EXCLUDE_EXT150.md): 임시 파일명 로드와 정식 경로 덮어쓰기+docker restart 후 모두 sm exts info가 1.5.0; 한도 조인 재현 480/480 유향 Nav·Director 0회(라이브 버전 80/480), 전량 1083격 Nav 12906 / Director 77·평균 359.8ms로 1.4.4 라운드와 노이즈 범위 내 동등, 08-26 기준 대비 개선은 전부 유지. 기본 한도에서는 1인당 5자리라 아무도 차지 않아 제외 목록이 항상 비므로 이 매트릭스는 1.5.0의 무회귀만 증명하고 고속 경로 이득은 측정할 수 없다. 1.5.0에서는 제외된 owner의 후보가 native 안에서 재귀속되므로 핫 루프 ownerCapacity가 0인 것이 정상이며 새 [TEAM EXCLUDE] 로그를 본다. 테스트 후 클라우드4/8은 운영 파일 해시 검증 복원, cvar 초기화 완료.
  • 웨이브 타이밍(킬 페이즈 + 독립 16초 카운트다운 + Anti-Bait)은 이번과 09-02 모두 변경하지 않음. "고정 24초"로 느껴진 것은 먼 곳 폴백 스폰이 늦게 죽어 조기 시작 조건이 충족되지 않은 부작용.
  • scripts/run_nav_wave_matrix.py: 워치독이 srcds를 재시작한 뒤 infected_control과 픽스처만 다시 로드하고 SI_Target_limit/l4d_target_override를 조용히 빠뜨렸다(클라우드8 첫 전량 실행이 이 때문에 무효화되어 재실행). 두 플러그인을 동반 플러그인으로 추적해 시작 시 실행 중이던 것은 복구 시 infected_control 앞에 로드 순서대로 다시 로드한다.
  • 재컴파일: infected_control.smx; 재빌드: anne_spawn_accel.ext.2.l4d2.so(-static-libgcc).

2026년 9월 3일 특수 감염자 연속 점프: 첫 점프 가속 비율 + 사다리 꼭대기 착지 오판 수정

  • "첫 점프 가속 비율" cvar 신설: ai_charger3_bhop_first_hop_ratio, ai_tank3_bhop_first_hop_ratio, ai_BoomerBhopFirstHopRatio, ai_SpitterBhopFirstHopRatio (0.0–1.0). 달리기에서 바로 뛴 첫 점프는 가속도 × 비율을 받고, 착지 직후 재점프는 여전히 가속도 전량을 받습니다. 0은 이전 항목의 "첫 점프 가속 없음", 1은 기존 동작과 동일. 공용 AIPathMovement_GetHopImpulse에 선택 인자 firstHopRatio 추가(기본 0, 호환 유지).
  • 이 조절값이 필요한 이유: 첫 점프 무가속으로 생기는 체인 시작 지연은 ≈ 0.75초 × (1 − 비율) × (연속 점프 상한 − 달리기 속도)로 가속도 크기와 무관하므로 BhopSpeed / impulse로는 보상할 수 없습니다. 높은 단계는 상한이 높아 오히려 가장 많이 깎였습니다(음리 단계 Charger 0.48초, Boomer 0.62초 vs 쉬움 0.28 / 0.53초) — 난이도 계단이 뒤집혀 있었습니다.
  • 동적 AI 난이도 dynamic_ai_difficulty.cfg 6단계: Charger·Boomer·Spitter는 공통 0.2 / 0.3 / 0.4 / 0.5 / 0.6 / 0.8, Tank은 0 / 0.2 / 0.4 / 0.6 / 0.8 / 1.0(Tank은 영향이 가장 적은 직업으로 체인이 한 점프만 밀리므로 음리 단계는 기존의 첫 점프 전량 가속을 유지). 보정 후 지연은 단계가 오를수록 단조 감소(음리: Charger 0.10초, Tank 0초, Boomer 0.12초, Spitter 0.12초). Tank을 제외하면 최상위 단계도 1.0을 주지 않아 Boomer / Spitter 추진력 250의 "첫 점프 급가속"이 되돌아오지 않습니다. 플러그인 기본값은 음리 단계에 맞춰 0.8 / 1.0 / 0.8 / 0.8.
  • 사다리 꼭대기 dismount가 착지로 오판되는 문제 수정: AIPathMovement_NotifyGrounded(client, onLadder)가 직전 등록이 사다리 위였는지 기억하고, 사다리 꼭대기에서 엔진이 수행하는 짧은 dismount(사다리도 지면도 아닌 구간) 이후의 접지는 착지로 치지 않습니다. 사다리를 다 오른 뒤 첫 점프가 가속도를 공짜로 받지 않습니다. 네 플러그인 모두 onLadder를 전달.
  • ai_boomer_3를 3.0.10으로 업데이트: 지면/사다리 등록을 m_customAbility 무효 시 조기 return 앞으로 이동(누락된 프레임 때문에 지면에 서 있는 Boomer가 공중 착지로 오판되는 것을 방지); round_start에서도 연속 점프 체인 초기화.
  • ai_spitter_3를 3.0.9로 업데이트: player_spawn을 후킹해 연속 점프 체인 초기화(Boomer / Charger와 정렬).
  • ai_charger3를 1.0.1.13, ai_tank3를 1.0.0.13으로 업데이트: Path / Direct / 견제 후방 점프(Charger)와 일반 / Path 연속 점프(Tank) 이륙 후 AIPathMovement_MarkHopStart를 호출해 착지 허가를 소비(Boomer / Spitter와 동일).
  • 재컴파일: ai_charger3.smx, ai_tank3.smx, ai_boomer_3.smx, ai_spitter_3.smx.

2026년 9월 3일 특수 감염자 연속 점프: 첫 점프에서 가속하지 않음

  • 원인 파악: 공유 공중 보정 ai_path_movement.inc 자체는 절대 가속하지 않는다(속도를 이륙 속도 이하로만 받쳐 주고, 회전 시에는 오히려 감속). 네 종류 특수 감염자에서 "첫 점프가 공중에서 갑자기 빨라진다"고 보인 것은 전부 이륙 프레임 때문이었다: Path / Direct 이륙이 속도를 달리기 속도 + 가속도 한 번분으로 그대로 바꿔 넣었다(Charger 250→350, +40%; Boomer 175→325, +86%, 게다가 ClientPush가 250 이하의 속도를 251까지 강제로 끌어올림). 사람의 버니합처럼 착지 때 늘어나는 방식이 아니었다.
  • ai_path_movement.inc에 연속 점프 체인 추적 추가: AIPathMovement_NotifyGrounded를 지상(또는 사다리) 프레임마다 가능한 한 빨리 호출하고, 지면을 떠난 뒤 0.1초 이상 지나 다시 닿은 시점을 착지로 등록한다. 플러그인 점프, 엔진 점프, 경로를 따라 낙차를 걸어 내려온 경우를 구분하지 않으며 높은 곳에서의 낙하도 착지로 치고 체공 시간 상한은 두지 않는다——낙하로 얻은 속도는 그대로 유지되고 착지 점프에서 가속도가 추가된다. AIPathMovement_GetHopImpulse는 "착지 후 0.15초 이내에 다시 점프"할 때만 가속도를 온전히 돌려주고, 지면에서 바로 뛴 첫 점프에는 0을 돌려주어 방향만 바꾼다. AIPathMovement_MarkHopStart는 이륙마다 착지 허가를 소비한다(BeginPathHop 내부에서 자동 호출). 엔진이 점프를 거부하면 다음 프레임에도 지면에 서 있고 한 번도 떠나지 않았으므로 착지로 치지 않아 제자리 재시도가 매 프레임 가속도를 받는 일이 없다. 사다리는 지면으로 취급하여 사다리를 오른 뒤 첫 점프는 가속하지 않는다.
  • ai_charger3를 1.0.1.12로 업데이트: Path / Direct 지상 이륙을 착지 가속으로 변경, 돌격 전 마지막 점프도 이륙으로 등록해 접근 상태로 돌아간 뒤 체인이 이어진다. 새 Charger / 리스폰 시 체인 리셋. 디버그 로그에 지상 속도와 착지 점프 여부 추가.
  • ai_tank3를 1.0.0.12로 업데이트: 일반 점프(전/후/측면 임펄스)와 Path 선행 점프 모두 착지 가속으로 변경. 라운드 시작과 접속 시 체인 리셋.
  • ai_boomer_3를 3.0.9로 업데이트: 추진력을 착지 시에만 추가. 이륙 속도를 먼저 구성해 경로 검사와 Teleport에서 같은 값을 사용(기존 경로 검사는 추진력 벡터만 보았기 때문에 추진력 없는 첫 점프에서는 길이 0 검사로 퇴화했을 것). 최저 점프 속도 251은 실제로 추진력을 더하는 점프에서만 적용되어 첫 점프에 몰래 속도를 더하지 않는다.
  • ai_spitter_3를 3.0.8로 업데이트: 추진력을 착지 시에만 추가. spitterDoBhop은 경로 검사를 통과한 이륙 속도를 그대로 써 넣고 Teleport 전에 다시 계산하지 않는다.
  • 네 플러그인의 가속도 cvar 설명을 "착지 직후 재점프에서 추가, 달리기에서 바로 뛴 첫 점프에는 없음"으로 통일. 가속 체인이 한 단계 뒤로 밀리는 것뿐이며 상한, 공중 보정, 회전 감속 로직은 변경 없음.
  • 재컴파일: ai_charger3.smx, ai_tank3.smx, ai_boomer_3.smx, ai_spitter_3.smx.

2026년 9월 2일 특수 감염자 타겟 상한 연동 수정 (infected_control / SI_Target_limit / l4d_target_override)

  • infected_control.smx를 2026-09-02.1로 업데이트. 러너(단독 질주) 상태가 켜질 때만 외부에 알리던 회귀를 수정: 해제 시(팀 복귀, 웨이브/라운드 리셋, 기믹 이벤트)에도 OnDetectRushman(0)을 보내므로 SI_Target_limit가 한 번의 감지 후 맵이 끝날 때까지 "전원 상한 해제" 상태에 머무르지 않는다. 해제에는 확인 창을 두었다: 새 cvar inf_antibait_runner_off_confirm(기본 2.3초)로, 러너가 판정 임계값 안으로 돌아온 것만으로는(격차 축소, 팀원이 따라붙음) 그 시간 동안 지속되어야 해제되어 팀원이 잠깐 따라붙고 다시 벌어질 때 타겟 상한이 반복적으로 켜졌다 꺼지는 것을 막는다. 러너의 사망/기절/구속/접속 종료는 여전히 즉시 해제. 웨이브 리셋이 gAB.runner만 지우고 gST.bPickRushMan을 남기는 일도 없어져 두 러너 상태가 일치한다.
  • 스폰 측 용량 판정을 "남은 자리" 기준으로 변경: 아직 ChooseVictim을 거치지 않은 새 스폰은 유도 대상에 예약 자리를 기록(장부에 등록되거나 2.5초 후 해제)하여 같은 프레임의 연속 스폰이 전부 한 생존자에게 향하지 않는다. 무작위 대상 선택은 남은 자리로 가중치를 두어 선두 플레이어의 ×2 상한이 자연스럽게 더 많은 압박을 받는다.
  • 텔레포트 보충 클래스가 생존 자리를 모두 소진한 경우(일반 큐나 내통자 예약이 먼저 차지) 자리가 남은 클래스로 대체. 모든 클래스가 가득 차면 1초 백오프하고 매 프레임 헛돌지 않는다.
  • SI_Target_limit.smx를 1.9로 업데이트. 새 cvar SI_target_rushman_scope(기본 1): 러너 감지 중에는 러너 본인의 개인 상한만 해제(SetTargetedCap)하고 나머지 생존자는 정상 보호를 유지. 0이면 예전 동작(전원을 SI 총 상한까지 올림). SI_target_enable이 실제로 동작함(끄면 target_override의 targeted 옵션을 비우고 모든 native가 "제한 없음"을 반환). 재계산은 한 번 계산·한 번 전송으로 바꿔 클래스 단위 옵션을 66개 슬롯마다 보내지 않음. 수동 모드도 초기화 시 전송. survivor_rescued / defibrillator_used / player_team / 생존자 player_spawn 트리거를 추가하고 매초 검증하여 가동 인원 변화나 target_override 설정 재로드(sm_to_reload는 targeted를 0으로 되돌림)로 생긴 어긋남을 자동 보정. infected_control 재로드 후 l4d_infected_limit를 이중 훅하지 않음. si_target_limit.inc의 라이브러리 이름을 RegPluginLibrary와 같은 소문자로 통일.
  • l4d_target_override.smx를 2.28로 업데이트. ChooseVictim에 post detour 추가: pre 단계가 결정을 게임에 맡긴 경우(type=1에서 시야 내 대상 없음, 클래스가 오버라이드 대상 아님, forward가 Handled 반환)에도 엔진이 고른 피해자를 targeted 장부에 등록하여 GetValue(VALUE_INDEX_TOTAL)의 상시 과소 보고를 해소. SetLastVictim으로 등록한 대상은 wait/dist 의미를 유지하고 다음 호출에서 버리고 다시 고르지 않음. forward가 Plugin_Handled를 반환할 때의 ArrayList 핸들 누수 수정.
  • 재컴파일: infected_control.smx, SI_Target_limit.smx, l4d_target_override.smx.

2026년 9월 2일 spechud 탱크 패널 수정

  • spechud를 3.10.1로 업데이트. 탱크 HUD 패널은 관전자와 감염자 플레이어에게만 전송하며, 이전에 versus_coop용으로 추가했던 생존자 수신 분기를 제거. 팀 변경 시 CancelClientMenu로 패널을 회수하려던 코드도 제거했다——이 SourceMod 함수는 서버 측 상태만 지우고 클라이언트에는 아무것도 보내지 않아 radio 패널은 3초 타임아웃까지 그대로 남으므로 실제 효과가 없었다.
  • AI 탱크 대상이 항상 "탐색 중"으로 표시되던 문제 수정. 기존에는 l4d_target_override가 기록한 피해자만 읽었지만, 탱크의 타겟 변경은 실제로 Left4DHooks의 L4D2_OnChooseVictim(ai_tank3, SI_Target_limit가 사용)을 통하므로 기록이 비어 있는 경우가 많았다. 이제 spechud가 직접 L4D2_OnChooseVictim을 수신해 각 탱크의 최근 대상을 userid로 캐시하며, target_override에 기록이 있으면 그쪽을 우선한다.
  • 탱크 패널 문구를 모두 번역 파일로 옮기고 수신자별 클라이언트 언어로 패널을 생성. 서버 기본 언어가 chi라서 일치하는 언어가 없는 플레이어에게는 중국어가 표시된다. en/chi/zho/jp/ko/vi/es 7개 spechud.phrases.txt에 구문 추가.
  • scripts/spcomp-docker.sh를 bind mount 의존에서 tar 스트리밍 방식(소스는 stdin으로 전송, .smx는 stdout으로 수신)으로 변경. 기본적으로 SSH를 통해 pve-yma 호스트에서 spcomp64를 네이티브 실행하며(SPCOMP_SSH_HOST로 변경), SPCOMP_BACKEND=docker로 컨테이너 실행 전환 가능. 로컬 Docker 데몬이 없는 Mac에서도 컴파일된다.
  • 재컴파일: spechud.smx.

2026년 9월 2일 Scripted HUD 표시 언어 메뉴

  • l4d2_scripted_hud를 1.7.0으로 올렸습니다. !hud 메뉴에 「언어」 항목이 추가되었고 !lang / !hudlang으로 바로 열 수도 있습니다. 게임 언어 따르기, English, 간체 중국어, 번체 중국어, 일본어, 한국어, 베트남어 사이를 전환합니다. languages.cfg에서 서버가 실제로 아는 언어만 표시하므로, 골라도 아무 일도 일어나지 않는 항목은 나오지 않습니다.
  • 선택 값은 SteamID별로 scripted_hud_prefs의 새 lang_code 열과 별도 쿠키 l4d2_scripted_hud_lang_v1에 저장하며, 다른 HUD 설정과 같은 revision 병합 규칙을 사용해 서버 이동·재접속·데이터베이스 일시 중단 뒤에도 유지됩니다.
  • 언어는 SetClientLanguage로 적용하므로 HUD와 HUD 메뉴는 물론, 다른 플러그인이 해당 플레이어에게 보내는 채팅 번역도 함께 바뀝니다. SourceMod는 클라이언트가 설정을 바꿀 때마다 cl_language를 다시 읽으므로, 해당 플레이어의 HUD를 만들 때마다 이 덮어쓰기 값을 확인하고 다시 적용합니다.
  • !hud의 「기본값으로 되돌리기」는 언어를 일부러 초기화하지 않습니다. 메뉴 자체가 어떤 언어로 보일지 정하는 설정이라 지워지면 되돌리는 길을 읽지 못할 수 있기 때문입니다. 되돌리려면 언어 메뉴의 「게임 언어 따르기」를 고르세요.
  • 알려진 제한: HUD2의 모드 태그([普通药役] 등)와 AI 난이도 이름은 여전히 전역 공유 하드코딩 중국어라 이 설정을 따르지 않습니다.
  • docs/plugin_commands.md에 sm_hudlang / sm_lang을 추가했습니다.
  • 재컴파일: l4d2_scripted_hud.smx.

2026년 9월 1일 AI Smoker 즉시 해제 쿨다운 및 후퇴 수정

  • ai_smoker3를 1.0.1.4로 올렸습니다. 적중한 혀가 잘리거나 즉시 해제되면 Actions 확장이 남아 있는 SmokerTongueVictim을 끝내고 원래의 SmokerRetreatToCover를 유지한 채, Action 콜백 스택 밖에서 피해자 반대 방향의 Nav 이동 명령을 내립니다. 혀가 준비되면 원래 AI로 돌아갑니다. 이벤트는 한 프레임 뒤 m_tongueVictim이 비었는지 확인하므로 정상 조르기 전환을 즉시 해제로 오인하지 않습니다.
  • Anne, Hardcore, Shotgun, Alone 모드에서 Zonemod의 l4d2_tongue_timer.smx를 다시 로드합니다. 생존자 즉시 해제는 최소 4초입니다. Tank 피해 해제는 Anne 계열 설정에서 1초로 낮췄으며(상류 Zonemod은 8초 유지), AI Tank의 주먹이나 바위가 아군 Smoker를 크게 벌하지 않도록 했습니다. 빗나감은 계속 tongue_miss_delay를 사용해 쉬움~전문가 7초, 극한/음리 3초이며, 정상 적중 후 놓기는 tongue_hit_delay 13을 그대로 사용합니다.
  • l4d2_tongue_timer를 1.3-anne.1로 올렸습니다. 피해자마다 실제로 당기는 Smoker를 기록하므로 Tank 즉시 해제의 8초 쿨다운이 첫 번째 Smoker에게 잘못 적용되지 않습니다. 혀 하나가 풀릴 때도 해당 연결만 지워 여러 Smoker의 동시 당기기를 지원합니다.
  • 재컴파일: ai_smoker3.smx, l4d2_tongue_timer.smx.

2026년 9월 1일 Charger 선타격 후 돌진 키/견제 수정

  • ai_charger3를 1.0.1.11로 올렸습니다. 근접 발톱과 제압된 대상 구타를 다시 IN_ATTACK2로 되돌렸습니다(좌클릭은 돌진 능력). 이전 IN_ATTACK는 최대 2.5초 동안 서 있기만 하고 한 대도 나가지 않았습니다.
  • 총기 대상 콤보는 상대가 차저를 보지 않고, 차저 체력이 근접 즉사선보다 높으며, 재장전/밀치기 쿨이 아닐 때만 시작합니다. 체력 500인 차저가 총구 앞에서 가만히 서 있지 않습니다.
  • 두 대 예측은 두 번째 타격의 0.85초만 계산합니다. 확정 돌진 하한은 110으로 제한하고, 횡이동 속도 ≥ 150이면 commit 거리를 올리지 않습니다.
  • 발톱 공격 카운트에 유효한 m_flNextPrimaryAttack 기준값 상태를 추가해 초기값이 0이거나 발톱 무기를 잠시 찾지 못해도 첫 타격을 놓치지 않습니다. 발톱 사거리 속성 로드 실패는 맵마다 간격을 두고 최대 3회만 재시도해 매 프레임 native 호출과 Left4DHooks 오류 로그 반복을 막습니다.
  • 재컴파일: ai_charger3.smx.

2026년 9월 1일 2026-08 AI 밸런스 조정

  • ai_charger3를 1.0.1.10으로 올렸습니다. 차저 전용 CVar ai_charger3_air_speed_floor_ratio(기본 0.50)를 추가했습니다. Path / Direct 도약 시 공중 방향 보정에 등록하는 hopSpeed는 실제 도약 속도×이 비율이고, m_flLastHopSpeed는 실제 도약 속도를 그대로 둡니다.
  • 동적 AI 난이도 6단계에서 차저를 약화했습니다. 가속도 30/40/50/60/80/100, 상한 400/400/400/500/700/700, 공중 보간 0.10/0.15/0.20/0.25/0.30/0.40, 선회 감속 0.50/0.39/0.325/0.26/0.195/0.13, 보속 하한 0.50/0.60/0.65/0.70/0.80/0.80. 쉬움 단계 감속은 기존 하드 상한 0.50을 사용합니다.
  • ai_spitter_3를 3.0.7로 올렸습니다. ai_spitter3_air_spit(기본 0)을 추가했습니다. 극한과 음리만 1로 두어 AI 스피터의 공중 침 뱉기를 허용합니다. 쉬움~전문가는 공중 침을 금지하고, 지면 침은 그대로이며 플러그인이 점프를 붙이지 않습니다. 플레이어 스피터는 제한하지 않습니다.
  • 스모커 tongue_fly_speed는 6단계 모두 명시적으로 1000입니다(극한/음리는 1200에서 내림).
  • 재컴파일: ai_charger3.smx, ai_spitter_3.smx.

2026년 8월 31일 커밋 전 검토 수정

  • ai_charger3.smx를 다시 컴파일했습니다. 기존 산출물은 1.0.1.8에 머물러 있어서 1.0.1.9의 「두 대 친 뒤 돌진」, 총기 타깃용 명중 보장 구간 resolveChargeCommitDist, 제어형 상한 타깃 변경 게이트, 연속 점프 상한 등록이 모두 빠져 있었습니다. 소스에서는 이미 삭제한 확률 돌진이 그대로 돌고 있었습니다.
  • ai_boomer_3 / ai_spitter_3의 밀기 벡터 정규화에서 0으로 나누는 문제를 고쳤습니다. 수평 성분이 0일 때(타깃이 바로 위·아래, 시야 피치 ±90) 정규화가 순수 수직 벡터로 퇴화해 특감을 그대로 위로 쏘아 올립니다. 이번에 고치려던 「하늘 날기」의 또 다른 경로입니다. 공용 헬퍼 AIPathMovement_NormalizeHorizontal()을 추가해 영벡터면 이번 밀기를 건너뜁니다.
  • getChargerClawRange()의 캐시가 맵 전환 때 무효화되고, 범위 밖 값을 읽으면 캐시하지 않도록 했습니다. 기존에는 함수 내 static에 영구 캐시해서, 무기 스크립트 로드보다 첫 호출이 빠르면 70.0 폴백이 프로세스가 끝날 때까지 고정됐습니다.
  • spechud의 FillTankInfo를 고정 길이 배열로 바꿔, HUD 그리기 경로에서 패널을 만들 때마다 발생하던 new int[MaxClients] 힙 할당을 없앴습니다.
  • sm_navtest는 대상 생존자를 쓸 수 없을 때 GetTargetAnchorPos로 폴백합니다. 후보 지점을 자기 자신과 높이 비교(높이 점수가 항상 0)하던 문제를 없앴고, 초기화되지 않은 targetEye도 함께 고쳤습니다.
  • 특감 AI 플러그인 5개에서 #include <si_target_limit> 뒤에 #define REQUIRE_PLUGIN을 되돌려 ai_tank3.sp와 맞췄습니다. 선택적 의존은 이 include 한 줄로만 한정됩니다.
  • si_target_limit.inc가 l4d_target_override를 전이적으로 include할 때 호출 측의 REQUIRE_PLUGIN 상태를 유지하도록 했습니다. 서드파티 플러그인에 하드 의존이 조용히 붙지 않습니다.
  • 섹터 시스템 제거 후 infected_control.sp에 남아 있던 참조 없는 매크로 5개를 정리하고 PEN_LIMIT_SCALE_LO 주석을 바로잡았습니다.
  • .gitignore 중복을 정리하고 /test_results가 이미 덮는 9개 개별 경로를 삭제했습니다.
  • anne_server_redirect의 jp / ko / vi 번역을 추가해 다른 Anne 플러그인과 같은 6개 언어 범위로 맞췄습니다.
  • 문서 정정: README_SPAWN_ARCHITECTURE.md의 분산도 두 항목, annehappy_dynamic_ai_difficulty.md의 반응 시간 행·탱크 행·새로 추가된 연속 점프 상한 cvar 2개, ai_charger3/readme.md에서 확률 돌진을 그대로 설명하던 네 곳. 함께 비교표의 차저 연속 점프 속도도 정정했습니다(문서 45/60/75/90/150, 실제 설정 40/50/60/70/100).

2026년 8월 30일 AI 반응 시간을 전 난이도 통일

  • dynamic_ai_difficulty.cfg의 z_acquire_far_time / z_acquire_near_time을 쉬움~전문가 네 단계에서 5.0 / 0.5에서 0.0 / 0.0으로 바꿨습니다. 원래부터 0.0 / 0.0이던 극한 / 음리 단계와 같아집니다. 타깃 포착 지연은 더 이상 난이도 축이 아니며, 단계 차이는 모두 직업별 행동 cvar로 표현합니다.
  • 설정만 바뀌었고 재컴파일은 필요 없습니다. docs/annehappy_dynamic_ai_difficulty.md의 「반응 시간」 행도 함께 갱신했습니다.

2026년 8월 30일 고특 생성 후 배척 완화

  • infected_control.smx를 2026-08-30.1로 올렸습니다. 고특에서는 웨이브 후반이 「생성 후 배척」에 의해 근거리 대역에서 밀려났습니다. 핵밀도가 이미 생존자 코앞에 붙은 특감까지 점유원으로 넣고 특감 상한 스케일도 곱하지 않았으며, 하드 분산은 상한에 따라 줄어들어 완화해야 할 고특에서 오히려 더 조여졌습니다.
  • 생존자와의 거리가 400 미만인 생존 특감은 핵밀도 원천에서 제외합니다. 특감 상한 스케일은 감점값이 아니라 핵 반경(inf_spawn_kernel_radius × 계수)에 적용됩니다. 저특에서는 벌려 놓고 고특에서는 겹침만 막아, 곡선 전체가 평탄해져 분해능을 잃는 일이 없습니다. inf_spawn_kernel_points 기본값은 30에서 50으로 올렸습니다.
  • 섹터 분산 축은 일체 제거했습니다(recentSectors, 웨이브 내 섹터 쿼터, inf_spawn_sector_quota_bonus). 방향 선호는 연속적인 전술 기하 점수만으로 표현하며, 분산 점수는 핵밀도 한 항만 남았습니다.
  • 하드 분산은 고정 반경이 되어 특감 상한이나 웨이브 점유와 연동하지 않습니다. 이 판정은 「여기에 스폰해도 되는가」만 답합니다. inf_spawn_sep_radius 기본값은 120에서 100으로 낮췄습니다. 웨이브 시퀀스는 그대로입니다.

2026년 8월 30일 차저 총기 근접은 두 대 친 뒤 돌진

  • ai_charger3를 1.0.1.9로 올렸습니다. ai_charger3_bhop_min_dist(기본 100) 안의 총기 타깃은 명중이 보장된 거리로 보고, 확률 대기를 없애며 돌진 창을 36–95로 줄이지 않습니다.
  • 할퀴기 거리 안에서 두 대를 치는 동안 타깃이 빠져나가지 못하면 먼저 두 대를 친 뒤 돌진합니다. 손이 닿지 않거나, 뒤로 빠지고 있거나, 2.5초를 넘기면 바로 돌진합니다. 샷건은 100 안에서 즉시 돌진하는 기존 동작을 유지합니다. 근접 무기 견제는 그대로입니다.
  • 재컴파일: ai_charger3.smx.

2026년 8월 30일 트레이터 등록 취소 지원 및 트레이터 탱크 제거

  • infected_control.smx를 2026-08-30으로 올렸습니다. !itcancel(=!neiguicancel) 명령을 추가했습니다. 웨이브 해제 전이라면 언제든 트레이터 등록을 취소할 수 있으며, 등록된 상태에서 !it를 열면 메뉴에 「등록 취소」 항목만 표시됩니다. 취소된 슬롯을 AI로 즉시 보충하지 않습니다. 해제 창이 열린 후(SpawnAllowed)에는 취소할 수 없고 「창이 닫혔습니다」 안내가 표시됩니다.
  • 트레이터 클래스에서 탱크를 제거했습니다. 선택 가능한 클래스는 현재 스폰 풀이 결정하며 탱크는 더 이상 등록할 수 없습니다. inf_traitor_class_mask의 기본값과 상한은 127 그대로입니다(bit 64는 롤백 호환용으로만 남겨두었고 실제로는 적용되지 않습니다). z_max_player_zombies는 트레이터 탱크용 여분 슬롯을 예약하지 않습니다.
  • 선택 가능한 트레이터 클래스는 현재 스폰 풀과의 교집합으로 바뀌었습니다. inf_*_limit 0으로 꺼진 클래스는 등록할 수 없고, allcharger / allhunter 모드에서는 각각 차저 / 헌터만 등록할 수 있습니다.
  • inf_traitor_max_slots의 유효값은 l4d_infected_limit - 1로 클램프되어 웨이브마다 최소 1개의 AI 스폰 예산을 보장합니다. 트레이터 자동 연속 점프는 OnPlayerRunCmd 채널로 이동했습니다(내부 리팩터링, 동작 동일). usage와 메뉴 문구는 6개 언어 번역을 동기화했습니다.
  • 버전 롤백 설정 Anne26-07 / Anne25-11 / Anne25-10에 smart_ai_rock.smx 로드를 보충했습니다(기존에는 unloadall.cfg가 언로드한 뒤 롤백 버전에서 탱크 스마트 투석이 사라졌습니다).

2026년 8월 30일 탱크 난이도별 연속 점프 상한 하향

  • 동적 AI 난이도의 ai_tank3_bhop_max_speed를 단계별로 200씩 낮추고 상한 800으로 클램프했습니다: 쉬움 500, 보통 600, 어려움 700, 전문가 800, 극한 800(850이 상한 도달); 음리 단계는 1000을 유지합니다. 설정만 변경(dynamic_ai_difficulty.cfg)이라 재컴파일이 필요 없으며, 플러그인 기본값(동적 난이도 미사용 시)은 1000 그대로입니다.

2026년 8월 29일 AI 특감 path 연속 점프 「하늘을 나는 대점프」 수정

  • path 연속 점프(버니합)에서 가끔 한 번의 점프가 비정상적으로 멀리, 하늘을 나는 것처럼 날아가는 문제를 수정했습니다. 근본 원인은 세 가지입니다. ai_spitter_3 / ai_boomer_3의 점프 가속이 「현재 속도에 더하기」 방식인데 상한이 전혀 없어 긴 직선 구간에서 점프마다 +100/+150씩 눈덩이처럼 불어났습니다. 공유 공중 보정이 기록된 점프 속도를 체공 내내 하한으로 유지했고, 벽에 부딪혀 엔진이 감속시켜도 피크 속도로 되돌렸습니다. 추진 벡터에 수직 성분이 포함되어(부머는 높은 경로 지점을 향해, 스피터는 시선 피치를 따라) 점프 자체의 Z 속도와 겹치면서 말 그대로 「하늘을 나는」 상태가 됐습니다.
  • ai_spitter_3를 3.0.6으로 올리고 ai_SpitterBhopMaxSpeed를 신설, ai_boomer_3를 3.0.8로 올리고 ai_BoomerBhopMaxSpeed를 신설했습니다. 더하기식 추진은 수평 속도 기준으로 상한 클램프되고, 추진 벡터는 수평 성분만 유지하며(수직 높이는 점프 본연의 몫), 도약 전 안전 검사는 실제 적용되는 속도와 같은 속도로 트레이스합니다.
  • 두 새 상한은 「차저 상한 +200」 규칙을 따릅니다. 플러그인 기본값은 1000(= ai_charger3_bhop_max_speed 기본 800 + 200)이며, 동적 AI 난이도 dynamic_ai_difficulty.cfg의 6개 단계에는 600 / 600 / 700 / 800 / 1000 / 1000을 설정했습니다(차저 각 단계 400 / 400 / 500 / 600 / 800 / 800에 대응).
  • 공유 모듈 ai_path_movement.inc: AIPathMovement_BeginPathHop에 최대 속도 인자를 추가했습니다. 공중 보정은 각 특감 자신의 연속 점프 상한을 넘는 속도를 유지하지 않으며, 외력으로 생긴 속도 스파이크도 체공 내내 보존되지 않습니다.
  • ai_smoker3를 1.0.1.3, ai_tank3를 1.0.0.11로 올렸습니다. 도약 프레임도 ai_smoker3_bhop_max_speed / ai_tank3_bhop_max_speed로 클램프합니다(기존에는 공중 프레임만 제한해 도약 순간에는 초과할 수 있었습니다). ai_charger3를 1.0.1.8로 올렸습니다. 지상/Direct 연속 점프에는 원래 상한이 있었고, 이번에 그 상한을 공유 공중 보정에 등록하도록 했습니다.
  • 재컴파일: ai_smoker3.smx, ai_boomer_3.smx, ai_spitter_3.smx, ai_tank3.smx, ai_charger3.smx. 자키는 원래 점프당 400+80 상한이 있고 헌터는 연속 점프 로직이 없어 변경이 필요 없습니다.

2026년 8월 30일 자키 밀치기 후 억제를 AI 난이도별로 단계화

  • ai_JockeyShovedCooldown을 동적 AI 난이도 시스템에 연결했습니다. dynamic_ai_difficulty.cfg의 6단계에서 각각 쉬움 3.0, 보통 2.5, 어려움 2.0, 전문가 1.5, 극한/네리 1.0초입니다. 값은 annehappy_dynamic_ai_difficulty.smx가 단계별로 적용하고 단계 전환 시 기준선으로 복원하므로 잔류하지 않습니다.
  • 플러그인 기본값 3.0은 쉬움 단계와 같은 강도이므로, 동적 난이도 플러그인을 실행하지 않는 모드는 자동으로 가장 관대한 설정이 됩니다.

2026년 8월 29일 AI 자키 밀치기 후 억제 수정

  • ai_jockey_2.smx를 2026.08.29로 올렸습니다. 「자키를 밀쳐낸 그 한 번이 오히려 징벌 분기를 무장시키는」 허점을 수정했습니다. 생존자의 최근 스윙이 자키 자신을 밀친 것이라면 「밀치기 쿨다운 중 즉시 도약」 징벌이 더 이상 발동하지 않습니다. 정말로 밀치기를 낭비한 경우(헛스윙, 다른 특감에 사용)에 대한 징벌은 그대로 유지됩니다.
  • 밀치기 후 억제 시간은 더 이상 z_jockey_leap_time(Anne 설정 0.4초)을 재사용하지 않고, 새 독립 cvar ai_JockeyShovedCooldown(기본 3.0초, 대전 규칙에서 밀쳐진 자키에게 적용되는 jumpcap 차단 표준 시간과 일치)으로 관리합니다. 억제 기간에는 플러그인이 bhop/도약 강화를 전혀 하지 않고 네이티브 AI의 도약 버튼도 차단하므로, 0.9초 경직이 끝나자마자 공중에서 재도약하는 일이 없어졌습니다. 이동과 후퇴는 정상적으로 가능합니다. jumpcap 패치의 AI 예외와 경직 관련 cvar는 건드리지 않았습니다.

2026년 8월 28일 Scripted HUD 슬롯 팀별 표시

  • Scripted HUD를 1.6.0으로 올렸습니다. !hudmenu의 각 슬롯에 「표시 팀」 하위 메뉴를 추가해 생존자/관전/감염자를 자유롭게 조합할 수 있습니다(최소 1개는 유지). 예를 들어 관전 중에만 보이는 슬롯을 만들 수 있습니다. 선택은 쿠키와 데이터베이스에 저장되며(scripted_hud_prefs에 slot_team_masks 열 추가, 자동 마이그레이션), 기존 설정은 모든 팀 표시로 유지됩니다.
  • 민감한 콘텐츠 소스의 팀 허용 목록이 하드코딩에서 cvar l4d2_scripted_hud_source_teams로 바뀌었습니다(소스:팀마스크 형식, 비트 1=관전 2=생존자 4=감염자). 기본값 2:5,7:5,13:5는 기존 동작과 동일해 특감 체력·다음 웨이브 카운트다운·스폰 대기열은 관전/감염자 전용이며, 13:1처럼 설정하면 관전 전용으로 좁힐 수 있습니다. 메뉴의 「(관전/특감)」 태그는 실제 설정에 따라 동적으로 생성되고 6개 언어 번역도 추가했습니다.
  • 팀 제한으로 숨겨진 슬롯은 텍스트 자체를 클라이언트로 전송하지 않습니다(기존에는 렌더링만 안 될 뿐 문자열이 네트워크로 전송되었습니다). 팀 변경 시 즉시 표시가 갱신됩니다.

2026년 8월 29일 제어형 특감 독립 타깃 상한

  • SI_Target_limit.smx를 1.7로 올렸습니다. 스모커/헌터/자키/차저가 하나의 잠금 상한을 공유하며, 공식은 「제어형 특감 예산 / 기동 가능한 생존자 + 1」입니다. 예산은 활성화된 제어 직업의 z_* / inf_* 상한 합이며 l4d_infected_limit로 클램프됩니다. 부머/스피터/탱크는 이 풀을 쓰지 않습니다.
  • l4d_target_override.smx를 2.26으로 올리고 targeted_mask를 추가해 제어형 특감만 집계합니다. 플로 선두 ×2와 러너 해제 의미는 그대로입니다.
  • Anne 각 모드 shared_settings.cfg에 SI_enable_option 53을 명시했습니다.
  • l4d_target_override.smx를 2.27로 업데이트: L4D_TargetOverride_SetLastVictim을 추가해 L4D2_OnChooseVictim 채널로 타깃을 바꾸는 AI 플러그인도 동일한 잠금 장부에 등록되도록 했습니다. L4D_OnTargetOverride forward가 타깃을 바꿔도 장부가 동기화되지 않던 문제도 수정했습니다.
  • SI_Target_limit.smx를 1.8로 업데이트: IsClassTargetLimited native와 관리자 디버그 명령 sm_targetlimit_status를 추가했습니다. 1.7의 변경 이력 주석이 조기 종료되어 컴파일되지 않던 문제를 수정했습니다.
  • 특감 AI 장부 통일: ai_hunter_2 / ai_jockey_2 / ai_charger3는 타깃 변경 전에 제어형 상한을 준수하고(새 타깃이 가득 차면 변경하지 않음), ai_spitter_3 / ai_boomer_3 / ai_tank3는 변경 후 장부에 등록합니다. ai_smoker3는 타깃을 바꾸지 않아 대상이 아닙니다.
  • 인원 자동 적응: 4인, 5~8인 멀티, 솔로 잔여전 모두에서 공식이 자동으로 조정됩니다. 총 용량은 항상 「제어형 예산 + 행동 가능 생존자 수」로 필드의 제어형 특감 수보다 반드시 큽니다. 생존자가 적으면 상한이 자동으로 완화되어 특감이 합법 타깃을 잃지 않습니다.
  • 버전 롤백 설정 Anne25-11 / Anne25-10 / Anne24-5 / Anne23-1에 SI_Target_limit.smx 로드를 보강했습니다(기존에는 unloadall.cfg가 언로드한 뒤 다시 로드하지 않아 타깃 상한이 조용히 사라졌습니다).

2026년 8월 19일 Tank 머리 탑승과 사다리 낙하

  • ai_tank3를 1.0.0.9로 올렸습니다. 머리 위 차단을 Tank 충돌체 위에 서 있는 탑승과, 닿지 않는 고대 정체의 두 가지로 나눴습니다.
  • 탑승은 2초 정지 대기도, 블랙리스트 전환도 하지 않습니다. 펀치 시 탑승자에게 위쪽 SweepFist를 추가하고, 3초가 지나도 머리 위에 있으면 그 자리에서 강제 투석합니다. 250을 벌릴 필요가 없습니다. 근거리 돌은 목표를 직접 조준해 수평 거리가 0에 가까울 때의 탄도 나눗셈을 피합니다. 탑승 중에는 점프 투석을 하지 않습니다.
  • 고대 정체는 기존 2초 이동 창을 씁니다. 다른 생존자가 있으면 전환(서 있는 사람 → 핀 → 다운). 대안이 없으면 그 자리에서 투석하고, 같은 닿지 않는 목표에 CommandABot RESET을 하지 않습니다. 같은 사다리를 다시 오르다 떨어지는 주된 원인 중 하나였습니다. 사다리 근처에서도 RESET하지 않습니다.
  • 사다리 위에서는 피치를 수평으로 되돌리고 m_climbableNormal로 요를 사다리에 잠가, 바닐라의 “오르면서 피해자를 보다가 떨어짐” 순환을 줄입니다.

2026년 8월 20일 Tank 탑승 판정에 모델 파고들기 보완

  • ai_tank3를 1.0.0.10으로 올렸습니다. 탑승의 첫 증거는 계속 m_hGroundEntity == Tank이며, 옆 고대/계단을 탑승으로 치지 않습니다.
  • 지면 엔티티가 Tank가 아니면 「수평 충돌 투영이 겹치고 + 발에서 아래로 쏜 hull이 실제로 Tank에 맞아야」 합니다. 모델에 파고들어 지면이 여전히 world인 탑승을 잡고, 벽에 안 맞는 중심 레이를 증거로 쓰지 않습니다.

2026년 8월 23일 모자 설정 이중 영속화

  • l4d_hats.smx를 1.47.0으로 올렸습니다. 모자 종류, 착용 여부, 1인칭 자기 모자 표시, 3인칭 자기 모자 표시, 다른 플레이어 모자 표시의 다섯 항목을 clientprefs Cookie에 저장하며 l4d_hats_save 0에서도 불러옵니다.
  • rpg.smx를 2.1.0으로 올렸습니다. databases.cfg에 rpg MySQL이 있으면 네 가지 토글은 RPG 테이블을 우선 사용하고, MySQL을 사용할 수 없거나 열 마이그레이션에 실패하면 clientprefs SQLite로 자동 전환합니다. 기존 계정의 NULL 열은 Cookie 준비 후에만 이전해 기본값 덮어쓰기를 막습니다.
  • 라운드에서 다시 생성된 모든 모자 엔티티에도 관전자별 SetTransmit 필터를 계속 적용하므로 다른 플레이어 모자 숨김 설정이 유지됩니다. 멱등 마이그레이션 20260823_hat_preferences.sql을 추가했으며 플러그인도 같은 열을 자동으로 추가할 수 있습니다.

2026년 8월 23일 공용 사운드 카탈로그와 전체 이벤트 자유 선택

  • l4d2_hitsound.smx를 2.8.0으로 올렸습니다. 프리셋의 비어 있지 않은 25개 참조를 실제 경로 기준으로 중복 제거해 안정 ID S01..S22의 22개 사운드로 구성했습니다. 프리셋 1/3의 중복 3개는 한 번만 보관하며 다운로드와 프리캐시도 사운드마다 한 번만 수행합니다.
  • 헤드샷, 타격, 킬, 헤드샷 킬 메뉴가 모두 같은 공용 카탈로그 전체를 표시하므로 어떤 사운드든 원하는 이벤트에 지정하고 즉시 미리 들을 수 있습니다. 프리셋 9는 [헤드:S21 타격:끔 킬:끔 헤드킬:S22]로 표시됩니다.
  • 기존 DB/KV의 양수 프리셋 값은 데이터 마이그레이션이 필요 없습니다. 새 공용 선택은 -1..-127을 사용하므로 네 사운드 열은 signed 정수여야 합니다. 저장소 기본 schema는 이미 signed이며, 20260823_hitsound_sound_catalog.sql은 잘못 UNSIGNED로 생성된 운영 테이블만 멱등하게 수정합니다.

2026년 8월 23일 타격 피드백 개별 사운드 리팩터링

  • l4d2_hitsound.smx를 2.7.0으로 올렸습니다. 사운드 세트는 실제 샘플이 있는 항목만 적용하는 프리셋이 되었고 빈 항목은 자동으로 꺼집니다. 따라서 세트 9는 헤드샷과 전용 헤드샷 킬 사운드만 켜고 타격/킬은 켜진 것으로 표시하지 않습니다.
  • 플레이어가 헤드샷, 타격, 킬, 헤드샷 킬 사운드 소스를 각각 선택할 수 있습니다. 각 메뉴는 해당 사운드를 제공하는 소스만 표시하고 선택 즉시 미리 재생합니다. 헤드샷 킬 0은 일반 헤드샷 사운드를 따릅니다.
  • 피해 적용 전 발생하는 infected_hurt의 치명타 판정, 관리자 단일 항목 끄기가 세 항목을 모두 지우던 문제, 초기 DB 로드 전 덮어쓰기, 인증 순서, 빠른 연속 저장 역전, 동적 전체 스위치를 수정했습니다. 기존 DB/KV 값은 로드 시 정규화됩니다. 이번 리팩터링은 열을 추가하지 않지만, hitsound_stack_mode / hitsound_headkill 열이 아직 없는 DB는 기존의 멱등 마이그레이션 20260820_hitsound_personal_prefs.sql을 한 번 실행해야 합니다.

2026년 8월 20일 히트 피드백 딩딩 세트를 옛 killsound에 가깝게

  • l4d2_hitsound.smx를 2.6.0으로 올렸습니다. 사운드 세트 선택 키는 세 개로 줄었습니다. headshot_kill(헤드샷 킬 전용음), stack(같은 프레임 겹침/병합), apply_special_only(선택 시 범위 기록). 킬은 사망 이벤트에서 한 번만 울립니다. 재생은 항상 SNDCHAN_AUTO. 화염, 연소탄, DMG_DIRECT, 폭발, weapon_id==0 필터는 그대로입니다.
  • 사운드 세트는 헤드샷, 타격, 킬, 헤드샷 킬 4개 항목을 함께 설정합니다. 헤드샷 킬은 0..N에서 별도로 선택할 수 있고(0=일반 헤드샷 사운드), 해당 ID와 재생 모드는 RPG 테이블에 저장되며 기존 SoundSelect.txt에서 한 번 마이그레이션됩니다. 중복되는 오버레이 빠른 전환과 sm_hitui는 제거하고 아이콘 세트 0으로 비활성화합니다.
  • 세트 9 「원조 딩 투표 플러그인」: 헤드샷 명중은 딩, 헤드샷 킬은 종, 그 외는 무음. 선택하면 범위를 모든 감염자로 맞춥니다.

2026년 8월 19일 Scripted HUD 생존자 「특감 수/간격」 복원

  • Scripted HUD를 1.5.1로 올렸습니다. 1.4.2는 HUD2의 「특수 감염자 수/간격」을 특수 감염자 체력, 다음 웨이브 초, 스폰 대기열과 함께 관전/감염자 정보로 숨겼습니다. [N특M초]는 호스트명과 같은 공개 방 설정이지 현장 정보가 아닙니다. 생존자에게 다시 표시하며, 내장 CS 킬 피드와 함께 씁니다. 킬 피드는 해당 플레이어의 엔진 슬롯 8–14만 사용합니다.
  • 특수 감염자 체력, 다음 웨이브 초, 다음 웨이브 대기열은 계속 관전/감염자만 볼 수 있습니다.

2026년 8월 19일 낮은 실혈·구급상자 없음이면 Anti-Bait 정지

  • infected_control.smx를 2026-08-19.1로 올렸습니다. 서 있는 생존자의 실체력이 모두 inf_antibait_lowhp(기본 40) 미만이고, 살아 있는 생존자(다운 포함)가 구급상자/제세동기/진통제/아드레날린을 아무도 들지 않았으면 Anti-Bait 전체가 Paused로 들어갑니다. 다운 체력은 실체력이 아닙니다. 임계값을 0으로 두면 이 조건을 끌 수 있습니다.

2026년 8월 15일 시체 용해 투표 제거

  • 모든 모드 메뉴에서 “시체 용해” 투표를 제거하고 cfg/vote/dissolve_on.cfg, cfg/vote/dissolve_off.cfg 설정도 삭제했습니다.

2026년 8월 15일 Scripted HUD에 CS 킬 HUD 통합

  • Scripted HUD를 1.5.0으로 올렸습니다. 독립 l4d2_cs_kill_hud.smx와 기존 킬 HUD 투표를 삭제하고 CS 스타일 킬 피드를 내장했습니다. 플레이어별 기본값은 꺼짐이며 !hudmenu에서 각자 전환할 수 있고 서버 전체 스위치는 사용하지 않습니다.
  • 켠 플레이어에게만 SendProxy로 엔진 슬롯 8–14(메뉴에서는 HUD 9–15)를 덮어쓰고 그 플레이어의 설정 메뉴에서 숨깁니다. 끈 플레이어는 원래 엔진 킬 목록을 계속 받고 자신의 슬롯 설정도 유지합니다.
  • !hudmenu를 CS 킬 피드, 기본 HUD(1–4), 추가 HUD(5–8), 확장 HUD(9–15), 화면 레이아웃, HUD 2 항목 그룹으로 재구성했습니다. 킬 피드와 슬롯 설정은 플레이어별로 저장됩니다.

2026년 8월 15일 Scripted HUD 다음 웨이브 상태 수정

  • infected_control.smx를 2026-08-15.2로 올리고 처치 단계, 독립 기본 카운트다운, Anti-Bait 보류를 구분하는 웨이브 상태 native를 추가했습니다. 기존 ETA native 호환성은 유지됩니다.
  • Scripted HUD를 1.4.4로 올렸습니다. 정확한 초 단위 표시는 기본 카운트다운에서만 사용하고, 처치 단계와 Anti-Bait 보류는 명확한 다국어 상태로 표시합니다. 상태 API가 없는 2026-07 이전 버전은 약 8초 차이가 날 수 있는 추정값 대신 --를 표시합니다.

2026년 8월 15일 신규 플레이어 트레이터 상태 잔류 수정

  • infected_control.smx를 2026-08-15.1로 올렸습니다. 플레이어가 서버에 들어올 때 사용하는 client slot의 트레이터 등록을 명시적으로 초기화합니다. 이전 사용자의 접속 종료 정리가 끝나지 않았더라도 신규 플레이어는 비트레이터로 시작하며, 감염자 팀에 잘못 배정되면 join.smx가 관전자로 돌려보낼 수 있습니다. 이후 정상적인 트레이터 등록에는 영향을 주지 않습니다.

2026년 8월 15일 Actions 서버 충돌 수정

  • actions.ext가 Action 삭제 시 없는 user-data 키를 다시 삽입하고 세 종류의 Action 포인터 캐시를 지우지 않던 문제를 수정했습니다. Linux 확장 버전은 3.7.6-anne.2이며 삭제 시 user-data, identity-data, actor 캐시 항목을 제거합니다.
  • 리스너가 없어도 Action마다 빈 pre/post 리스너 해시를 만들고 삭제 뒤에도 바깥쪽 Action 포인터 키를 유지하던 문제를 수정했습니다. 후보 Action의 pending 표시도 함께 정리합니다.
  • ai_charger3를 1.0.1.7로 올렸습니다. ChargerEvade의 CommandABot(MOVE)는 다음 프레임으로 미뤄 Actions OnUpdate 콜백 안에서 NextBot 행동 트리를 동기식으로 재구성하지 않습니다.

2026년 8월 14일 플레이어 표시 문구 6개 언어 정렬

  • !xx 상태 줄 마지막 항목을 「버전」으로 바꿔 「통신선」 오기를 없앴습니다. 영어 Tank stall / HP return과 일본어 전문가·연속 점프 용어도 원본 의미에 맞췄습니다.
  • 트레이터와 Anti-Bait 안내는 캠핑, 홀드 중, 웨이브 해제를 구분합니다. 일/한/베트남어의 영어 잔여를 채우고 트레이터 역명을 통일했습니다.
  • 기본 Scripted HUD 생존자 상태는 클라이언트 언어로 만들고, 표머리를 「소생 횟수」로 바꿨습니다.
  • 흑백 Hint는 문구를 쓰며 다음 다운이 즉사임을 밝힙니다. !ht 메뉴에 일/한/베트남어를 추가했고, 스킬 알림의 중국어 직업명을 트레이터/통계와 맞췄습니다.

2026년 8월 14일 네트워크 품질 보고는 손실 우선

  • network_quality_hint.smx를 1.1.5로 올림. 웹사이트 이상 이벤트는 패킷 손실을 우선합니다. choke가 nqh_report_choke_limit(기본 20%) 이하면 이상으로 보고하지 않습니다. 게임 내 채팅 안내는 계속 nqh_choke_limit(기본 5%)를 씁니다.
  • 이상 사유: 타임아웃은 timing_out. 그 외에는 loss를 우선하고, 다음이 ping, 보고 임계값을 넘는 choke입니다.

2026년 8월 13일 스폰 분산과 클래스 다양성

  • infected_control.smx를 2026-08-13.11로 올렸습니다. Hunter / Jockey / Charger 후방 생성을 한 단계 더 조입니다. 하드 거부 -5→-4(inf_score_behind_reject_gap_hjc), 후방 감점 배율 2.0→2.5, Flow 뒤 앵커 -4/-3. Hunter rear 0.15→0.05, Jockey는 rear를 쓰지 않습니다. 같은 진행도 후보는 계속 점수를 받습니다. Smoker / Spitter / Boomer와 웨이브 시간 계약은 그대로입니다.
  • infected_control.smx를 2026-08-13.10으로 올렸습니다. Hunter / Jockey / Charger가 생존자 후방에 뜨는 확률을 더 낮춥니다. 이 세 종류의 후방 하드 거부는 전역 -8에서 -5(inf_score_behind_reject_gap_hjc)로, 후방 감점은 inf_score_behind_hjc_scale(기본 2.0)배입니다. Flow 뒤 앵커는 -10/-9에서 -5/-4로 당깁니다. 같은 진행도 후보는 계속 점수를 받습니다. Hunter 전술 품질은 rear를 줄이고 측면/가장자리를 더 보고, Jockey rear 비중은 0.15→0.05입니다. Smoker / Spitter / Boomer의 전역 후방 규칙은 그대로입니다. 웨이브 시간 계약은 변하지 않습니다.
  • infected_control.smx를 2026-08-13.9로 올렸습니다. 하드 분산을 한 단계 더 낮춥니다. inf_spawn_sep_radius 150→120, inf_spawn_sep_ttl 2.5→1.5. 같은 Nav 쿨다운 NAV_CD_SECS는 1.0초 유지. 웨이브 시간 계약은 그대로입니다.
  • infected_control.smx를 2026-08-13.8로 올렸습니다. Hunter 습격 준비 거리를 조금 밖으로 밉니다. 선호 대역을 350–750에서 350–850로, 스위트스팟을 고리의 38%(약 500)에서 48%(약 590)로 바꿨습니다. 중거리로 유지하며 예전 950 원거리로는 돌아가지 않습니다. Charger / Jockey는 그대로입니다. 웨이브 시간 계약은 그대로입니다.
  • infected_control.smx를 2026-08-13.7로 올렸습니다. Charger / Jockey / Hunter가 너무 먼 지점에 떨어지는 일을 줄입니다. Hunter 선호 대역을 350–950에서 350–750로 줄이고, 거리 스위트스팟을 고리의 45%에서 38%(약 500)로, 고지 정렬 0.90→0.95, 고지 거리 보정 800→450으로 바꿔 먼 고대를 중거리로 환산하기 어렵게 했습니다. Jockey는 250–550, Charger는 250–600, 스위트스팟을 조금 당겼습니다. 분산 가중치는 1.15/1.00→0.75/0.65. 전역 하드 분산 반경 180→150, 커널 40→30, 채점 정원은 근/중/원 6/4/2에서 6/5/1. 웨이브 시간 계약은 그대로입니다.
  • infected_control.smx를 2026-08-13.6으로 올렸습니다. 거리 대역을 10단계에서 6단계로 되돌립니다(stage 0은 선호 대역 유지, stage 1–5는 선호 상한과 최종 상한 사이를 선형 보간). 10단계에서는 고리가 이백 유닛 남짓이라 근/중/원을 나눌 의미가 없고, 6단계여야 삼분위에 거리 의미가 생깁니다. 근/중/원은 현재 페이지 min/max가 아니라 현재 대역 검색 고리(scanMin..maxRange)를 삼등분합니다. 섹터 할당량은 앞 2/3(0–3)만 제한하고 뒤 1/3(4–5)은 제외합니다. 웨이브 시간 계약은 그대로입니다.
  • infected_control.smx를 2026-08-13.5로 올렸습니다. 채점 정원은 더 이상 “이번 틱에서 먼저 합격한 8개”가 아니라, 현재 후보 페이지를 경로 거리 삼분위로 나눠 근 6 / 중 4 / 원 2(합 12)로 채점합니다. 소비 순서는 3:2:1이고, 정원이 찬 삼분위는 비용이 큰 가시/경로 검사를 건너뛰며, 빈 칸은 다른 삼분위로 넘겨 스폰을 지연시키지 않습니다. inf_spawn_nav_expensive_per_slice 기본값은 24(이전 16). CVar: inf_spawn_budget_near/mid/far(모두 0이면 삼분위 끔). ranked 고지대 채널은 그룹 내 순서를 유지하고 삼분위 교차만 합니다. 웨이브 시간 계약은 그대로입니다.
  • infected_control.smx를 2026-08-13.4로 올렸습니다. 한 웨이브의 특수 감염자가 좁은 구역에 몰리는 문제를 해결하기 위해 분산 로직을 재작업했고, 클래스 구성에 통제된 무작위성을 도입했습니다. 웨이브 시간 계약(킬 페이즈, 독립적인 16초 기본 카운트다운, Anti-Bait)은 그대로입니다.
  • 거리 대역을 6단계에서 10단계로 세분화했습니다: 각 클래스의 선호 대역(stage 0, 예: Smoker 500–1200)은 나누지 않고 유지해 스폰이 평균적으로 더 가까워지지 않게 했습니다. 실패 시 확장은 선호 상한과 최종 상한 사이를 선형 보간하며(Smoker는 단계당 +200, 최대 3000), 한 번의 승급마다 더 작은 고리만 스캔해 거리 상승이 더 부드럽고, 고리 하나가 섞인 한 페이지에 담기기 쉬워집니다. 섹터 할당량은 앞 2/3 대역만 제한하고 뒤 1/3은 제외하도록 바꿨습니다.
  • 방향 후보 페이지를 가져온 뒤 페이지 내에서 섞습니다(inf_spawn_candidate_shuffle, 기본 켜짐). 기존 구현은 경로 거리 오름차순으로 후보를 소비하고 시도당 최대 8개의 합격점만 받아들여, 항상 "가장 가까운 한 덩어리"에서만 골랐습니다. 섞은 뒤에는 채점 대상이 거리 대역 전체를 덮습니다. ranked 고지대 채널은 정렬 이점을 유지하기 위해 섞지 않습니다.
  • 하드 분산을 CVar로 만들고 강화했습니다: inf_spawn_sep_radius(기본 180, 이전 하드코딩 80)와 inf_spawn_sep_ttl(기본 2.5초, 이전 0.5초). 특수 상한에 따른 스케일링은 유지됩니다.
  • 분산 점수에 연속 커널 감점을 추가했습니다: 생존 특수 감염자와 최근 스폰 지점에 exp(-d/r)를 누적해 감점합니다(inf_spawn_kernel_radius 기본 280, inf_spawn_kernel_points 기본 40). "최근 3개 섹터만 기억"하는 거친 메모리를 보완하며 섹터 감점은 유지됩니다.
  • 웨이브 내 섹터 할당량을 신설했습니다: 웨이브당 섹터당 착지 수 ≤ ceil(특수 상한/섹터 수)+inf_spawn_sector_quota_bonus(기본 +1, -1이면 끔). 가까운 4개 거리 대역만 제한하며, 먼 대역과 디렉터 대체 경로는 제외되어 좁고 긴 맵에서도 스폰이 멈추지 않습니다.
  • 고지대 우선은 확장의 ranked 방향 후보로 전환했습니다: inf_nav_high_sort_scale을 다시 활성화(새 기본 Smoker 0.85 / Hunter 0.90). 대상보다 inf_nav_high_sort_min_height(기본 64) 이상 높은 후보는 할인된 거리로 앞당겨져 제한된 채점 정원에 들어가며, 더 이상 지면 근처 덩어리 안에서 사후 가점에만 의존하지 않습니다.
  • 클래스 다양성: 클래스 선택 희소도에 ±inf_class_scarcity_jitter(기본 0.06)의 작은 무작위 흔들림을 더해 점유율이 비슷한 클래스 간 고정 순환을 깨고, 지원 게이트(Boomer/Spitter는 압박 클래스 이후)는 웨이브마다 확률 inf_support_gate_waive_prob(기본 0.15)로 완화합니다. Breach 웨이브는 완화하지 않습니다.
  • [SCORE-CHOSEN] 디버그 로그와 sm_navdebug 채점 내역에 커널 항목(kern)을 추가했습니다.

2026년 8월 13일 Scripted HUD 15슬롯 개방

  • extend/l4d2_scripted_hud.smx를 1.4.0으로 올렸습니다. EMS HUD 슬롯 15개를 !hudmenu → “HUD 슬롯 15개 모두”에서 표시 내용에 할당할 수 있습니다(서버 기본값 / 사용 안 함 / 생존자 상태 / SI 체력 / 킬 / 핑 / 탱크 피해 / 팀 아이템 / 다음 웨이브 / 속도 / 라운드 시간 / Witch 경고 / 진행·탱크·Witch / 서버 상태).
  • HUD 1–4 기본 동작은 유지합니다. 슬롯 5–8(엔진 4–7)은 내용을 고를 때까지 숨고, 고르면 왼쪽 열에 나타납니다. 슬롯 9–15(엔진 8–14)는 기본으로 엔진 킬 목록을 통과시키고, 내용을 묶은 플레이어만 해당 킬 줄을 덮어 오른쪽 아래로 옮깁니다(조준점을 피함).
  • 설정은 쿠키와 scripted_hud_prefs에 저장됩니다(hud_mask를 smallint로 확장, slot_sources 추가. 옛 6필드 쿠키와 hud3/hud4 열은 그대로 읽힘). 메뉴와 슬롯 이름은 6개 언어입니다.
  • 1.4.1: 슬롯 좌표를 화면 비율에 맞춰 배치합니다. !hudmenu → “화면 레이아웃”에서 16:9 / 16:10 / 4:3 / 21:9를 고를 수 있습니다. 추가 슬롯은 왼쪽 열 4–9, 오른쪽 열 10–14이며 HUD 1–4, 킬 피드, 조준점, 무기 HUD를 피합니다. 4:3은 오른쪽 잘림을 막고, 21:9는 가운데 16:9 안전 영역으로 모읍니다. SendProxy로 플레이어마다 적용됩니다.
  • 1.4.2: 특수 감염자 체력, 다음 웨이브 카운트다운, 다음 웨이브 대기열, HUD 2의 “특감 수/간격”은 관전자와 특감만 보고, 생존자는 해당 슬롯 전체를 숨깁니다. 메뉴에는 “ (관전/특감)” 표시가 붙습니다. 새 내용 “다음 특수 감염자 대기열”은 infected_control의 InfectedControl_GetSpawnQueue를 읽습니다(옛 플러그인은 --). 같은 열의 추가 슬롯은 실제 줄 수로 아래로 쌓이며, 추가 슬롯은 최대 4줄, HUD 3/4는 최대 6줄로 긴 텍스트가 겹치지 않게 합니다.

2026년 8월 13일 Scripted HUD 추가 콘텐츠

  • extend/l4d2_scripted_hud.smx를 1.3.0으로 올렸습니다. HUD 3 / HUD 4 내용 메뉴에 여섯 항목을 추가하며, 슬롯별로 골라 쿠키/데이터베이스에 저장합니다.
  • 탱크 피해 순위: 이번 탱크전 피해 상위 4명(비율 포함). 처치 후에도 다음 탱크까지 유지하고, 없으면 “탱크 없음”.
  • 팀 아이템: 생존자마다 투척/치료/임시 아이템을 압축 표시.
  • 다음 특수 감염자: infected_control의 GetNextSpawnTime을 읽고(미로드 시 --), 필드 소형 SI 수/상한도 표시.
  • 속도/버니합: 자신(관전 중이면 대상)의 수평 속도와 이번 버니합 최고치. 땅에 내려 걷기 약 0.25초 후 최고치를 지웁니다.
  • 라운드 시간+호드: round_start부터 mm:ss. create_panic_event 60초 창에서는 “진행 중”, 그 외에는 디렉터 호드 카운트다운.
  • Witch 거리 경고: 가장 가까운 Witch의 미터 거리와 상태(평온/근접/경계/격노). 15m 이내는 “근접”. 여러 마리면 나머지를 따로 셉니다. 새 문구는 6개 언어.

2026년 8월 13일 Scripted HUD 사용자 지정 슬롯과 흰 테두리 상자 대응

  • extend/l4d2_scripted_hud.smx를 1.2.0으로 올렸습니다. HUD 3 / HUD 4에서 플레이어가 표시 내용을 고를 수 있으며, !hudmenu에 “HUD 3/4 표시 내용” 하위 메뉴(서버 기본값 / 생존자 상태 / 특수 감염자 체력 / 킬 통계 / 플레이어 핑)를 추가했습니다. 선택은 쿠키와 scripted_hud_prefs 데이터베이스에 저장되며(구 테이블은 자동 ALTER 마이그레이션) 서버를 옮겨도 유지됩니다.
  • 특수 감염자 체력 페이지는 생존 SI 수/상한, 각 소형 SI의 현재 체력, Tank 현재/최대 체력, Witch 수를 보여 줍니다. 킬 통계는 생존자별 라운드 내 SI/일반 좀비 킬을 집계하고, 핑 페이지는 사람 플레이어의 지연을 나열합니다(생존자 우선). 모든 문구는 번역 문구를 사용합니다(간체/번체 중국어, 영어, 일본어, 한국어, 베트남어).
  • hudmenu 주변에서 가끔 보이던 “흰 테두리의 빈 직사각형”에 대한 대응: 이는 EMS 스크립트 HUD 슬롯이 일시적으로 “보이지만 NOBG/TEXT 없음” 플래그 상태로 그린 빈 배경 패널입니다. 클라이언트별 플래그를 서버의 정식 플래그에서 재구성하도록 바꿔 ResetHUD 새로 고침 구간에 플래그 0이 전송되지 않게 했고, 두 HUD 플러그인이 쓰지 않는 EMS 슬롯 4-7을 맵 시작 시 명시적으로 숨깁니다.
  • 패널 높이를 클라이언트별 실제 줄 수로 개인화(m_fScriptedHUDHeight sendproxy 추가)해 여러 줄 사용자 지정 내용이 잘리지 않습니다. l4d2_scripted_hud_hud3_team 기본값을 2(감염자만)에서 0(전원)으로 바꿔 생존자가 고른 HUD 3 내용이 실제로 보이게 했습니다.
  • HUD 2에 “전멸 횟수” 필드를 추가: server.smx가 게시하는 anne_round_wipe_count(현재 맵 전멸 수, 맵 이동 시 초기화)를 읽어 1회 이상 전멸하면 HUD 2 끝에 [전멸xN]을 표시합니다. HUD 전용이며 방 이름에는 쓰지 않습니다. !hudmenu → “HUD 2 표시 필드”에서 개별 전환할 수 있습니다. HUD 2 설정을 저장한 적 있는 플레이어는 기본 꺼짐이라 한 번 켜야 하고, 신규 플레이어는 기본 켜짐입니다.

2026년 8월 13일 server_name AI 난이도 태그 잔류

  • server_name.smx를 1.4.8로 올렸습니다. [AI:전문가] 같은 방 이름 태그는 annehappy_dynamic_ai_difficulty.smx가 실제로 로드되어 있고 등급이 정해진 경우에만 표시합니다.
  • 동적 난이도 플러그인을 언로드해도 SourceMod는 ah_ai_dynamic_current_level을 남깁니다. 이전 로직은 남은 1-6 값을 hostname / sn_main_name에 계속 썼습니다. 이제 플러그인이 실행 중인지 확인하고, 이미 방 이름에 들어간 [AI:...]도 지웁니다.
  • annehappy_dynamic_ai_difficulty.smx를 2026.08.13.1로 올렸습니다. annehappy_dynamic_ai_difficulty 라이브러리를 등록하고, 언로드 시 current_level / current_mode / current_ppm / current_locked를 0으로 되돌립니다. HUD, !xx, 스폰 전략이 옛 등급을 계속 읽지 않게 합니다.

2026년 8월 13일 물속 감속 채팅 번역

  • l4d2_slowdown_control의 Tank 등장/퇴장 시 물속 감속 채팅을 고쳤습니다. 영어의 중국어 직역(“tank generation” 등), 베트남어가 Tank를 “xe tăng”으로 오역하던 문제, 일본어 플러그인 이름 불일치를 바로잡았습니다.
  • 간체/번체 중국어, 영어, 일본어, 한국어, 베트남어를 업스트림 의미에 맞췄습니다. Tank가 있으면 물속 감속이 약해지고, Tank가 사라지면 정상으로 돌아갑니다.

2026년 8월 13일 Anne Tank 등장 알림 번역

  • optional/AnneHappy/l4d2_tank_announce.smx를 1.0.1.1로 올렸습니다. 채팅, Hint, 중앙 문구가 모두 i18n을 쓰며, 플레이어/AI 접두사는 클라이언트 언어로 만들어 영어 플레이어에게 중국어 “(玩家)”가 더 이상 보이지 않습니다.
  • 영어의 중국어 직역(“has been generated / controller”)을 고치고 Hint 문구를 추가했습니다. 스페인어에 없던 Anne 전용 키도 채웠습니다.
  • 대전판 Spawned의 일본어/한국어/베트남어/번체 표현도 맞추고 Tank는 고유 명사로 유지했습니다.

2026년 8월 13일 l4d_stats 채팅 i18n

  • l4d_stats.smx를 1.5.6으로 올렸습니다. 채팅에 남아 있던 하드코드 영어를 번역 문구로 옮겼고, 간체/번체/영/일/한/베트남어 315개 문구를 맞췄습니다. 언어별 파일은 더 이상 맵 기록 13개만이 아닙니다.
  • 특수 감염자 처치, Smoker/Hunter 구조, Charger 구조, Tank 바위 적중 안내는 보는 사람 언어로 직업 이름을 표시합니다. 인간 플레이어는 닉네임 그대로입니다.
  • 맵 기록 팀 이름 대체값과 콘솔 [RANK] 접두사도 번역합니다. global_chat.smx를 1.1.1로 올려 전 서버 맵 기록 방송도 같은 팀 이름 번역을 씁니다.

2026년 8월 13일 Survivor MVP 번역

  • survivor_mvp.smx를 0.3.4로 올렸습니다. 라운드 MVP/LVP, 개인 순위, 봇 접두사의 중국어 하드코딩을 없애고 클라이언트 언어로 출력합니다. 영어 플레이어에게 “特感/小僵尸/友伤/[机器人]”가 더 이상 보이지 않습니다.
  • 일본어/한국어/베트남어에서 특감·아군 피해·약역을 “특별한 느낌, 검은 총, 제약”으로 오역하던 문제를 고치고, 스페인어에 없던 Tag와 Anne 요약 통계 키를 채웠습니다.

2026년 8월 13일 text 플러그인 상태 표시 번역

  • optional/AnneHappy/text.smx의 !xx / 입장 / 라운드 시작 상태 줄을 클라이언트 언어로 출력하도록 바꿔, 영어 플레이어에게 “特感, 连跳, 动态难度, 回血” 같은 중국어 하드코딩이 더 이상 보이지 않습니다.
  • 영어의 “does not exceed 10 special” 직역을 고쳤고, 일본어가 AnneHappy를 “アンハッピー”로 오역하던 문제도 수정했습니다.

2026년 8월 13일 전원 신규 팀 보너스

  • l4d_stats.smx를 1.5.6으로 올렸습니다. 인간 생존자가 모두 신규이고 4명 이상이면 전원에게 1.5배 신규 팀 배율을 줍니다. 예전에는 “신규 본인은 배율 없음” 때문에 아무도 받지 못했습니다.
  • 신규가 1~3명일 때는 기존처럼 숙련 생존자만 1.1 / 1.3 / 1.5를 받습니다.
  • l4d_stats_newbie_bonus_all_newbie_multiplier(기본 1.5)와 l4d_stats_newbie_bonus_all_newbie_min(기본 4)을 추가했습니다. 세이프룸, 피날레, 라운드 시작 채팅은 각 언어로 팀 보너스를 설명합니다.

2026년 8월 13일 모자/스킨 저장 수정

  • l4d_hats.smx를 1.46.1로 올렸습니다. Hats_SetClientHat / Hats_GetClientHat native를 추가해 RPG가 sm_hatclient 콘솔 명령 없이 모자를 복원할 수 있습니다.
  • 모자를 벗을 때 L4D_OnHatLoadSave가 -1을 반환하도록 바꿔, 첫 번째 모자의 0번 인덱스와 겹치지 않습니다. Cookie 삭제와 자동 착용은 모자 메뉴 권한(상위 100 포함)을 따르고 랭킹 캐시를 기다린 뒤에만 저장된 모자를 지우지 않습니다.
  • rpg.smx를 2.0.1로 올렸습니다. 데이터베이스 로드가 끝난 뒤에 모자, 스킨, 외곽선을 다시 적용하며, 랭킹 캐시가 준비되기 전에 모자/스킨을 0으로 지우지 않습니다.
  • 첫 번째 모자는 -2로 저장해 HAT=0이 "모자 없음"과 "첫 모자"를 동시에 뜻하지 않게 했습니다. 이전 데이터를 읽기 전에 모자를 골라도 덮어쓰지 않습니다.
  • 스킨에도 같은 "비동기 DB 로드 후 미복원 / 랭킹 미준비 시 초기화" 구멍이 있어 함께 고쳤습니다. 칭호, 반동, 피해 HUD, 히트 사운드는 각자의 로드 완료 콜백이나 독립 Cookie/DB로 복원되어 같은 구멍이 없습니다.

2026년 8월 13일 네트워크 품질 보고

  • network_quality_hint.smx를 1.1.4로 올림. 플러그인 ConVar 기본값은 여전히 nqh_report_enable "0"이라 로컬 ping/loss/choke 검사는 보고에 의존하지 않습니다. 이 저장소는 웹사이트 화이트리스트에 있는 Anne 게임 서버를 위해 cfg/sourcemod/network_quality_hint.cfg에서 nqh_report_enable "1"을 명시합니다.
  • 보고에는 SteamWorks가 필요합니다. SteamWorks가 없어도 플러그인은 로드되고 로컬 안내는 동작하지만 nqh_report_url로는 보내지 않습니다.
  • 웹사이트 화이트리스트에 없는 커뮤니티 서버는 nqh_report_enable "0"을 유지하거나 되돌리세요.

2026년 8월 13일 타격 피드백 사운드 세트 커스터마이징

  • l4d2_hitsound.smx 사운드 세트에 "언제 울릴지"를 정의하는 선택 키 2개를 추가했습니다. headshot_kill은 헤드샷 킬 전용 사운드를 지정하고(비우면 headshot 사운드로 폴백, 기존 동작), stack은 해당 세트의 타격/킬 이벤트를 매번 즉시 개별 재생할지(같은 프레임에서 겹침)를 제어합니다. stack 기본값은 1(겹침)이며, 명시적으로 stack 0을 써야 "프레임당 1개, 킬>헤드샷>타격" 우선순위 병합이 활성화됩니다.
  • 세트 9 "원조 딩 투표 플러그인"은 이를 통해 옛 killsound(Dingshot)의 느낌을 재현합니다: 헤드샷 명중=딩(littlereward), 헤드샷 킬=종소리(bell_normal), 비헤드샷 명중/킬은 무음. 샷건 다중 킬은 킬마다 울리는 동작으로 돌아갑니다.
  • !snd 메인 메뉴에 모든 플레이어가 쓸 수 있는 개인 재생 설정 2개를 추가했습니다(서버별 KV 파일에 저장, 데이터베이스 미사용): "헤드샷 킬 전용음 켜기/끄기"(끄면 일반 헤드샷 사운드로 폴백)와 3단계 "사운드 재생 모드"(세트 따름/강제 겹침/강제 병합). 전환 응답은 각 모드의 의미를 설명하는 번역 문구(간체/번체/영/일/한/베트남어 5개 문구 추가)를 사용합니다.

2026년 8월 13일 AI3 플러그인 감사 수정

  • ai_tank3를 1.0.0.8로 올림: OnPlayerRunCmd가 하위 로직 반환값을 모아 돌려주어, 돌 던지기 거리 제한·머리 위 차단 강제 투석·연속 점프 키 수정이 엔진에서 버려지던 문제를 고쳤습니다. 펀치 시점 잠금은 GameTime 시계로 통일하고, 투석 발사 높이는 애니메이션 액티비티 이름으로 해석하며, tank_swing_range 변경 훅은 맵 전환마다 중복 등록되지 않습니다.
  • ai_charger3를 1.0.1.6으로 올림: 상태 머신 transitionTo가 이전 상태 OnExit를 실행하지 않던 문제(정적 구조체를 읽기 전에 판정)를 수정했습니다. 공중 급정지, 후퇴 점프, 인위 돌진 지연 플래그가 상태 전환 시 올바르게 리셋됩니다. player_spawn은 Post 훅이며, 사다리에서 점프/앉기 입력을 지운 뒤 Plugin_Changed를 올바르게 반환합니다.
  • ai_boomer_3를 3.0.6으로 올림: L4D_OnVomitedUpon이 attacker를 검증해 담즙탄 등 attacker가 0일 때마다 오류가 나던 문제를 고쳤습니다. 이미 회전 구토 큐가 있으면 다시 만들지 않아 강제 담즙 재귀로 목표가 중복 팽창하지 않습니다. 피해자 선택 조기 반환 시 trace 핸들 누수와 강제 담즙 루프의 잘못된 인덱스를 수정했고, 최근 생존자 조회는 0.2초 캐시로 매 프레임 정렬 비용을 줄입니다.
  • ai_spitter_3를 3.0.4로 올림: 혼잡 목표가 핀된 대상 좌표를 쓰던 복사-붙여넣기 버그(-1 인덱스 오류 포함)를 고쳤습니다. getTargetByPriority가 누군가 핀되었거나 생존자가 한 명일 때 ArrayList를 새던 문제를 수정하고, 생존자가 1명이어도 flow로 고를 수 있게 했으며, 군중 거리 누적 셀을 명시적으로 0으로 초기화하고 항상 거짓인 레이 필터 죽은 코드를 제거했습니다.
  • 레이 비용 최적화: ai_path_movement.inc에 점프 경로 실패 스로틀을 추가했습니다. 지형(벽, 단차, 위험한 착지)으로 실패한 안전 검사는 0.1초 쿨다운 안에 재시도하지 않습니다. 네 AI의 지면 점프 시도가 이를 사용해, 지형에 걸린 동안 hull/ray trace 비용을 약 1/6(60 tick)로 줄입니다. AIPathMovement_IsJumpRouteSafe는 레이가 필요 없는 무시야 각도 거부를 hull trace 앞으로 옮겨, 거부된 시도는 트레이스를 내지 않습니다.

2026년 8월 13일 infected_control 감사 수정

  • infected_control.smx를 2026-08-13.2로 올리고 전체 코드 감사에서 발견된 카운트·상태 문제를 수정했습니다. 웨이브 타이밍 계약(킬 페이즈, 독립적인 16초 기본 카운트다운, 상한 없는 Anti-Bait 홀드)의 동작은 그대로입니다.
  • 특수 감염자 생존 수 이중 집계를 수정: RecalcSiCapFromAlive 같은 전체 재집계가 킥 큐에 있는 임시 클라이언트를 건너뛰어, 텔레포트 킥 중에 클래스/전체 상한이 먼저 차서 웨이브 판단이 왜곡되는 문제를 막았습니다.
  • 동적 Nav 토폴로지(엘리베이터 등) 재구축 후 진행도 내보내기가 실패하면 스폰 탐색이 영원히 Pending에 멈추는 교착을 수정: 대기 플래그를 해제하고 기존 Flow 버킷으로 폴백하여 디렉터 대체 경로를 복구합니다.
  • 맵 간 캐시 오염 수정: 생존자 진행도 폴백 값과 버킷별 생존 특감 카운트 캐시를 라운드/맵 전환 시 초기화하여, 새 맵에서 GetGameTime이 되돌아가도 이전 맵 데이터를 계속 쓰지 않습니다. 버킷을 만들 때마다 새던 ArrayList 핸들 2개도 해제합니다.
  • Nav 버킷 디스크 캐시 견고화: 메타데이터는 일치하지만 유효한 구역이 하나도 없는 손상 캐시는 삭제 후 재구축하며, 내보내기 실패 시 반쯤 쓰인 파일을 남기지 않고 삭제합니다.
  • 텔레포트 감독의 두 가지 문제 수정: 출생 시각을 알 수 없는 경우를 더 이상 영구 출생 유예로 취급하지 않으며(워치독 하드 복구 후 인계된 특감에 출생 시각을 다시 찍어 정상적으로 텔레포트됩니다), Smoker는 능력 쿨다운 중에도 비가시 카운트를 누적해 준비 완료 후 초를 다시 모을 필요가 없습니다.
  • GetNextSpawnTime native는 킬 페이즈 중 "남은 확인 창 + 완전한 기본 카운트다운"의 상한 추정치를 반환하고, 일시정지 중에도 동결된 시계로 추정을 이어가며 킬 페이즈 시간을 기본 카운트다운에 합산하지 않습니다.
  • Anti-Bait 상태 머신 재설계: 산개로 홀드를 해제하려면 지속 확인이 필요합니다. 새 inf_antibait_spread_confirm(기본 3초) 동안 대형이 연속으로 산개해 있어야 Pressure가 강등됩니다. "잠깐 벗어났다가 바로 복귀"하는 페이크로는 강화 홀드를 속일 수 없으며, 산개는 정체 타이머도 초기화하지 않습니다. 무장 창 안에서 다시 뭉치고 여전히 정체 중인 팀은 같은 홀드의 연속으로 간주되어 Pressure로 바로 복귀합니다.
  • Anti-Bait 진행도 스냅샷 분리: 생존자의 flow 읽기 실패(엘리베이터, 잘못된 Nav)를 더 이상 "취약 멤버"로 취급하지 않아 홀드를 잘못 막거나 해제하지 않습니다. 해당 멤버는 대형 통계에는 정상 참여하고 진행도 통계에서만 제외됩니다. flow가 복구되면 기준선을 다시 세우지만 정체 타이머는 초기화하지 않습니다.
  • 내부자(Traitor) 실체화가 totalSI 상한의 제한을 받지 않습니다. 방출 웨이브가 도래한 SpawnAllowed 상태의 내부자(슬롯은 웨이브 시작 시 해당 웨이브 AI 예산에서 이미 차감됨)는 잔존 AI가 일시적으로 상한을 채우고 있어도 실체화할 수 있으며, 초과 상태에서는 AI 충원이 자동으로 멈춥니다.
  • create_panic_event가 60초 롤링 윈도로 바뀌었습니다. 새 panic 이벤트를 받을 때마다 갱신되고 panic_event_finished가 오면 즉시 해제됩니다. 종료 이벤트를 보내지 않는 커스텀 맵이 라운드 내내 Anti-Bait를 조용히 우회하는 것을 막으며, 연속/무한 호드 스크립트는 윈도를 계속 갱신하므로 영향이 없습니다.
  • sm_wavestatus와 Nav 디버그 명령 응답이 번역 문구(간체/번체/영/일/한/베트남어 12개 문구 추가)를 사용하며 중국어 하드코딩을 없앴습니다.
  • 조기 카운트다운 시작의 "생존자 건강 + 탱크 없음" 자비 게이트는 의도된 설계로 확인되어 AGENTS.md 행동 계약에 문서화했습니다(산개 확인과 내부자 면제 의미도 함께 기재).

2026년 8월 10일 Boomer/Spitter Path Follow 연속 점프

  • 네이티브 anne_nextbot 확장을 1.3.0으로 올렸습니다. 단일 PathFollower::Update detour를 유지하면서 제한된 경로 노드 값을 네이티브에서 직접 복사합니다. Tank, Charger, Boomer, Spitter는 native를 통해 스냅샷을 읽으며 SourcePawn은 PathFollower/PathSegment 포인터를 받거나 읽지 않습니다. 공통 경로 시그니처와 가상 함수 오프셋은 anne_nextbot.games.txt로 통합했습니다.
  • Tank ai_tank3를 1.0.0.4로, Charger ai_charger3를 1.0.1.4로 올리고 기존 Path Follow 연속 점프를 동일한 native 스냅샷으로 이전했습니다. 각 gamedata에는 발톱 공격, 돌진, locomotion 등 전용 호출만 남기고 공통 PathFollower 정의 중복을 제거했습니다.
  • Boomer를 ai_boomer_3.smx 3.0.4로, Spitter를 ai_spitter_3.smx 3.0.2로 올렸습니다. 연속 점프는 유효한 경로 미리보기 지점을 우선 사용하고, 경로가 오래되었거나 막혔거나 앞에 등반·틈 점프 같은 특수 이동이 있으면 기존 AI/직선 행동으로 되돌아갑니다.
  • 네 AI 플러그인은 체공 시간 예측, 벽·착지점·국지 단차 안전 검사, 점진적인 공중 속도 보정을 ai_path_movement.inc에서 공유합니다. Charger 전용 돌진, 재경로 탐색, 측면 교전 판단은 기존 상태 머신이 계속 담당합니다.
  • Boomer는 직접 시야를 잠시 잃은 모퉁이에서도 유효한 PathFollower 경로가 있으면 계속 접근하며, 26-07 AI2의 구토 각도 수정도 유지합니다.
  • 현재 2026-08 AnneHappy, Hardcore, Shotgun 설정은 AI3를 로드합니다. Anne26-07.cfg는 변경되지 않은 ai_boomer_2.smx와 ai_spitter_2.smx를 계속 로드하여 26-07 롤백 버전으로 유지합니다.

2026년 7월 26일 맵 기록 v2

  • l4d_stats.smx에 공통 ruleset_key, 규칙 버전, 기록 정책, 시간/난이도 프로필을 추가했습니다. Anne 계열은 최고 기록 랭킹을 유지하고, 협동/생존은 규칙별로 분리하며, 대전·리얼리즘 대전·스캐빈지는 최단 시간 비교 대신 라운드 기록으로 저장합니다.
  • map_runs, map_run_players, legacy_map_bests와 반복 실행 가능한 마이그레이션/롤백 스크립트를 추가했습니다. 기존 timedmaps/timedmap_runs는 유지하며, 이전 플레이어별 최고 기록과 실제 경기를 분리합니다.
  • NewAnneWeb의 플레이어 페이지, 맵 목록, 상세 페이지를 v2에 맞게 갱신했으며 규칙, 버전, 난이도/AI 단계, 팀 인원, Anne 리스폰 설정으로 필터링할 수 있습니다. 배포 순서는 docs/map_records_v2_migration.md를 참고하세요.

2026년 7월 23일 FastDL 맵 전환 안정성 수정

  • l4d2_blackscreen_fix.smx에서 비공개 엔진 함수 CNetworkStringTable::DeleteAllStrings() 호출을 제거해 맵 로딩 중 downloadables 테이블이 비워지면서 서버가 충돌하거나 watchdog에 의해 재시작될 가능성을 없앴습니다.
  • 다운로드 처리를 추가 전용 방식으로 변경했습니다. 각 맵에서는 우선 피드백 리소스만 추가하고, 실제 맵 전환 때 SourceMod 공개 API AddFileToDownloadsTable()로 춤 모델 3개와 무작위 음악 그룹 1개를 추가합니다. 문자열 테이블 항목을 삭제하거나 덮어쓰거나 복원하지 않습니다.
  • fornite_l4d.smx는 기존 전체 춤 리소스 다운로드 등록을 새 기본 비활성화 스위치 sm_emotes_add_downloads 안에 유지합니다. 현재 서버는 전역으로 로드되는 l4d2_blackscreen_fix.smx가 단계별 다운로드를 관리하며, 다른 서버는 이 스위치를 켜 기존 일괄 다운로드 방식으로 되돌릴 수 있습니다.

2026년 7월 22일 동적 난이도 PPM 수정

  • 자동 난이도가 round_start 시점에 팀 PPM을 너무 일찍 잠가 이후 관전자로 전환한 플레이어가 해당 라운드 난이도 계산에 남을 수 있던 문제를 수정했습니다.
  • 라운드 시작 시 난이도는 이제 임시 결과입니다. 안전실 안에서 생존자 명단이 바뀌면 임시 PPM을 무효화하고, 첫 생존자가 안전 구역을 벗어날 때 생존자 팀에 남아 있는 실제 플레이어만 다시 계산해 잠급니다. 관전자와 Bot은 제외됩니다.

2026년 7월 16일 신규 플레이어 다운로드 최적화

  • l4d2_blackscreen_fix.smx의 FastDL 배포 방식을 재구성했습니다. 신규 플레이어는 최초 접속 시 명중 피드백 아이콘과 게임 내장 파일이 아닌 피드백 사운드를 우선 다운로드하며, 춤 리소스는 실제 맵 전환 때 단계적으로 받습니다.
  • restrict_strings.cfg는 최초 접속 우선 목록으로 사용되며 피드백 소재 파일 44개와 중복 제거된 피드백 사운드 파일 17개를 포함합니다. 춤 모델은 최초 접속 시 다운로드하지 않습니다.
  • 춤 음악 60곡을 관리하는 deferred_strings.cfg를 추가하고 기본값으로 8개 그룹으로 나눴습니다. 실제 changelevel마다 무작위 그룹 하나를 배포하며, 8개 그룹이 모두 나오기 전에는 같은 그룹이 반복되지 않습니다. 한 바퀴가 끝나면 다시 섞습니다. sm_fixscreen_deferred_group_count로 그룹 수를 조정할 수 있고, 클라이언트 캐시에 이미 있는 파일은 게임이 자동으로 건너뜁니다.
  • 서로 의존하는 춤 모델 파일 3개는 첫 맵 전환 때 함께 다운로드해 일부 모델만 받은 상태에서 오류 모델이 표시되는 일을 방지합니다. 같은 맵 전환에서 무작위 음악 그룹 하나도 내려받으며, 이후에는 무작위 비반복 순서로 계속 보충합니다.
  • 피드백, 모델, 춤 음악 리소스 124개를 기존 디렉터리 구조를 유지한 .bz2로 만들어 NewAnneWeb 파일 관리로 이전했습니다. FastDL은 Cloudflare 캐시를 사용하는 http://anne.trygek.com/fastdl/left4dead2로 전환했습니다.

2026년 7월 15일 동적 난이도 수정

  • 제어 대상 CVar 기준값 검색이 뒤쪽 난이도 섹션을 누락할 수 있던 문제를 수정했습니다. 이제 level1부터 level6까지 명시적으로 검색하여 전문가 이하로 돌아갈 때 극한 또는 음리 전용 값이 남지 않습니다.
  • 동적 난이도에서 시선 올리기/회전이 활성화된 뒤 기존 ai_boomer_2가 아래쪽 각도를 유지해 바닥을 향해 토하던 문제를 수정했습니다. 최초 조준과 다중 대상 전환 모두 최소 5도의 상향 각도를 유지하며 연속 점프와 점프 토사 설정은 변경하지 않습니다.
  • 현재 사용 중인 AI 플러그인에 등록되지 않은 기존 CVar 4개를 제거했습니다. SourceMod에서 조회하고 변경할 수 있는 hidden/launcher CVar 3개는 설정을 유지합니다.
  • 고정 난이도에서 자동으로 전환할 때 PPM 데이터를 잠시 사용할 수 없으면 음리/고난도 CVar가 남던 문제를 수정했습니다. 이제 쉬움 난이도를 안전한 기준으로 먼저 적용하고 통계가 준비되면 자동 난이도를 다시 선택합니다.
  • 서버 콘솔/RCON의 sm_aidiff도 안전 구역 이탈 잠금에 걸려 다음 라운드로 지연되어 운영 난이도 변경이 적용되지 않은 것처럼 보이던 문제를 수정했습니다. 콘솔/RCON은 즉시 적용되며, 안전 구역 이탈 후 플레이어가 실행한 관리자 명령은 기존처럼 다음 라운드에 적용됩니다.
  • Boomer2와 Smoker3에 기본 비활성화된 공중 능력 CVar를 추가했습니다. 음리에서만 활성화되며 기존 연속 점프 행동은 그대로 유지됩니다. 추가 점프를 강제하지 않고 이미 공중에 있는 AI가 구토 또는 혀 능력을 사용할 수 있게 할 뿐이며, 지상 공격과 기존 발동 거리는 변경하지 않습니다.

2026년 7월 14일 동적 난이도 긴급 수정

  • 하드코딩된 level0가 1vHunters, Alone, WitchParty 등 모드별 값을 덮어쓰던 문제를 수정했습니다. 이제 각 모드 설정 적용이 끝난 뒤 실제 CVar 기준값을 기록하고 난이도 전환, 맵 변경, 플러그인 언로드 시 복원합니다.
  • 낮은 난이도에 잘못 추가된 z_lunge_up 0, z_lunge_interval 0.08, z_lunge_cooldown 0 등을 제거해 기존 ai_hunter_2에서 Hunter가 제자리에서 덮치거나 반복해서 웅크리던 문제를 수정했습니다. Jockey, Tank, Boomer 값이 의도치 않게 덮어써지는 문제도 방지합니다.
  • 극한 및 음리 난이도의 명시적 강화값은 유지하며, 해당 난이도를 벗어나면 현재 모드에서 기록한 기준값으로 복원합니다.

2026년 6월 12일-7월 3일 업데이트 기록

Anne 모드와 특수 감염자 스폰 시스템

  • Anne 계열 대전 모드에서 3인칭 시점을 사용할 수 없던 문제를 수정했습니다. !tp 및 3인칭 관련 cvar는 Anne 계열 공유 설정을 통해 적용됩니다.
  • infected_control.smx 리팩터링과 최적화를 계속 진행했습니다. Left4DHooks PVS/가시성 보조, 스폰 성능 설정, 큐 조정, 웨이브 판정 정리를 추가해 비정상 스폰, Smoker 이중 혀, 너무 빠른 보충 웨이브를 줄였습니다.
  • anne_cvar_shield.smx를 추가해 Anne 핵심 cvar를 보호합니다. 플러그인이나 투표 잔여 설정이 다른 모드 난이도로 섞이는 문제를 방지합니다.
  • 26-7 테스트 설정 Anne25-11.cfg를 추가하고, 롤백용으로 기존 infected_control25-11.smx를 유지했습니다. 동적 난이도 문서도 함께 갱신했습니다.
  • 극한 난이도 값이 다른 난이도에 영향을 주던 문제를 수정했습니다. 동적 난이도 전환 시 Tank, Hunter 등 단계별 파라미터가 더 명확히 초기화됩니다.
  • 일부 상황에서 안전실 근접 무기가 나오지 않을 수 있던 문제를 수정하고 MeleeInTheSafeRoom 로직을 정리했습니다.
  • 스폰 투표 메뉴 spawn_vote_menu.smx를 기존 SourceMod 메뉴로 되돌리고 외부 메뉴 의존성을 제거했습니다. Anne/캠페인 특수 감염자 수, 간격, 자동 모드, 텔레포트 검사는 항목을 선택할 때마다 단일 항목 투표를 시작하며, preset은 계속 전체 preset 기준으로 투표합니다.
  • not0721 계열 coop/community/mutation 모드, dirspawn preset, 무기 설정, SI limit 설정을 추가하고 정리했습니다.
  • infected_control에 배신자 모드 관련 로직, 스폰 preset 테이블, 다국어 문구를 추가해 이후 게임플레이 확장에 대비했습니다.

플레이어 경험, 투표와 피드백

  • l4d_stats.smx의 커스텀 맵 점수 정산을 조정했습니다. 일반 커스텀 맵은 실제 맵 시간이 5분을 초과한 경우에만 세이프룸 통과 점수와 강행 통과 보너스를 지급하고, 공식 맵은 기존 점수 규칙을 유지합니다. 커스텀 구출 맵에는 팀 통과 점수를 지급하지 않으며 맵 시간, 맵 기록, 특수 감염자 통계는 계속 정상 저장됩니다.
  • 기존 hextags를 hextags_lite.smx로 교체하고 칭호 색상 문제를 수정했습니다. 관리자 명령을 숨겼으며 설정 파일은 hextags_lite.cfg로 이동했습니다.
  • global_chat.smx, join.smx 및 관련 메시지에 다국어 문구를 보강했습니다. 전체 채팅 상태와 팀 찾기 수신 상태 문구도 추가했습니다.
  • l4d_stats.smx의 분기 랭킹과 영속화 세부 문제를 수정하고 라운드 상태 기록을 보강했습니다. 일부 데이터베이스 처리와 피크 시간 상태 쓰기 부담도 낮췄습니다.
  • 기존 killsound와 딩딩딩 사운드 투표 기능을 l4d2_hitsound.smx에 통합했습니다. 오래된 sound_on/off 투표 파일을 제거하고 명중, 처치, 헤드샷 사운드와 아이콘을 피드백 플러그인에서 통합 관리합니다.
  • spechud.sp를 업데이트했습니다. ping 표시, 보너스 비율 상세 표시, 관전 지연 표시 수정이 포함됩니다.
  • basevotes.smx를 비활성화하고 무차지 Hunter 직접 투표 항목을 삭제했습니다. Anne 동적 난이도 및 새 투표 메뉴와의 충돌을 줄입니다.
  • server_name.smx는 더 이상 SourceBans description을 직접 덮어쓰지 않습니다. 표시 처리는 외부 proxy 흐름으로 이동했습니다.
  • 피크 시간 언로드와 NPC 관리의 검사/안내 빈도를 낮춰 바쁜 시간대의 추가 부하를 줄였습니다.
  • 일부 관리자 명령 출력을 숨겨 플레이어 채팅 영역의 불필요한 메시지를 줄였습니다.

업스트림 동기화, 맵과 핵심 의존성

  • Left4DHooks, gamedata, include, 테스트 소스를 업데이트하고 새 native/forward를 보강했습니다. 관련 플러그인도 다시 빌드했습니다.
  • ai_tank3.smx를 업데이트해 RPG 점수 권한 처리와 극한 난이도 callback/파라미터 문제를 수정하고 Tank 행동 설정을 조정했습니다.
  • Dead Center 2025, City 17, No Echo m3, Carried Off cwm1_intro 등 업스트림 map/stripper 수정을 동기화했습니다. 적용 가능한 zonemod 변경은 zonemod_anne에도 반영했습니다.
  • cwm1_intro hittable/clipwall 수정과 여러 모드의 mapinfo.txt를 동기화했습니다.
  • Rust 서버 브라우저 도구와 Docker 배포 README를 이 저장소에서 제거했습니다. 관련 워크플로는 외부 도구/웹 경로에서 유지합니다.
  • basevotes.smx 위치, SourceMod 설정, 데이터베이스 필드, 여러 문서를 갱신해 플러그인 팩 구조를 더 깔끔하게 정리했습니다.