Files
2nd/10_Wiki/Topic_Programming/Architecture/Alliance_(동맹).md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +09:00

190 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: wiki-2026-0508-alliance-동맹
title: Alliance (동맹)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Guild, Clan, Faction, 동맹]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [game-design, social-systems, mmo, architecture]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: Game backend (Colyseus, Nakama, custom)
---
# Alliance (동맹)
## 매 한 줄
> **"매 player-formed group — shared goals, shared resources, shared identity"**. 매 MMO/SLG 의 retention 핵심 system. 매 EverQuest guild (1999) → World of Warcraft guild (2004) → Lords Mobile/Rise of Kingdoms 동맹 (2017+). 매 2026 modern SLG (4X/RTS hybrid) 의 core loop driver — solo player retention < 7 days, alliance member retention > 90 days 의 typical metric.
## 매 핵심
### 매 구조
- **Membership tier**: Leader / Officers (R4/R5) / Members / Recruits.
- **State**: roster, treasury, buff inventory, war declarations, territory.
- **Permissions**: hierarchical RBAC — invite/kick/promote/demote/disband.
- **Lifecycle**: create → recruit → grow → war → decline → disband.
### 매 server-authoritative invariants
- 매 single alliance per player 의 enforcement (atomic).
- Member cap (typical 50100) — atomic check-and-insert.
- Treasury balance — race-free debit/credit (transactional).
- War state machine — pending/active/peace transitions.
### 매 응용
1. **SLG 4X game** (Lords Mobile pattern) — alliance buffs, rallies, KvK.
2. **MMO guild** (WoW pattern) — guild bank, calendar, perk levels.
3. **Mobile RPG clan** (Clash of Clans pattern) — clan wars, donations.
4. **Social fitness app** (Strava clubs) — challenges, leaderboards.
## 💻 패턴
### Schema (Postgres)
```sql
CREATE TABLE alliances (
id BIGSERIAL PRIMARY KEY,
tag VARCHAR(5) UNIQUE NOT NULL,
name VARCHAR(40) NOT NULL,
leader_id BIGINT NOT NULL REFERENCES players(id),
member_cap SMALLINT NOT NULL DEFAULT 50,
treasury_gold BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE alliance_members (
player_id BIGINT PRIMARY KEY REFERENCES players(id),
alliance_id BIGINT NOT NULL REFERENCES alliances(id) ON DELETE CASCADE,
rank SMALLINT NOT NULL, -- 1=member..5=leader
joined_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON alliance_members (alliance_id);
```
### Atomic join (server)
```typescript
async function joinAlliance(playerId: bigint, allianceId: bigint) {
return await db.tx(async (t) => {
// 1. Player must not be in any alliance
const existing = await t.oneOrNone(
"SELECT 1 FROM alliance_members WHERE player_id=$1 FOR UPDATE", [playerId]);
if (existing) throw new Error("ALREADY_IN_ALLIANCE");
// 2. Member cap check (lock alliance row)
const a = await t.one(
"SELECT member_cap, (SELECT COUNT(*) FROM alliance_members WHERE alliance_id=$1)::int AS n " +
"FROM alliances WHERE id=$1 FOR UPDATE", [allianceId]);
if (a.n >= a.member_cap) throw new Error("ALLIANCE_FULL");
await t.none(
"INSERT INTO alliance_members(player_id, alliance_id, rank) VALUES($1,$2,1)",
[playerId, allianceId]);
});
}
```
### Permission check
```typescript
const PERMS = {
invite: 2, // R2+
kick: 3, // R3+
promote:4, // R4+
disband:5, // leader only
} as const;
function can(memberRank: number, action: keyof typeof PERMS): boolean {
return memberRank >= PERMS[action];
}
```
### Alliance chat (Redis pub/sub)
```typescript
// Publish
await redis.publish(`alliance:${allianceId}:chat`,
JSON.stringify({ from: playerId, msg, ts: Date.now() }));
// Subscribe (per connected client)
const sub = redis.duplicate();
await sub.subscribe(`alliance:${allianceId}:chat`, (raw) => {
ws.send(raw);
});
```
### War declaration state machine
```typescript
type WarState = "PEACE" | "PENDING" | "ACTIVE" | "COOLDOWN";
const transitions: Record<WarState, WarState[]> = {
PEACE: ["PENDING"],
PENDING: ["ACTIVE", "PEACE"],
ACTIVE: ["COOLDOWN"],
COOLDOWN: ["PEACE"],
};
function transition(from: WarState, to: WarState) {
if (!transitions[from].includes(to))
throw new Error(`INVALID_TRANSITION ${from}->${to}`);
}
```
### Treasury (idempotent donation)
```typescript
async function donate(playerId: bigint, amt: bigint, idemKey: string) {
await db.tx(async (t) => {
const dup = await t.oneOrNone(
"SELECT 1 FROM idempotency WHERE key=$1", [idemKey]);
if (dup) return;
await t.none("INSERT INTO idempotency(key) VALUES($1)", [idemKey]);
await t.none("UPDATE players SET gold = gold - $2 WHERE id=$1 AND gold >= $2", [playerId, amt]);
await t.none("UPDATE alliances SET treasury_gold = treasury_gold + $2 WHERE id=$1", [allianceId, amt]);
});
}
```
### Member roster cache invalidation
```typescript
async function onMembershipChange(allianceId: bigint) {
await redis.del(`alliance:${allianceId}:roster`);
await redis.publish(`alliance:${allianceId}:events`, JSON.stringify({type:"ROSTER_CHANGED"}));
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Casual mobile, < 30 members | Single-shard SQL, simple roster |
| MMO, 100+ members, real-time chat | Sharded SQL + Redis pub/sub |
| Cross-server alliance war (KvK) | Event-sourced log + global service |
| Persistent territory control | Server-authoritative grid + alliance ownership |
**기본값**: 매 Postgres alliance/member tables + Redis pub/sub for chat/presence + idempotent treasury operations.
## 🔗 Graph
- 변형: [[Guild]] · [[Clan]] · [[Faction]]
- Adjacent: [[Leaderboard]]
## 🤖 LLM 활용
**언제**: 매 long-session retention 의 game (MMO, SLG, persistent world), 매 social cooperation 의 core mechanic.
**언제 X**: 매 short-session arcade, 매 strict-PvP only without cooperation, 매 < 1k DAU 의 single-player feel.
## ❌ 안티패턴
- **Client-authoritative membership**: 매 cheat 의 trivial (forge join). 매 server-authoritative 만.
- **No member cap**: 매 mega-alliance dominance — 매 game balance 의 destruction.
- **Synchronous broadcast**: 매 large alliance (500+) 의 chat fan-out blocks. 매 async pub/sub 의 사용.
- **Disband without grace period**: 매 leader 의 grief vector. 매 24h cooldown.
## 🧪 검증 / 중복
- Verified (Lords Mobile design, EVE Online corporation system, WoW guild system, Clash of Clans clan system).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full content (game alliance system architecture) |