refactor(topics): 멀티 에이전트용 지식 재편 — _Common(공통 기본기) + Domain_* 구조
에이전트 8종(대화형/프로그래머 C·S/디자이너/설계자/기획자/QA/PD/PM)에게 [공통 기본 능력 + 롤별 Specialty] 2층으로 지식을 주입하기 위한 재분류. 문서 내용·포맷은 무수정, 폴더 이동만 (6,372개 문서 수 보존 확인). - Topic_Programming → Domain_Programming (내부 구조 보존) - Topic_Graphic → Domain_Design - Topic_Business → Domain_Product - Topic_General → Domain_General - _Common 신설: Math(구 Topic_Math_Specialty), Reasoning(구 General/From_Thinking & Reasoning), Reasoning_Creativity(구 General/From_창의성), Communication(Poetic_Blog_Writing + From_writing) - 타 도메인의 From_* 폴더는 유지 (출처 표기일 뿐, 이미 도메인에 맞게 분류된 문서) - 빈 폴더 정리 (memory/procedures) - 에이전트→폴더 매핑은 workspace의 .astra/agent-knowledge-map.json (9개 에이전트) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
---
|
||||
id: wiki-2026-0508-control-points
|
||||
title: Control Points
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Capture Points, KOTH, Domination]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [game-design, multiplayer, objectives, fps]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: csharp-gdscript
|
||||
framework: unity-godot-unreal
|
||||
---
|
||||
|
||||
# Control Points
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 spatial objective — 매 team 이 매 zone 의 occupy 통해 score/win."**. Control Points는 multiplayer game 의 가장 ubiquitous objective primitive. Domination, KOTH, Capture-and-Hold, Push, Hardpoint 모두 변형. Team Fortress 2, Battlefield, Overwatch, Apex, CS Bombsite (variant), Splatoon (area-based) 의 핵심. 2026년 server-authoritative netcode + lag compensation 패턴 매 stable.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 variants
|
||||
| Variant | Mechanic |
|
||||
|---|---|
|
||||
| **Domination** | Multiple points, hold majority for tickets/score |
|
||||
| **KOTH** | Single point, hold-time wins |
|
||||
| **Capture & Hold** | Capture sequentially, last team standing |
|
||||
| **Hardpoint** | Single rotating point, score over time |
|
||||
| **Push/Payload** | Mobile control point along track |
|
||||
| **Linear (5CP TF2)** | Sequential capture, central pivot |
|
||||
|
||||
### 매 mechanics
|
||||
- **Capture progress**: 0~100%, increases with attackers in zone.
|
||||
- **Multi-capture rate**: more attackers → faster (capped, e.g. 2x at 2+).
|
||||
- **Contest**: defenders inside → progress paused.
|
||||
- **Lock/unlock**: previous point capture unlocks next.
|
||||
- **Decay**: progress drops when zone empty (configurable).
|
||||
- **Overtime**: contested point prevents game end.
|
||||
|
||||
### 매 design principles
|
||||
- **Sightline balance**: defenders 의 advantage 와 attacker chokes 의 균형.
|
||||
- **Capture time**: too short → trivial, too long → stalemate. 5-15s typical.
|
||||
- **Cap-zone size**: encourages clustering vs spread.
|
||||
- **Spawn distance**: defender respawn 가 너무 가까우면 attack 불가.
|
||||
|
||||
### 매 응용
|
||||
1. FPS multiplayer modes (Overwatch, BF, CoD).
|
||||
2. MOBA jungle camps / objectives (Roshan, Drake areas).
|
||||
3. RTS resource nodes (StarCraft expansions).
|
||||
4. MMO PvP zones (WoW battlegrounds).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Unity C# — control point trigger
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
public class ControlPoint : NetworkBehaviour {
|
||||
public NetworkVariable<float> Progress = new(0f);
|
||||
public NetworkVariable<int> OwnerTeam = new(-1);
|
||||
|
||||
[SerializeField] private float captureRate = 10f; // pct/sec per attacker
|
||||
[SerializeField] private float maxRate = 20f;
|
||||
|
||||
private readonly Dictionary<int, int> teamCount = new();
|
||||
|
||||
private void OnTriggerEnter(Collider other) {
|
||||
if (!IsServer) return;
|
||||
if (other.TryGetComponent<Player>(out var p)) {
|
||||
teamCount.TryGetValue(p.Team, out var n);
|
||||
teamCount[p.Team] = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other) {
|
||||
if (!IsServer) return;
|
||||
if (other.TryGetComponent<Player>(out var p)
|
||||
&& teamCount.TryGetValue(p.Team, out var n)) {
|
||||
teamCount[p.Team] = Mathf.Max(0, n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void FixedUpdate() {
|
||||
if (!IsServer) return;
|
||||
|
||||
var teams = new List<KeyValuePair<int,int>>(teamCount);
|
||||
teams.Sort((a, b) => b.Value.CompareTo(a.Value));
|
||||
|
||||
if (teams.Count == 0 || teams[0].Value == 0) return;
|
||||
|
||||
// Contested: top two teams equal & nonzero
|
||||
if (teams.Count > 1 && teams[0].Value == teams[1].Value) return;
|
||||
|
||||
var attacker = teams[0];
|
||||
var rate = Mathf.Min(maxRate, captureRate * Mathf.Sqrt(attacker.Value));
|
||||
|
||||
if (OwnerTeam.Value == attacker.Key) {
|
||||
Progress.Value = Mathf.Min(100f, Progress.Value + rate * Time.fixedDeltaTime);
|
||||
} else {
|
||||
Progress.Value = Mathf.Max(0f, Progress.Value - rate * Time.fixedDeltaTime);
|
||||
if (Progress.Value <= 0f) OwnerTeam.Value = attacker.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Godot 4 / GDScript
|
||||
```gdscript
|
||||
extends Area3D
|
||||
class_name ControlPoint
|
||||
|
||||
@export var capture_rate: float = 10.0
|
||||
var progress: float = 0.0
|
||||
var owner_team: int = -1
|
||||
var team_in_zone: Dictionary = {}
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not multiplayer.is_server(): return
|
||||
|
||||
var top_team := -1
|
||||
var top_count := 0
|
||||
var contested := false
|
||||
|
||||
for team in team_in_zone:
|
||||
var n: int = team_in_zone[team]
|
||||
if n > top_count:
|
||||
top_count = n
|
||||
top_team = team
|
||||
contested = false
|
||||
elif n == top_count and n > 0:
|
||||
contested = true
|
||||
|
||||
if contested or top_count == 0: return
|
||||
|
||||
var rate = capture_rate * sqrt(top_count)
|
||||
if owner_team == top_team:
|
||||
progress = min(100.0, progress + rate * delta)
|
||||
else:
|
||||
progress = max(0.0, progress - rate * delta)
|
||||
if progress <= 0.0:
|
||||
owner_team = top_team
|
||||
_notify_capture(top_team)
|
||||
```
|
||||
|
||||
### Server-authoritative state with lag compensation
|
||||
```csharp
|
||||
// Server stores state snapshots for last 1s
|
||||
private readonly CircularBuffer<Snapshot> history = new(60);
|
||||
|
||||
public bool WasInsideAtTime(Vector3 playerPos, float clientTime) {
|
||||
var snap = history.SampleAt(clientTime);
|
||||
return snap.Bounds.Contains(playerPos);
|
||||
}
|
||||
```
|
||||
|
||||
### Configurable progression (data-driven)
|
||||
```json
|
||||
{
|
||||
"id": "cp_central",
|
||||
"captureTimeSeconds": 8,
|
||||
"multiCapMultiplier": [1.0, 1.5, 1.75, 2.0],
|
||||
"decayRate": 0.5,
|
||||
"unlockedBy": ["cp_a", "cp_b"],
|
||||
"scoresPerSecond": 10
|
||||
}
|
||||
```
|
||||
|
||||
### UI broadcast (client-side prediction visual)
|
||||
```typescript
|
||||
// Client receives Progress NetworkVariable changes,
|
||||
// interpolates between ticks for smooth bar fill
|
||||
useEffect(() => {
|
||||
const start = performance.now();
|
||||
const startVal = displayedProgress;
|
||||
const targetVal = serverProgress;
|
||||
|
||||
const tick = () => {
|
||||
const t = Math.min(1, (performance.now() - start) / 100);
|
||||
setDisplayedProgress(startVal + (targetVal - startVal) * t);
|
||||
if (t < 1) requestAnimationFrame(tick);
|
||||
};
|
||||
tick();
|
||||
}, [serverProgress]);
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Casual fast-match | KOTH, single-point, 3-5 min rounds |
|
||||
| Competitive | Linear/5CP, longer rounds, overtime |
|
||||
| Asymmetric | Push/Payload (attack vs defend) |
|
||||
| Objective rotation | Hardpoint (rotating zone keeps action moving) |
|
||||
| Large maps | Domination (multiple distributed) |
|
||||
|
||||
**기본값**: server-authoritative + 5-10s capture + multi-cap multiplier + decay + contested-pause + overtime.
|
||||
|
||||
## 🔗 Graph
|
||||
- 변형: [[Domination]]
|
||||
- Adjacent: [[Lag Compensation]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: multiplayer mode design, level layout review, balance tuning, netcode design for objectives.
|
||||
**언제 X**: single-player, asynchronous (turn-based), pure deathmatch (no objective).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Client-authoritative capture**: trivially exploitable — server-side only.
|
||||
- **Spawn too close to objective**: defender immortal — distance + lockout window.
|
||||
- **No contested-pause**: solo defender can't stall — feels unfair.
|
||||
- **Capture too short**: zerg wins, no skill — 8-12s standard.
|
||||
- **No decay**: half-cap then leave is permanent — partial progress decay.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Valve TF2 design / Blizzard Overwatch dev blogs / GDC talks 2018-2024).
|
||||
- 신뢰도 A-.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — variants + Unity/Godot impl + netcode |
|
||||
Reference in New Issue
Block a user