feat: Wiki 지식 자산 업데이트 - UX Scenarios, Frontend, Game Design, Topics 추가 [2026-05-08]
This commit is contained in:
@@ -0,0 +1 @@
|
||||
10_Wiki/Topics/Game_Design/Live and Deepspace.md missing
|
||||
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{"path":"10_Wiki/Topics/Computer_Vision.md","summary":"컴퓨터 비전은 픽셀 입력을 의미 있는 객체·관계·맥락으로 변환하는 영역으로, CNN→Transformer 기반 모델 발전과 함께 분류·검출·세그멘테이션·생성의 4대 축으로 분화되어 왔다.","content":"**추출된 패턴:** 합성곱(CNN)이 공간적 지역성을, ViT가 글로벌 어텐션을 잡으면서 도메인별로 둘을 섞은 하이브리드(Swin, ConvNeXt)가 표준이 됨.\n\n**세부 내용:**\n- **분류(Classification)**: 이미지 → 단일 라벨. ImageNet 벤치마크가 생태계를 견인.\n- **검출(Detection)**: 객체 박스 + 라벨. 2-stage(R-CNN 계열) vs 1-stage(YOLO/SSD/DETR)의 트레이드오프.\n- **세그멘테이션(Segmentation)**: 픽셀 단위 라벨. Semantic / Instance / Panoptic 3단계.\n- **생성(Generation)**: GAN→Diffusion으로 패러다임 이동. SD/DALL·E/Imagen 등.\n- **자기지도(Self-supervised)**: SimCLR, MAE, DINO 같은 라벨 없이 표현 학습."},
|
||||
{"path":"10_Wiki/Topics/CNN.md","summary":"합성곱 신경망(CNN)은 학습 가능한 커널의 슬라이딩으로 공간적 지역성과 가중치 공유를 동시에 잡아, 이미지 같은 격자 데이터에서 표현 학습의 표준이 된 아키텍처다.","content":"**추출된 패턴:** 컨볼루션-비선형-풀링의 반복으로 receptive field를 점진 확장 → 저수준 엣지에서 고수준 객체로 추상화가 자연스럽게 형성됨.\n\n**세부 내용:**\n- **핵심 연산**: Conv(공간 가중합) + Activation(ReLU 등) + Pool(다운샘플) + BatchNorm.\n- **대표 아키텍처**: LeNet→AlexNet→VGG→GoogLeNet→ResNet→DenseNet→EfficientNet→ConvNeXt.\n- **Residual connection**: 깊은 네트워크에서 그래디언트 소실을 우회하며 100층+ 학습을 가능케 함.\n- **한계**: 글로벌 컨텍스트 부족 → ViT/Hybrid 등으로 보완.\n- **응용**: 비전 외에도 음성·시계열·게놈 등 1D/3D 합성곱으로 확장."},
|
||||
{"path":"10_Wiki/Topics/AI_Sampling_Strategies.md","summary":"LLM 디코딩에서 다음 토큰을 고르는 방식(temperature·top-k·top-p·repetition penalty 등)이 출력의 다양성·일관성·환각 비율을 좌우한다.","content":"**추출된 패턴:** 결정적(greedy/beam) ↔ 확률적(sampling) 스펙트럼에서, 작업 유형(코딩=낮은 온도, 창작=높은 온도)에 맞춘 파라미터 매칭이 핵심.\n\n**세부 내용:**\n- **Greedy / Beam Search**: 항상 최고 확률만 선택. 코딩·번역에 적합하지만 단조로움.\n- **Temperature**: logit을 T로 나눠 분포 평탄화. T<1 보수적, T>1 다양함.\n- **Top-k**: 상위 k개 토큰만 후보. k=40~50이 흔함.\n- **Top-p (nucleus)**: 누적확률 p까지 컷오프. p=0.9~0.95가 표준.\n- **Repetition / Frequency Penalty**: 반복 토큰의 logit을 깎아 루프 방지.\n- **Min-p / Mirostat**: 최신 기법으로 perplexity 기반 동적 샘플링."},
|
||||
{"path":"10_Wiki/Topics/Reinforcement_Learning_and_Decision_Making.md","summary":"강화학습은 환경과 상호작용하며 누적 보상을 최대화하는 정책을 학습하는 프레임워크로, MDP 가정 위에서 가치 추정과 정책 개선의 두 축으로 발전해 왔다.","content":"**추출된 패턴:** 환경 모델 유무(Model-based vs Model-free), 가치 vs 정책 학습, 온폴리시 vs 오프폴리시 — 이 세 축으로 거의 모든 RL 알고리즘이 분류된다.\n\n**세부 내용:**\n- **MDP**: (S, A, P, R, γ) 5-튜플. 마르코프 가정 = 미래는 현재 상태에만 의존.\n- **가치 기반**: Q-learning, DQN, Double/Dueling DQN — 가치함수 추정 후 argmax 행동.\n- **정책 기반**: REINFORCE, A2C/A3C, PPO, TRPO — 정책 자체를 직접 최적화.\n- **모델 기반**: Dyna, MuZero, Dreamer — 환경 동역학을 학습해 시뮬레이션으로 효율 향상.\n- **현대적 응용**: RLHF(LLM 정렬), 로보틱스, AlphaGo/AlphaZero, 자율주행."},
|
||||
{"path":"10_Wiki/Topics/Reinforcement_Learning_Fundamentals.md","summary":"RL의 토대는 보상 가설·탐색-활용 트레이드오프·벨만 방정식 세 가지로 압축되며, 이 셋의 균형이 알고리즘 설계의 핵심 결정점이 된다.","content":"**추출된 패턴:** \"즉시 보상 vs 장기 보상\"의 시간 신용 할당이 모든 RL 문제의 본질이며, 할인계수 γ와 부트스트래핑 깊이가 이 균형을 조정하는 손잡이다.\n\n**세부 내용:**\n- **보상 가설(Reward Hypothesis)**: 모든 목표는 누적 스칼라 보상으로 표현 가능하다는 전제.\n- **벨만 방정식**: V(s) = E[R + γV(s')] — 가치 추정의 재귀적 정의.\n- **탐색-활용**: ε-greedy, UCB, Thompson sampling, entropy bonus 등.\n- **시간 차분(TD)**: MC와 DP의 절충. SARSA, Q-learning이 대표.\n- **함수 근사**: 상태 공간이 크면 NN/선형 근사 필요. 수렴성 이슈(Deadly Triad) 주의."},
|
||||
{"path":"10_Wiki/Topics/Neural_Networks_and_Deep_Learning_Foundations.md","summary":"심층 신경망은 미분 가능한 합성 함수의 스택으로, 표현 학습·역전파·확률적 경사하강이라는 세 기둥이 결합되어 비정형 데이터에서 패턴을 추출한다.","content":"**추출된 패턴:** \"깊이 + 적절한 비선형성 + 충분한 데이터 + 정규화\"의 4박자가 일반화를 결정하며, 이 중 어느 하나라도 무너지면 과적합 또는 수렴 실패로 이어짐.\n\n**세부 내용:**\n- **순전파/역전파**: 체인 룰로 모든 파라미터에 대한 손실의 기울기 계산.\n- **활성함수**: ReLU(기본), GELU(트랜스포머), Sigmoid/Tanh(게이트).\n- **최적화**: SGD+모멘텀, Adam, AdamW. Learning rate scheduling 중요.\n- **정규화**: BatchNorm, LayerNorm, Dropout, weight decay.\n- **표현 학습**: 사전학습→파인튜닝, 자기지도, 멀티태스크 등."},
|
||||
{"path":"10_Wiki/Topics/Theoretical_Foundations.md","summary":"AI/ML의 이론적 기반은 확률·통계·정보이론·최적화·계산복잡도가 교차하는 지점이며, 응용 모델 선택의 정당성을 이 층위에서 찾는다.","content":"**추출된 패턴:** 모든 학습 알고리즘은 결국 \"가설공간 + 손실함수 + 최적화 절차\"의 조합으로 환원되며, 각 선택의 통계적·계산적 트레이드오프를 이해하는 것이 핵심.\n\n**세부 내용:**\n- **확률·통계**: MLE, MAP, 베이즈 추론, 정보이론(엔트로피, KL divergence).\n- **최적화**: 볼록/비볼록, 1차/2차 방법, 확률적 경사, 라그랑지안 쌍대.\n- **PAC 학습**: 표본 복잡도, VC 차원, 일반화 이론.\n- **계산복잡도**: P/NP, 근사 알고리즘, 샘플링 기반 추론.\n- **표현이론**: 보편 근사 정리, 만성/유한 표현, 신경 정량화."},
|
||||
{"path":"10_Wiki/Topics/Test-time computing.md","summary":"Test-time compute는 학습 후 추론 단계에서 더 많은 연산을 투입(샘플 증강·체인 오브 사고·반복 증류 등)해 추가 학습 없이 정확도를 높이는 패러다임이다.","content":"**추출된 패턴:** \"파라미터 키우기\" 대신 \"추론 시간 키우기\" — OpenAI o1/DeepSeek-R1처럼 thinking tokens를 늘려 reasoning 깊이를 확장.\n\n**세부 내용:**\n- **Chain-of-Thought**: 중간 추론 단계를 명시적으로 생성.\n- **Self-Consistency**: 여러 샘플 후 다수결.\n- **Tree-of-Thoughts / Graph-of-Thoughts**: 분기 탐색.\n- **Verifier-Guided Search**: 검증자로 후보를 가지치기.\n- **Process Reward Model (PRM)**: 단계별 보상으로 reasoning 강화."},
|
||||
{"path":"10_Wiki/Topics/Self-Correction.md","summary":"자기 교정은 LLM이 자신의 출력을 비판·수정하는 능력으로, 외부 피드백 없이도 reasoning 품질을 높일 수 있는 중요 기제이지만 한계도 분명하다.","content":"**추출된 패턴:** Self-critic이 잘 작동하려면 (1) 검증이 생성보다 쉬워야 하고 (2) 모델이 자신의 오류를 식별할 메타인지가 있어야 함. 둘 중 하나라도 무너지면 자기 강화 환각으로 빠짐.\n\n**세부 내용:**\n- **Self-Refine**: 출력 → 비판 → 재생성 루프.\n- **Reflexion**: 에피소드 메모리 + 자기 반성 텍스트.\n- **Self-Consistency**: 여러 추론 경로 비교.\n- **한계**: 동일 모델로 비판하면 같은 편향 재생산. 외부 verifier가 더 강력함.\n- **연구 동향**: o1/R1 류 모델은 학습 단계에서 자기교정을 내재화."},
|
||||
{"path":"10_Wiki/Topics/Self-Correction Mechanisms.md","summary":"Self-correction 메커니즘은 LLM 추론 파이프라인 안에 검증·재시도 루프를 명시적으로 구조화한 기법군이다.","content":"**추출된 패턴:** 외부 verifier가 가능할수록 강력 — 코드(컴파일러), 수학(증명자), 검색(retrieval) 같이 정답이 검증 가능한 도메인에서 가장 효과적.\n\n**세부 내용:**\n- **Retry-with-feedback**: 실패 시그널 + 원인 텍스트를 다시 입력.\n- **Tool-augmented**: 코드 실행/검색/계산기로 결과를 검증.\n- **Critic-actor 분리**: 비판자와 실행자를 별도 모델로 분리하여 편향 감소.\n- **Constitutional AI**: 헌법 원칙 기반 자기 비판.\n- **검증 가능성 원칙**: \"verification ≪ generation\"인 도메인에서만 진정한 효과."},
|
||||
{"path":"10_Wiki/Topics/Reward Prediction Error (상태 예측 오류).md","summary":"보상 예측 오류(RPE)는 \"기대했던 보상 - 실제 받은 보상\"의 차이로, 도파민 신호의 신경과학적 모델이자 TD 학습의 핵심 신호다.","content":"**추출된 패턴:** 신경과학(VTA 도파민 뉴런 활동)과 강화학습 이론(TD-error)이 같은 수학을 공유 — 이 수렴이 \"뇌가 RL 기계인가\"라는 질문의 출발점.\n\n**세부 내용:**\n- **수식**: δ = r + γV(s') - V(s).\n- **양/음 RPE**: 기대보다 좋으면 양(보상 학습), 나쁘면 음(소거 학습).\n- **도파민 가설(Schultz)**: VTA/SNc 도파민 뉴런이 RPE를 인코딩.\n- **연관**: 중독·우울·파킨슨 같은 질환의 신경경제학적 모델 기반.\n- **알고리즘**: Q-learning, SARSA, Actor-Critic의 핵심 업데이트 신호."},
|
||||
{"path":"10_Wiki/Topics/Reward Prediciton Error.md","summary":"보상 예측 오류는 강화학습과 신경과학의 교차점에서 학습 신호를 통합 설명하는 핵심 개념이다.","content":"**추출된 패턴:** δ = r + γV(s') - V(s) 라는 동일 수식이 도파민 발화율과 가치함수 갱신 모두를 설명한다는 점이 \"학습은 곧 RPE 최소화\"라는 통합 가설의 근거.\n\n**세부 내용:**\n- 양의 RPE → 가치 함수 상향, 음의 RPE → 하향.\n- 사람·동물에서 도파민 phasic burst가 RPE와 일치.\n- TD-learning 알고리즘이 이 신호를 그대로 사용.\n- 환경 비정상성(non-stationarity) 하에서는 학습률 조정 필요.\n- LLM RLHF의 KL 페널티도 광의의 RPE 정규화로 볼 수 있음."},
|
||||
{"path":"10_Wiki/Topics/SSM.md","summary":"State Space Model(SSM)은 연속 시간 선형 동역학을 신경망으로 매개변수화한 시퀀스 모델로, Transformer 대비 선형 복잡도로 긴 컨텍스트를 처리할 수 있는 대안이다.","content":"**추출된 패턴:** \"행렬 A·B·C·D로 정의되는 ODE → 이산화 → 컨볼루션/RNN 형태로 효율 계산\" — 이 수학적 골격이 모든 SSM 변형의 기본 틀.\n\n**세부 내용:**\n- **수식**: h'(t) = Ah(t) + Bx(t), y(t) = Ch(t) + Dx(t).\n- **이산화**: ZOH/Bilinear로 (A,B) → (Ā,B̄).\n- **HiPPO 초기화**: 직교 다항식 기반으로 장기 메모리 보존.\n- **S4 / S5 / S6 (Mamba)**: 선택적 게이팅으로 데이터 의존성 도입.\n- **장점**: 추론 시 O(L) 시간·메모리. Transformer의 O(L²) 대비 유리."},
|
||||
{"path":"10_Wiki/Topics/Selective-SSM.md","summary":"Selective SSM(Mamba)은 입력에 따라 SSM 파라미터(B, C, Δ)를 동적으로 변화시켜, 기존 시간 불변 SSM의 한계를 극복하고 Transformer에 근접한 표현력을 확보한다.","content":"**추출된 패턴:** \"선택적 입력 의존성\"이 콘텐츠 기반 reasoning을 가능케 함 — Linear RNN의 효율과 어텐션의 표현력을 절충하려는 시도.\n\n**세부 내용:**\n- **핵심 수식**: B(x), C(x), Δ(x)가 입력 x의 함수.\n- **Hardware-aware 알고리즘**: parallel scan으로 GPU에서 효율 학습.\n- **벤치마크**: 언어 모델링·DNA·오디오에서 Transformer와 동등하거나 우월.\n- **한계**: in-context learning과 retrieval에서는 Transformer가 여전히 강함.\n- **하이브리드**: Jamba/Bamba처럼 SSM+어텐션 결합 모델 등장."},
|
||||
{"path":"10_Wiki/Topics/Jamba-and-Bamba.md","summary":"Jamba와 Bamba는 SSM(Mamba)과 Transformer 어텐션을 레이어 단위로 혼합한 하이브리드 모델로, 긴 컨텍스트 효율과 짧은 컨텍스트 표현력을 동시에 노린다.","content":"**추출된 패턴:** 순수 SSM은 retrieval에 약하고 순수 Transformer는 long-context 비용이 크므로, 두 블록을 인터리브해 양쪽 약점을 상쇄하는 설계.\n\n**세부 내용:**\n- **Jamba (AI21)**: 256K 컨텍스트, 12B activated / 52B 총 파라미터. MoE + SSM + Attention.\n- **Bamba (IBM/Meta)**: Mamba2 기반 + 부분 어텐션. 효율-품질 균형 강조.\n- **장점**: 메모리 풋프린트 감소, 추론 throughput 향상.\n- **트레이드오프**: 학습 복잡도 증가, 어텐션 비율 튜닝 필요.\n- **방향성**: 차세대 LLM 아키텍처의 유력 후보 중 하나."},
|
||||
{"path":"10_Wiki/Topics/Soft-Prompt-Compression.md","summary":"Soft prompt compression은 긴 자연어 컨텍스트를 학습 가능한 가상 토큰(소프트 프롬프트)으로 압축해, 추론 시 토큰 비용을 줄이면서 정보 손실을 최소화하는 기법이다.","content":"**추출된 패턴:** 자연어 토큰 → 임베딩 공간의 연속 벡터로 압축하면, 정보 밀도는 높지만 인간 해석성은 잃음 (\"black-box prompt\").\n\n**세부 내용:**\n- **GIST tokens**: 긴 instruction을 소수 게이트 토큰으로 증류.\n- **AutoCompressors**: LLM이 자기 출력을 누적 압축.\n- **Prefix tuning과의 차이**: 전자는 입력 압축, 후자는 태스크 적응.\n- **활용**: API 비용 절감, RAG 컨텍스트 압축, 에이전트 메모리.\n- **한계**: 압축률↑ 시 OOD 일반화 저하."},
|
||||
{"path":"10_Wiki/Topics/S2-Attn.md","summary":"Shifted Sparse Attention(S²-Attn)은 LongLoRA 등에서 사용된 효율적 어텐션 패턴으로, 긴 컨텍스트 파인튜닝 시 메모리·시간 비용을 줄이면서 글로벌 정보 흐름은 유지한다.","content":"**추출된 패턴:** \"local sparsity + 절반의 헤드 shift\"로 윈도우 경계 정보 누설을 방지 — 단순 슬라이딩 윈도우의 단점을 보완.\n\n**세부 내용:**\n- 그룹 크기 G로 시퀀스를 분할하여 그룹 내 어텐션만 계산.\n- 헤드의 절반은 G/2만큼 시프트해 다음 그룹 정보까지 흡수.\n- 학습 시 시간 복잡도 O(L·G)로 O(L²) 대비 큰 절감.\n- LongLoRA에서 32K~100K 컨텍스트 파인튜닝에 활용.\n- 추론 시에는 일반 어텐션으로 전환 가능."},
|
||||
{"path":"10_Wiki/Topics/CFG_스케일_제어.md","summary":"Classifier-Free Guidance(CFG) 스케일은 디퓨전 모델에서 조건부 생성과 무조건부 생성의 차이를 증폭하는 하이퍼파라미터로, 프롬프트 충실도와 다양성·자연스러움의 트레이드오프를 결정한다.","content":"**추출된 패턴:** CFG = 조건부 + s × (조건부 - 무조건부). s 값이 클수록 프롬프트에 충실하지만 색상 포화·아티팩트가 늘어남.\n\n**세부 내용:**\n- **공식**: ε̂ = ε(x, ∅) + s · (ε(x, c) - ε(x, ∅)).\n- **권장 범위**: SD 1.5/SDXL은 7~8, FLUX는 3~4가 표준.\n- **Dynamic Thresholding**: 고스케일에서 색 포화 방지.\n- **Guidance Distillation**: CFG 효과를 단일 forward로 압축(Lumina, Hunyuan-DiT).\n- **음수 가이던스**: 부정 프롬프트 강조."},
|
||||
{"path":"10_Wiki/Topics/AI_추론_및_맥락_인식_아키텍처.md","summary":"맥락 인식 아키텍처는 정적 가중치 외에 외부 메모리·검색·도구를 시점마다 동적으로 결합해, 모델 내부 지식의 한계를 시스템 수준에서 보완하는 패턴이다.","content":"**추출된 패턴:** \"파라미터에 모든 걸 외우게 하지 말고, 필요할 때 가져오게 하라\" — RAG·툴 호출·에이전트 루프가 이 원칙의 구체화.\n\n**세부 내용:**\n- **RAG**: 벡터 검색 + 생성. 최신성·도메인 특화 강함.\n- **툴 사용(Function calling)**: 계산·코드 실행·API 호출.\n- **메모리**: 단기(컨텍스트), 장기(벡터 DB), 에피소드 기억.\n- **에이전트 루프**: 관찰-사고-행동(ReAct) 사이클.\n- **트레이드오프**: 지연시간·비용·실패 모드 다양화."},
|
||||
{"path":"10_Wiki/Topics/Stability.md","summary":"AI 시스템의 안정성은 입력 perturbation·분포 변화·파라미터 변동 하에서 출력 일관성을 유지하는 능력으로, 신뢰성·재현성·안전성의 토대가 된다.","content":"**추출된 패턴:** 학습 안정성(loss 발산 방지)과 추론 안정성(출력 변동성)은 별개 — 전자는 옵티마이저·정규화 문제, 후자는 샘플링·탈옥 문제.\n\n**세부 내용:**\n- **학습 안정성**: gradient clipping, learning rate warmup, residual scaling.\n- **추론 안정성**: temperature 고정, deterministic flag, seed 관리.\n- **분포 외 강건성**: adversarial training, ensemble.\n- **재현성**: hardware nondeterminism (cuDNN), float precision.\n- **안전성**: jailbreak 저항, prompt injection 방어."}
|
||||
]
|
||||
@@ -0,0 +1,50 @@
|
||||
[
|
||||
{"path":"10_Wiki/Topics/Game_Design/Gacha Mechanics Analysis.md","summary":"가챠는 확률 기반 무작위 보상 시스템으로, 가변 비율 강화 스케줄과 손실 회피 심리를 활용해 ARPPU를 끌어올리지만 규제·윤리 리스크도 동반한다.","content":"**추출된 패턴:** \"확률 공개 + 천장(pity) + 픽업 → 신뢰 구축\"이 핵심. 한국·중국·일본 시장에서 천장 시스템이 사실상 표준.\n\n**세부 내용:**\n- **확률 구조**: 일반·고급·최고급의 다층 확률 (예: 0.7%, 5.85%, 93.45%).\n- **천장(Pity)**: N회 미만 픽업 보장 — 손실 회피 진정.\n- **픽업/스텝업**: 특정 캐릭터 확률 상승 또는 단계별 비용 감소.\n- **규제 동향**: 일본 콤프 가챠 금지, 한국 확률 공개 의무화, 중국 최소 보장 의무.\n- **데이터 지표**: ARPPU, 천장 도달률, 가챠 의존도 비율."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/가챠(Gacha) 시스템.md","summary":"가챠 시스템은 확률 보상의 도파민 루프를 게임 진행과 결합한 한·중·일 모바일 게임의 핵심 수익화 패턴이다.","content":"**추출된 패턴:** 단순 무작위 추첨이 아니라 \"수집 메타게임 + 픽업 + 천장\" 3박자가 들어가야 장기 LTV가 만들어진다.\n\n**세부 내용:**\n- 일반 / 픽업 / 콜라보 가챠로 분화.\n- 단챠 vs 10연챠(보너스+할인) 가격 구조.\n- 코스튬·무기·캐릭터 등 카테고리별 풀 분리.\n- 라이브옵스의 핵심 인벤토리 — 신규 가챠가 곧 신규 매출.\n- 한국 게임위 확률 표시 의무화(2024) 이후 \"투명성 마케팅\" 확산."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/가차(Gacha) 시스템.md","summary":"가차(가챠)는 확률 추첨을 통해 캐릭터·아이템을 획득하는 시스템으로, 모바일 RPG·수집형 게임의 핵심 매출원이다.","content":"**추출된 패턴:** 신뢰 가능한 확률·천장·픽업의 조합 없이는 장기 ARPPU를 유지하기 어렵다.\n\n**세부 내용:**\n- 일반 가챠 + 픽업 가챠 + 천장 시스템 = 표준 구성.\n- 도파민 루프와 손실 회피 심리에 의존.\n- 픽업 캐릭터 출시가 라이브옵스 핵심 이벤트.\n- 확률 공개 규제(KR 2024, JP/CN 시행) 영향 큼.\n- 광고비 없이도 SNS 입소문으로 매출 견인 가능."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/전리품 상자(Loot Box).md","summary":"루트박스는 무작위 보상 컨테이너로, 가챠와 동일한 메커니즘이지만 서구권 콘솔·PC 게임 맥락에서 더 강한 도박성 규제 논쟁을 받는다.","content":"**추출된 패턴:** \"실물 화폐로 직접 구매 가능한 무작위 보상\"은 벨기에·네덜란드에서 도박으로 분류 — 루트박스 디자인은 곧 법적 리스크 관리.\n\n**세부 내용:**\n- 게임 내 화폐 vs 현금 구매 vs 듀얼 통화 구조.\n- 보상 표시(공개 확률) + 픽업 + 무료 키.\n- 오버워치·FIFA·CS:GO 사례.\n- 벨기에/네덜란드 금지, 영국 GambleAware 권고.\n- 대안: Battle Pass 등 결정론적 모델로 이동."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Power Creep (Content Treadmills).md","summary":"파워 크리프는 신규 콘텐츠가 기존 콘텐츠보다 점진적으로 강해지는 현상으로, 단기 매출 부스트를 주지만 장기적으로 밸런스 붕괴와 신규 진입 장벽을 만든다.","content":"**추출된 패턴:** \"신규 캐릭터·장비를 더 강하게 만들어야 팔린다\"는 비즈니스 압력과 \"기존 콘텐츠의 가치 보존\"이라는 디자인 원칙이 충돌하는 구조적 문제.\n\n**세부 내용:**\n- 수직 진행(Power tier 상승) vs 수평 진행(Niche specialization).\n- 리워크 / 리밸런스 / 인플레이션 회수 메커니즘 필요.\n- 성능 정점을 회피한 디자인: 가위바위보 상성, 환경별 메타.\n- Hearthstone, MapleStory, FGO 사례.\n- 신규 유저 이탈 → 천장 비용 인상 → 추가 이탈의 악순환."},
|
||||
{"path":"10_Wiki/Topics/Game Design/Monetization/Power Creep.md","summary":"파워 크리프는 라이브 게임에서 신규 자원이 항상 더 강해지는 인플레이션 패턴으로, 매출과 밸런스의 트레이드오프를 만드는 핵심 변수다.","content":"**추출된 패턴:** 라이브 서비스 게임의 진행 곡선은 \"성능 인플레이션 + 회수 시스템\"의 두 핸들로 조율된다.\n\n**세부 내용:**\n- 상위 등급 도입(R→SR→SSR→UR) 패턴.\n- 능력치 캡 상향 vs 새 메커니즘 추가의 선택.\n- 회수: 인플레 통화 흡수 → 코어 자원 보존.\n- 신규 진입 패키지 / 점프 패스로 신규 유저 보호.\n- 성능 천장이 분명하면 \"수집 가치\"로 전환 가능."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/하이브리드 수익화.md","summary":"하이브리드 수익화는 IAP·광고·구독·배틀패스를 한 게임에 결합해 다양한 결제 의향 구간을 모두 흡수하는 전략이다.","content":"**추출된 패턴:** \"한 결제 모델 + 보조 모델\" 조합. 하이퍼캐주얼=광고+IAP, 미드코어=가챠+패스, 캐주얼=광고+구독.\n\n**세부 내용:**\n- 무과금 → 보상형 광고, 소액 결제 → 패스, 대형 결제 → 가챠 천장.\n- IAP가 광고 보상을 부정하지 않도록 \"광고 제거 + 패스\" 분리.\n- A/B 테스트로 모델 비율 최적화.\n- 글로벌 시장별 우세 모델: 동아시아=가챠, 서구=구독·패스.\n- LiveOps 캘린더와 묶여야 효과 극대화."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/하이브리드 수익화 (Hybrid Monetization).md","summary":"하이브리드 수익화는 단일 BM의 한계를 극복하기 위해 IAP, 광고, 구독을 같은 유저 경험 안에서 결합하는 모바일 게임의 표준 패턴이다.","content":"**추출된 패턴:** 결제 의향 구간(0원·소액·대량과금)별로 별도 진입점을 두되, 각 진입점이 다른 진입점의 가치를 깎지 않게 설계.\n\n**세부 내용:**\n- 광고 제거 IAP를 두면 광고 BM이 무너지므로 별도 reward stream으로 분리.\n- 패스: 소액 정기 결제로 안정적 LTV.\n- 가챠/번들: 비결제→소액→대형 결제로 단계적 안내.\n- LiveOps 이벤트와 결합해 시즌성 극대화.\n- KPI: ARPDAU, 결제율, ARPPU, 광고 ARPDAU."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/하이브리드 수익화(Hybrid Monetization).md","summary":"하이브리드 수익화는 광고+IAP+구독을 결합한 다층 BM으로, 무과금부터 고래까지 전 결제 스펙트럼을 흡수한다.","content":"**추출된 패턴:** 단일 BM은 LTV 천장이 명확하지만 하이브리드는 결제 단계별로 다른 가치 제안을 제시.\n\n**세부 내용:**\n- 무과금: 보상형 광고 시청 → 자원 / 회복.\n- 소액(F2P 트랜지셔너): 시즌 패스 4.99~9.99달러.\n- 미드: 번들·이벤트 가챠.\n- 대형: 한정 패키지·천장·VIP.\n- BM별 광고 인벤토리·IAP 진열·이벤트 캘린더의 정렬이 핵심."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/하이브리드 캐주얼(Hybrid-Casual).md","summary":"하이브리드 캐주얼은 하이퍼캐주얼의 단순 코어 루프에 미드코어의 메타·진행을 얹은 장르로, 광고 비용 상승을 IAP로 보완하는 진화 형태다.","content":"**추출된 패턴:** 하이퍼캐주얼 CPI 상승 + IAP 부재 → ROAS 붕괴 → 메타게임 추가로 LTV 확보.\n\n**세부 내용:**\n- 코어 루프는 직관적 단순함 유지(하이퍼캐주얼 DNA).\n- 메타: 컬렉션, 영웅 강화, 베이스 빌딩 등.\n- BM: 광고 + 하드 통화 + 패스.\n- 사례: Royal Match, Triple Match 3D, Last War.\n- 2023~2025년 모바일 매출 성장의 주역 장르."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/하이브리드 캐주얼(Hybrid-casual)의 하이브리드 수익화 모델.md","summary":"하이브리드 캐주얼 장르의 BM은 광고-광고 보상-IAP를 동시에 운영해 무과금 유저까지 LTV에 기여하게 만든다.","content":"**추출된 패턴:** 광고 시청 → 자원 → 진행 가속 → 결제 의향 형성, 이 깔때기를 광고와 IAP가 함께 만든다.\n\n**세부 내용:**\n- 광고: 인터스티셜 + 보상형, eCPM 추적 필수.\n- IAP: 패스(시즌·이벤트) + 한정 번들.\n- 광고 제거 IAP는 보상형 광고 보호 위해 별도 reward 가산.\n- LiveOps 캘린더: 주간 미니 이벤트 + 격주 큰 이벤트.\n- 측정: D7/D30 retention, ROAS, ARPDAU 분리."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/하이브리드 캐주얼 게임.md","summary":"하이브리드 캐주얼 게임은 단순한 코어와 깊은 메타를 결합한 모바일 장르로, 2023년 이후 가장 빠르게 성장한 카테고리다.","content":"**추출된 패턴:** \"5초 안에 이해, 30일 동안 깊어진다\" — 진입 장벽은 하이퍼캐주얼처럼 낮게, 잔존은 미드코어처럼 깊게.\n\n**세부 내용:**\n- 코어 루프: 매치-3, 머지, 체인 등 즉각 만족.\n- 메타: 베이스 건설, 영웅 컬렉션, 시즌 진행.\n- BM 구성: 광고 70% + IAP 30% → 점차 IAP 비중 확대.\n- 대표작: Royal Match, Last War, Project Makeover.\n- 마케팅: 광고 크리에이티브 자체가 게임 데모 역할."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/페이 투 윈(Pay to Win).md","summary":"페이 투 윈(P2W)은 결제로 직접 게임 내 경쟁 우위를 살 수 있는 BM으로, 매출 효율은 높지만 유저 정서·리텐션·평판 리스크가 크다.","content":"**추출된 패턴:** P2W ↔ Pay-to-Progress ↔ Pay-to-Customize의 스펙트럼에서 PvP 비중이 클수록 P2W 위험 노출↑.\n\n**세부 내용:**\n- 정의: 무과금 시간 투자로 따라잡기 어려운 격차.\n- Pay-to-Progress: 시간 단축은 OK, 절대값 격차는 NO 가이드.\n- 매칭: 결제력 기반 매칭으로 유저 보호.\n- 사례: 모바일 MMORPG가 자주 P2W로 분류됨.\n- 대안: Pay-to-Cosmetic(Fortnite), Pay-to-Convenience."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/지불 용의 (Willingness to Pay).md","summary":"지불 용의(WTP)는 유저가 특정 가치 단위에 대해 지불할 의향이 있는 최대 금액으로, 가격 책정과 번들 구성의 기초가 된다.","content":"**추출된 패턴:** WTP는 유저 페르소나별로 분포가 다름 — 같은 가격에도 \"비싸다/적당하다/싸다\"가 갈림. 번들·세그먼트로 다층 가격 제시 필요.\n\n**세부 내용:**\n- 측정: Van Westendorp PSM, Gabor-Granger.\n- 가격 차별: 첫 결제 할인, 신규 유저 패키지, VIP 한정.\n- 가격 anchoring: 더 비싼 옵션 옆에 두면 중간이 매력적.\n- 결제 통화 단위: 999원, $0.99 같은 가격 점.\n- 글로벌화 시 PPP 보정 필수(인도 ₹100 ≠ $1)."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/과금 의향 (Willingness to Pay).md","summary":"과금 의향은 유저별 결제 임계점으로, 정확히 측정하면 가격을 5~30% 올려도 결제율 손실 없이 매출을 늘릴 수 있다.","content":"**추출된 패턴:** \"가격을 낮추면 결제율↑\"은 종종 거짓 — 가격이 가치 신호로 작용해 너무 낮으면 오히려 회피되기도 함.\n\n**세부 내용:**\n- 무과금 / 미니멀 / 중과금 / 고래의 4분위.\n- 패키지 가격대: $0.99 / $4.99 / $19.99 / $99.99 그리드.\n- 전환 깔때기: 첫 1$ → 첫 10$ → 첫 100$ 마일스톤.\n- 시간대별 다른 WTP — 주말·이벤트에 결제율 상승.\n- A/B 테스트로 가격 -10%/+10% 실험 권장."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/인앱 구매 (IAP).md","summary":"인앱 구매(IAP)는 게임·앱 안에서 디지털 자원·상품을 구매하는 BM으로, 모바일 게임 매출의 60~80%를 차지하는 가장 큰 수익원이다.","content":"**추출된 패턴:** 한정·번들·인플레이션·VIP의 4축이 IAP 매출의 90%를 만든다 — 무한정 일반 상점은 효과 약함.\n\n**세부 내용:**\n- 소비형(가챠 키, 자원) vs 영구형(영웅, 코스튬) IAP 분리.\n- 시간/수량 한정 + 첫 구매 부스트로 결제 트리거.\n- 번들: 단일 가격보다 30~70% 가치 보이기.\n- 패스(Battle Pass): 안정적 정기 매출.\n- 결제 수수료: Apple/Google 30% (소형 사업자 15%)."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/인앱 구매(IAP).md","summary":"IAP는 모바일 게임 매출의 핵심으로, 한정·번들·천장·패스의 4가지 패턴이 매출 곡선을 결정한다.","content":"**추출된 패턴:** 무료 게임의 매출 = 결제율 × ARPPU × DAU. 결제율은 디자인, ARPPU는 가격 구조, DAU는 LiveOps가 좌우.\n\n**세부 내용:**\n- 첫 결제(First Time Purchase) 깔때기 최적화.\n- 한정성: 시간(72h) / 수량(N개) / 자격(VIP).\n- 번들 가치: 정가 대비 30~70% 할인 표시.\n- 패스 시즌: 4~6주 주기.\n- 광고 제거 IAP는 광고 BM과 호환되도록 별도 reward."},
|
||||
{"path":"10_Wiki/Topics/Economics/IAP_In_App_Purchase.md","summary":"In-App Purchase는 디지털 상품을 앱 내부에서 결제하는 표준 메커니즘으로, 플랫폼 수수료·환불 정책·세금이 BM 설계에 직접 영향을 준다.","content":"**추출된 패턴:** 30% 수수료를 전제로 단가·번들·통화 단위를 설계해야 마진이 보존됨. 신규 유저 첫 결제는 환불률이 높아 별도 추적.\n\n**세부 내용:**\n- App Store / Play Store 결제 시스템 의존.\n- 소형 개발자 프로그램: 첫 100만 달러 매출은 15%.\n- 환불 정책: iOS는 Apple 직영, Android는 개발자 처리.\n- 가격 점: $0.99, $4.99, $19.99, $49.99, $99.99.\n- 결제 데이터 분석: 결제 빈도·간격·금액 분포로 세그먼트."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/인앱 결제(IAP).md","summary":"인앱 결제는 모바일 게임 BM의 표준으로, 가격 구조·번들·플랫폼 수수료를 모두 고려한 설계가 필요하다.","content":"**추출된 패턴:** 결제율(전환)과 ARPPU(객단가)를 분리해서 측정·최적화 — 둘은 종종 trade-off 관계.\n\n**세부 내용:**\n- 결제 단가별 분포 모니터링.\n- 결제 첫 이벤트(D0~D3)에 집중 마케팅.\n- 동일 SKU도 지역별 PPP 보정 가격.\n- 환불 정책 명시.\n- VIP 등급 시스템과 결합해 LTV 누적."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/인앱 광고 (IAA).md","summary":"인앱 광고(IAA)는 광고 노출/클릭/설치로 매출을 만드는 BM으로, 무과금 유저까지 매출 풀에 편입시키는 핵심 수단이다.","content":"**추출된 패턴:** 광고 ARPDAU = eCPM × 노출수. 보상형 광고가 인터스티셜보다 LTV 친화적 — 유저 가치 인식과 진행 가속이 결합되기 때문.\n\n**세부 내용:**\n- 형태: 배너·인터스티셜·보상형(rewarded)·플레이어블.\n- 미디에이션(SDK): AppLovin MAX, ironSource, Google AdMob.\n- 측정: eCPM, fill rate, ARPDAU(ad).\n- 보상형 우대: \"내가 선택해서 보는 광고\"라는 인식.\n- 너무 잦은 광고 → retention 손상 → 균형 필수."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/인앱 광고(IAA).md","summary":"IAA는 광고 노출·시청·설치로 매출을 만드는 광고 기반 BM으로, 하이퍼캐주얼·캐주얼 게임의 주요 수익원이다.","content":"**추출된 패턴:** 광고 형식 × 빈도 × 보상의 조합이 ARPDAU를 결정 — 무리한 노출은 단기 매출↑·장기 매출↓.\n\n**세부 내용:**\n- 보상형이 가장 LTV 친화적.\n- 인터스티셜은 게임 흐름 단절 → 매 N판마다 1회 정도.\n- 배너는 ARPDAU 낮지만 항상 노출 가능.\n- 미디에이션 워터폴/비딩 조합으로 eCPM 최적화.\n- 광고 제거 IAP의 가격은 평균 LTV의 광고 부분 환산."},
|
||||
{"path":"10_Wiki/Topics/Economics\\IAA_In_App_Advertising.md","summary":"In-App Advertising은 무과금 유저까지 매출에 편입하는 광고 BM으로, 모바일 게임 LTV의 30~50%를 차지하는 경우가 많다.","content":"**추출된 패턴:** \"광고 = 무과금의 IAP\" — 무과금 유저는 시간을 화폐로 환산해 광고를 \"구매\"하는 셈.\n\n**세부 내용:**\n- 인벤토리: 광고 제공자(supply) ↔ 광고주(demand).\n- 미디에이션 SDK: AppLovin, ironSource, Unity.\n- 보상형 광고가 retention과 매출 모두 우수.\n- ATT (iOS 14.5+) 후 추적 제한 → SKAd 활용.\n- 보상형 → 인터스티셜 → 배너 순으로 매출 기여 큼."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/게임 내 광고(IAA).md","summary":"게임 내 광고는 보상형·인터스티셜·배너의 3축으로 운영되며, eCPM과 노출 빈도의 곱으로 ARPDAU를 결정한다.","content":"**추출된 패턴:** 광고 BM은 \"광고 자체의 가치\"보다 \"광고가 게임 진행을 가속하는 정도\"가 retention과 LTV에 영향.\n\n**세부 내용:**\n- 보상형: 게임 자원 가속 → 친화적.\n- 인터스티셜: 자연스러운 break point에 배치.\n- 배너: 메인 화면 일부 차지 → eCPM 낮음.\n- 광고 빈도 캡(Frequency Cap)으로 유저 보호.\n- 광고 제거 IAP는 광고로 얻을 자원도 동일 보상해야."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/CPI (Cost Per Install).md","summary":"CPI는 신규 유저 1명 설치당 마케팅 비용으로, 게임 사업의 단위 경제(Unit Economics) 핵심 지표다.","content":"**추출된 패턴:** ROAS = LTV ÷ CPI > 1.0 이어야 광고 캠페인이 수익. 시장·플랫폼·국가별로 큰 편차.\n\n**세부 내용:**\n- 측정: 광고비 ÷ 설치수.\n- 시장별 차이: 미국 $5+, 동남아 $0.5~1.\n- 플랫폼: iOS > Android (구매력 격차).\n- 게임 장르: 미드코어 > 캐주얼 > 하이퍼캐주얼.\n- ROAS D7/D30/D90 추적으로 캠페인 최적화."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/사용자 확보 (User Acquisition).md","summary":"사용자 확보(UA)는 유료/유기적 채널을 통해 신규 유저를 유입시키는 마케팅 활동으로, 게임 LiveOps의 절반 이상의 예산이 여기 투입된다.","content":"**추출된 패턴:** UA는 \"누구를(타겟)·얼마에(CPI)·얼마나 오래(LTV)\" 가져오는지의 3차원 최적화.\n\n**세부 내용:**\n- 채널: Meta, Google, TikTok, AppLovin, Unity.\n- 광고 크리에이티브 A/B 테스트 (수십~수백 개 동시).\n- 사용자 페르소나별 입찰 전략 (lookalike).\n- iOS ATT 이후 SKAdNetwork 데이터 의존.\n- 유기적 UA(ASO, 인플루언서)도 병행."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/User Acquisition (UA).md","summary":"UA는 디지털 광고를 통해 신규 유저를 유료 획득하는 활동으로, 정밀한 타겟팅과 ROAS 최적화가 비즈니스 생사를 가른다.","content":"**추출된 패턴:** \"광고 크리에이티브 → 설치 → D1 retention → D7 결제\"의 깔때기 각 단계 효율을 별도 측정·최적화.\n\n**세부 내용:**\n- 크리에이티브 다양성: 게임플레이·UGC·메타 광고.\n- 입찰 전략: tCPA, ROAS, App Install Optimization.\n- 어트리뷰션: AppsFlyer, Adjust, Singular.\n- iOS 14.5+ ATT 동의율 따라 데이터 변동.\n- 유기/유료 비율로 마케팅 효율 측정."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/플레이어 잔존율(Player Retention).md","summary":"잔존율(Retention)은 D1/D7/D30 시점에 유저가 게임에 다시 들어오는 비율로, F2P 게임의 LTV·UA 효율을 결정하는 가장 중요한 지표다.","content":"**추출된 패턴:** D1 retention은 첫 30분 경험, D7는 코어 루프 중독성, D30는 메타게임·소셜이 좌우.\n\n**세부 내용:**\n- D1 표준: 30~50%, D7: 10~25%, D30: 3~10% (장르별).\n- 첫 세션 길이·완료율이 D1을 좌우.\n- 일일 미션·이벤트 캘린더가 D7~D30 중요.\n- 길드/친구 시스템은 D30+ retention 핵심.\n- 코호트별 retention 곡선 비교(자연-유료, 채널별)."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/잔존율(Retention).md","summary":"잔존율은 신규 유저가 시간 경과 후에도 게임을 계속하는 비율로, F2P BM의 토대가 되는 단일 가장 중요한 KPI다.","content":"**추출된 패턴:** D1=hook, D7=loop, D30=meta — 각 구간이 다른 디자인 책임을 갖는다.\n\n**세부 내용:**\n- 측정: 코호트 분석으로 days-since-install별 활성률.\n- D1 강화: 튜토리얼·즉각 보상·간단한 첫 승리.\n- D7 강화: 일일 미션·진행 마일스톤.\n- D30 강화: 길드·시즌·서사·소셜 그래프.\n- Predicted LTV는 retention curve 적분에 ARPDAU 곱."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/고객 유지율(Retention).md","summary":"고객 유지율은 유저가 일정 기간 후에도 활성 상태로 남는 비율로, F2P 게임 BM의 모든 KPI를 떠받치는 토대다.","content":"**추출된 패턴:** Retention 1%p 개선이 LTV에 미치는 영향은 결제율 1%p 개선보다 큰 경우가 많다.\n\n**세부 내용:**\n- D1 부진 → 첫 인상·온보딩 문제.\n- D7 부진 → 코어 루프 단조로움.\n- D30 부진 → 콘텐츠·소셜 부족.\n- 푸시·이메일 reactivation 캠페인.\n- 시즌 패스가 강력한 retention 도구."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Live Operations (LiveOps).md","summary":"LiveOps는 출시 후 게임을 지속 운영하며 콘텐츠·이벤트·BM·밸런스를 동적으로 조정해 LTV를 극대화하는 활동이다.","content":"**추출된 패턴:** LiveOps 캘린더는 일일·주간·월간·시즌의 4단 시계열로 구성 — 각 주기마다 다른 retention/매출 책임.\n\n**세부 내용:**\n- 일일 미션·로그인 보상.\n- 주간 이벤트(특별 던전·보스).\n- 월간 패스·신규 캐릭터 가챠.\n- 시즌(분기) 대규모 콘텐츠 패치.\n- 데이터 모니터링 + 핫픽스 + 마케팅 동기화."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/라이브옵스(Live-ops).md","summary":"라이브옵스는 라이브 게임 운영팀이 매일/매주 콘텐츠와 BM을 운영하며 KPI를 최적화하는 프로세스다.","content":"**추출된 패턴:** \"제품팀이 만든 게임\"이 아닌 \"운영팀이 끊임없이 변형하는 서비스\" — 모바일 게임의 표준 모델.\n\n**세부 내용:**\n- 캘린더 운영: 다음 4~12주 이벤트 사전 기획.\n- A/B 테스트로 가격·UI·BM 실시간 조정.\n- 코호트 모니터링: 신규/리텐션/이탈.\n- 글로벌 + 지역별 운영 분리(EU/JP/KR/CN).\n- 마케팅 ↔ 콘텐츠 ↔ BM 3축 동기화."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/라이브 서비스 (Live Service).md","summary":"라이브 서비스 게임은 한 번 사고 끝이 아닌 지속 업데이트로 운영되는 게임 모델로, 모바일·PC·콘솔 모두에서 표준이 되었다.","content":"**추출된 패턴:** Game-as-a-Product → Game-as-a-Service. 출시는 시작점일 뿐, 매출의 90%는 출시 후 12개월에 발생.\n\n**세부 내용:**\n- 시즌 모델: Fortnite, Apex, Destiny.\n- 콘텐츠 캘린더 + 라이브 패치.\n- BM: 시즌 패스 + 코스튬 + 한정 번들.\n- 운영 인력: 콘텐츠/밸런스/마케팅/CS/QA 상시.\n- 위험: 콘텐츠 가뭄 시 빠른 이탈."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/무료 플레이(Free-to-Play) 모델.md","summary":"F2P 모델은 다운로드·기본 게임을 무료로 제공하고 IAP/광고로 수익을 내는 BM으로, 모바일 게임 매출의 95%+를 차지한다.","content":"**추출된 패턴:** \"무료 진입 + 결제 의향 분포 흡수\" — 결제율 1~5%만으로도 충분한 매출, 단 LTV가 충분히 길어야 함.\n\n**세부 내용:**\n- 진입 장벽 0 → 대규모 유입 가능.\n- 결제율(Conversion): 통상 1~5%, 우수작 5~15%.\n- ARPPU 분포: 고래(>$100/월)가 매출의 50%+ 차지.\n- 무과금 유저도 광고로 수익 기여.\n- 디자인 트레이드오프: 진행 속도 vs 결제 압박."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/부분 유료화(Free-to-Play).md","summary":"부분 유료화는 본 게임을 무료로 제공하고 추가 콘텐츠·편의성·아이템을 유료로 판매하는 BM이다.","content":"**추출된 패턴:** \"무료 코어 + 유료 확장\"의 균형 — 유료가 너무 약하면 매출 부족, 너무 강하면 P2W 비난.\n\n**세부 내용:**\n- 외형(코스튬) 위주: Pay-to-Customize (Fortnite).\n- 시간 단축: Pay-to-Skip-Grind (모바일 RPG).\n- 콘텐츠 잠금: Pay-to-Unlock (DLC 모델).\n- 가챠: Pay-to-Chance.\n- 한국·일본·중국 모바일에서 P2W 비율 높음."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/부분 유료화 메타게임(Free-to-play metagame).md","summary":"F2P 메타게임은 코어 루프 위에 얹혀진 장기 진행/수집/사회 시스템으로, 결제 동기와 retention의 핵심 동력이다.","content":"**추출된 패턴:** 코어 루프(즉각 만족) + 메타게임(장기 보상) + LiveOps(외부 시간 압박) 3층으로 LTV 곡선이 만들어진다.\n\n**세부 내용:**\n- 영웅 컬렉션 / 장비 강화 / 베이스 빌딩.\n- 시즌·이벤트·랭킹 같은 시간 한정 메타.\n- 길드·동맹 같은 소셜 메타.\n- 결제 트리거가 메타게임 단계에 자연스럽게 배치.\n- 메타가 단조로우면 컨텐츠 가뭄 → 빠른 이탈."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/소액 결제 (Microtransactions).md","summary":"소액결제는 $0.99~$9.99 단위의 작은 IAP로, 첫 결제 장벽을 낮추고 결제 빈도를 높이는 도구다.","content":"**추출된 패턴:** 소액 결제는 매출 자체보다 \"결제 습관 형성\"의 가치가 큼 — 첫 $1 결제 유저는 다음 $10/$100로 진화 확률↑.\n\n**세부 내용:**\n- 가격 점: $0.99 (가장 마찰 적음).\n- 첫 결제 부스트: 2배·3배 보상.\n- 일일 / 주간 한정 패키지.\n- 누적 결제 마일스톤 보상.\n- 모바일 RPG는 평균 ARPPU 30~80달러."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Dynamic Offers.md","summary":"동적 오퍼는 유저 행동·결제 이력·진행 단계에 맞춰 실시간으로 패키지·가격을 개인화하는 BM 시스템이다.","content":"**추출된 패턴:** 같은 가격이라도 다른 유저에게는 다른 보상 묶음 — 결제 의향(WTP) 추정에 기반한 1:1 가격 차별.\n\n**세부 내용:**\n- 추천 엔진(머신러닝)으로 패키지 선택.\n- 진행 단계별 적합 자원 추천.\n- 결제 이력으로 가격대 조정.\n- A/B 테스트로 학습.\n- 윤리·규제 우려: 가격 차별 공정성."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Data-Driven Personalization.md","summary":"데이터 기반 개인화는 유저별 행동 시그널을 활용해 콘텐츠·BM·UI를 동적으로 조정하는 LiveOps 기법이다.","content":"**추출된 패턴:** 결제 이력 + 진행 단계 + 활동 패턴 = 유저 세그먼트 → 세그먼트별 다른 경험 제공.\n\n**세부 내용:**\n- 추천 시스템: 다음 콘텐츠/패키지 예측.\n- 난이도 조정(DDA): 잔존율 ↑.\n- 동적 오퍼: WTP 기반 가격.\n- 광고 빈도 개인화.\n- 프라이버시 고려: 동의 + 익명화."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/동적 가격 책정(Dynamic Pricing).md","summary":"동적 가격 책정은 시간·유저·상황에 따라 가격을 변화시키는 전략으로, 매출 극대화와 공정성 인식 사이의 균형이 관건이다.","content":"**추출된 패턴:** 가격 차별(price discrimination)은 매출↑ 그러나 \"누군가 더 싸게 샀다\"는 인식이 신뢰 손상으로 이어질 수 있음.\n\n**세부 내용:**\n- 시간 기반: 출시 직후 할인 → 정가 회복.\n- 세그먼트 기반: VIP 우대, 신규 유저 패키지.\n- 행동 기반: 이탈 예측 → 윈백 할인.\n- 지역 기반: PPP 보정.\n- 투명성 vs 효율의 트레이드오프."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/Dynamic Pricing.md","summary":"Dynamic pricing은 수요·공급·유저 행동에 따라 실시간 가격을 조정해 매출을 극대화하는 시스템이다.","content":"**추출된 패턴:** 게임에선 직접 가격 변동보다 \"보상량 변동\"이 더 흔함 — 같은 $10에도 보상 50%~150% 변동.\n\n**세부 내용:**\n- 가격 알고리즘: 베이지안 최적화, 강화학습.\n- 윤리 가이드: 차별 정도·정당화 기준.\n- 측정: 결제율·ARPPU·만족도 동시 추적.\n- 한계: 신뢰·법적 리스크.\n- 산업 사례: Uber surge, 항공권, 호텔."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/탭과 싱크(Taps and Sinks).md","summary":"탭(Source)과 싱크(Sink)는 게임 내 통화의 유입과 소모로, 인플레이션 통제와 결제 동기 관리의 양 끝점이다.","content":"**추출된 패턴:** Sources × Sinks 균형이 무너지면 인플레이션→통화 가치 붕괴 또는 디플레이션→결제 동기 상실.\n\n**세부 내용:**\n- Source: 일일 보상, 미션, 이벤트, 가챠 환원.\n- Sink: 강화 비용, 가챠, 코스튬, 패스 구입.\n- 신규 콘텐츠는 새 Sink 도입 기회.\n- 데이터: 통화별 발행량·소비량 추적.\n- 화이트박스 시뮬레이션 = Machinations."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/수도꼭지와 배수구(Taps and Sinks).md","summary":"수도꼭지(Tap)는 게임 내 자원·통화의 발생 지점, 배수구(Sink)는 소모 지점으로, 둘의 균형이 게임 경제의 건강성을 결정한다.","content":"**추출된 패턴:** Tap > Sink → 인플레이션, Tap < Sink → 결제 압박. 시즌마다 새 Sink로 통화 흡수.\n\n**세부 내용:**\n- 디자인 패턴: 강화·가챠·시즌 패스가 주요 Sink.\n- 거래 시스템 도입 시 Tap-Sink 균형 더 복잡.\n- 시뮬레이션 도구: Machinations.io.\n- 데이터: 자원별 mint/burn 비율.\n- 통화 분리: 소프트(쉬움) / 하드(결제) 분리."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/자원 소모처(Sinks).md","summary":"자원 소모처(Sink)는 게임 내 통화를 회수해 인플레이션을 통제하고 결제 동기를 만드는 메커니즘이다.","content":"**추출된 패턴:** 강한 Sink 없이 Source만 있으면 통화 가치 폭락 → 매출 붕괴. 시즌 패스·가챠가 가장 효과적인 Sink.\n\n**세부 내용:**\n- 영구 Sink: 캐릭터 영구 해금.\n- 소비 Sink: 키·티켓·소모성 자원.\n- 한정 Sink: 시즌 한정 보상.\n- 강화 Sink: 무한 흡수 가능 (등급 한도 없으면).\n- 거래·경매 시스템은 양면 Sink."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/Play-and-Earn.md","summary":"Play-and-Earn은 P2E의 진화형으로, 게임 자체가 재미있어야 함을 전제로 일부 보상을 외부 가치로 교환할 수 있게 하는 모델이다.","content":"**추출된 패턴:** P2E의 \"돈벌이 동기\" 단점을 보완 — \"먼저 재밌게 하고, 잘하면 보상\"의 순서. 그러나 토크노믹스 안정성은 여전히 도전.\n\n**세부 내용:**\n- 게임성 우선, 토큰은 부수적 보상.\n- 토큰 발행량 제한·소각 메커니즘.\n- KYC·세금·규제 이슈.\n- 사례: Sky Mavis(Pixels, Axie), Yuga Labs.\n- 비판: 결국 P2E의 본질적 폰지 위험은 잔존."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/웹3 및 토크노믹스 모델링(Web3 and Tokenomics Modeling).md","summary":"웹3 게임은 자산을 NFT/토큰으로 표현하고 거래 가능하게 만드는 모델이지만, 토크노믹스 설계 부실과 투기 유입으로 대다수가 실패해 왔다.","content":"**추출된 패턴:** 토큰 가격 ≠ 게임 가치 — 시장 기대만 부풀려진 게임은 토큰 가격 폭락 시 동시에 무너짐. 게임성 자체가 토큰 가치 토대.\n\n**세부 내용:**\n- 듀얼 토큰: governance(희소) + utility(인플레).\n- NFT: 캐릭터·아이템·랜드.\n- Sink 메커니즘: 강화·소각·페그.\n- 진입 장벽: 지갑·KYC·가스비.\n- 사례: Axie 흥망, STEPN, Pixels."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Hedera(HCS 및 Fauxkens).md","summary":"Hedera는 Hashgraph 합의 알고리즘 기반 분산 원장으로, HCS(Consensus Service)와 Fauxken 같은 게임용 토큰 메커니즘을 제공한다.","content":"**추출된 패턴:** 일반 블록체인의 가스비·지연 문제를 해결하는 대안 인프라 — 게임 친화적 처리량(10K+ TPS) 강조.\n\n**세부 내용:**\n- HCS: 메시지 합의 → 게임 이벤트 로그 신뢰성.\n- HTS: 토큰 표준 (NFT/FT 모두 지원).\n- 거버넌스: 39개 기업이 운영하는 council 모델.\n- 게임 사용 사례: Sandbox, gaming partner integrations.\n- 한계: 탈중앙화 정도, 생태계 규모는 EVM 대비 작음."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/매출 동력/하이퍼인플레이션 in Closed-Loop Systems (Hyperinflation in Closed-Loop Systems).md","summary":"폐쇄 루프 게임 경제에서 하이퍼인플레이션은 발생량(Source)이 회수량(Sink)을 장기간 초과할 때 통화 가치가 급락하는 현상이다.","content":"**추출된 패턴:** 매주 새 콘텐츠로 Source 증가 → Sink는 늦게 따라옴 → 시간이 지나면 통화 발행량이 누적되어 가치 붕괴.\n\n**세부 내용:**\n- 사례: Diablo III 경매장 폐쇄 사건.\n- 회수 메커니즘: 강화·세금·소각·시즌 리셋.\n- 결제력 잠식: 인플레이션이 결제 의향을 깎음.\n- 모니터링: 통화별 발행량 vs 소비량 비율.\n- 시뮬레이션 도구로 사전 검증.","oh_skip":"path-mismatch"},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Hyperinflation-in-Closed-Loop-Systems.md","summary":"닫힌 루프 게임 경제에서 하이퍼인플레이션은 통화 발행이 소모를 초과해 통화 가치가 급락하는 현상으로, 매출 붕괴와 유저 이탈로 직결된다.","content":"**추출된 패턴:** Source-Sink 비율이 1.2 이상 장기 지속되면 위험 신호 — 신규 Sink 도입 또는 기존 Sink 강화 필요.\n\n**세부 내용:**\n- 정의: 통화 가치(아이템 환산)가 시간당 N% 이상 하락.\n- 원인: 새 콘텐츠로 Source 증가, Sink 미흡.\n- 해결: 시즌 리셋, 강한 Sink 도입, 통화 페그.\n- 사례: Diablo III 골드 인플레이션, MMORPG 후반 통화 붕괴.\n- 측정: 가격 인덱스(주요 아이템 가격 변화)."}
|
||||
]
|
||||
@@ -0,0 +1,49 @@
|
||||
[
|
||||
{"path":"10_Wiki/Topics/Game_Design/이중 계층 과금 모델 (Two-layer Monetization).md","summary":"이중 계층 과금은 소액 정기(패스/구독)와 대규모 단발(가챠/번들)을 결합해 결제 의향이 다른 두 유저층을 동시에 흡수하는 BM 패턴이다.","content":"**추출된 패턴:** 미들 결제층(소액 정기)과 고래(대형 단발)를 별도 계층으로 분리해 매출 변동성 ↓.\n\n**세부 내용:**\n- 1층: $4.99~$9.99 시즌 패스 (안정적 정기 매출).\n- 2층: $50~$200 가챠/번들 (피크 매출).\n- 두 층의 상호 보완: 패스 구입 후 가챠 결제율 ↑.\n- 디자인: 패스 보상이 가챠를 가속하는 자원.\n- 사례: 모바일 RPG 표준 BM."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/이중 VIP 시스템 (Dual-layer VIP).md","summary":"이중 VIP는 누적 결제 등급(영구) + 시즌 결제 등급(주기)으로 분리한 VIP 시스템으로, 장기 충성도와 단기 결제 동기를 동시에 자극한다.","content":"**추출된 패턴:** \"내 누적은 보존, 매 시즌 새로 시작\" — 둘을 분리해야 신규 유저도 경쟁할 수 있고 베테랑도 자긍심 유지.\n\n**세부 내용:**\n- 영구 VIP: 누적 결제 → 영구 혜택.\n- 시즌 VIP: 시즌 결제 → 시즌 한정 혜택.\n- 시즌마다 reset되어 신규 결제 동기 발생.\n- 영구 VIP는 \"명예\" 성격, 시즌 VIP는 \"실용\".\n- 사례: 다수 모바일 RPG, MMORPG."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/맞춤형 IAP 번들(Customizable IAP bundles).md","summary":"맞춤형 IAP 번들은 유저가 원하는 자원을 선택해 구성할 수 있는 번들 시스템으로, 가치 인식과 결제율을 동시에 높인다.","content":"**추출된 패턴:** \"내가 고른 보상\"이라는 인식이 가치 인식을 부풀림 — 동일 비용에 더 큰 만족감.\n\n**세부 내용:**\n- 유저 진행 단계에 맞는 자원 추천.\n- 가격대별 옵션: $4.99 / $19.99 / $49.99.\n- 추가 보너스 자원으로 \"이득 인식\" 강조.\n- 데이터 기반 개인화 가능.\n- 동적 오퍼와 결합."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/시간 제한 활성화 (Time-limited Activation).md","summary":"시간 제한 활성화는 한정된 시간 안에 자원·콘텐츠를 활성화해야 하는 메커니즘으로, FOMO를 유발해 결제·접속 동기를 만든다.","content":"**추출된 패턴:** 손실 회피 심리 + 사회적 비교 — \"지금 안 하면 영영 못 함\"이 강한 결제 트리거.\n\n**세부 내용:**\n- 24h / 72h / 7일 한정 패키지.\n- 시즌 / 이벤트 콘텐츠.\n- 픽업 캐릭터 (이번 가챠만).\n- 협업 이벤트 (한정 IP 컬래보).\n- 남용 시 피로 → 균형 필요."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/시간 제한 메커니즘 (Time-gating).md","summary":"타임 게이팅은 진행을 시간 단위로 제한해 결제 또는 인내를 강제하는 메커니즘으로, 일일 자원 캡과 쿨타임이 대표적이다.","content":"**추출된 패턴:** \"기다리거나, 결제하거나\" — F2P의 핵심 결제 트리거. 강도 조절이 retention과 매출의 균형 결정.\n\n**세부 내용:**\n- 에너지/스태미나 시스템.\n- 건설/연구 쿨타임.\n- 일일 입장 제한 (던전, 아레나).\n- 결제로 즉시 완료 옵션.\n- 너무 강하면 진입 장벽 ↑, 너무 약하면 매출 ↓."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/소유 효과.md","summary":"소유 효과(Endowment Effect)는 유저가 일단 소유한 자원/캐릭터의 가치를 객관적 가치보다 높게 평가하는 인지 편향이다.","content":"**추출된 패턴:** 무료로 일시 소유한 후 회수 시 손실로 인식 → 결제로 \"되찾기\" 유도. 도덕적 회색지대.\n\n**세부 내용:**\n- 무료 체험 → 종료 후 결제 유도.\n- 컬렉션 99% 완성 시 마지막 1% 결제 유도.\n- 결제 후 \"내 것\" 인식 → 후속 강화 결제.\n- Cialdini의 영향력 원칙과 연결.\n- 윤리: \"다크 패턴\" 비판 가능."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/사용자 참여도(Player Engagement).md","summary":"사용자 참여도는 유저가 게임에 투입하는 시간·인지·감정 자원의 총합으로, retention과 LTV의 선행 지표 역할을 한다.","content":"**추출된 패턴:** 참여도 = 동기 × 능력 × 트리거 (Fogg 행동 모델). 셋 중 하나만 약해도 참여 ↓.\n\n**세부 내용:**\n- 측정: DAU, 세션당 시간, 세션 빈도.\n- 동기: 진행감·소셜·자율성·관계성(SDT).\n- 능력: UI/UX·튜토리얼·DDA.\n- 트리거: 푸시·이벤트·소셜 알림.\n- 깊은 참여 → 결제 의향 ↑."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/프리미엄 통화(Premium Currency).md","summary":"프리미엄 통화는 결제로 획득하는 하드 통화로, 모든 BM의 중심 화폐 역할을 하며 인플레이션 통제·결제 마찰 감소를 동시에 노린다.","content":"**추출된 패턴:** 환율 모호함이 핵심 — \"100젬 = $1.99\" 직접 안 보이게 해서 결제 마찰 감소.\n\n**세부 내용:**\n- 직접 표기 회피: 1$=N젬으로 환산되는 구조.\n- 패키지로 보너스 (10$ → 1100젬).\n- 첫 구매 부스트 / 누적 마일스톤.\n- 가챠·강화·코스튬 결제에 사용.\n- 수령 후 환불·이체 불가 정책."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/플레이어 기반 경제.md","summary":"플레이어 기반 경제는 유저 간 직접 거래·생산·소비가 매출을 구동하는 모델로, EVE Online·MMORPG에서 두드러진다.","content":"**추출된 패턴:** 거래 시스템은 양면 Sink — 통화 흐름 통제 어렵지만 유저 간 가치 창출이 매출 외 동력 추가.\n\n**세부 내용:**\n- 거래 가능 자원: 자원·장비·소비 아이템.\n- 경매장 / 직거래 / 길드 창고.\n- 시장 봇·작업장(중국 골드파머) 위협.\n- 통화 발행 통제 정밀 필요.\n- 사례: EVE Online, RuneScape, WoW."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/게임 수익화 전략.md","summary":"게임 수익화 전략은 장르·플랫폼·유저 페르소나에 맞는 BM 조합을 설계하는 활동으로, 단일 BM이 아닌 4~6개 동시 운영이 표준이다.","content":"**추출된 패턴:** \"여러 결제 의향 구간을 모두 흡수\" — 무과금(광고), 소액(패스), 미들(번들), 고래(가챠 천장).\n\n**세부 내용:**\n- 광고: 보상형/인터스티셜.\n- 패스: 시즌·이벤트.\n- 번들: 단발 한정 결제.\n- 가챠: 확률 기반 수집.\n- 구독: 정기 매출 안정화."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Game_Monetization_Strategy.md","summary":"수익화 전략은 게임 장르·유저층·LTV 목표에 따라 광고·IAP·패스·구독을 어떻게 결합할지 결정하는 메타 디자인이다.","content":"**추출된 패턴:** 장르별 \"표준 BM\"이 있고, 이를 따르면 안전하지만 차별화 어려움 — 새 BM 실험은 장르 정착 후 가능.\n\n**세부 내용:**\n- 하이퍼캐주얼: 광고 100%.\n- 캐주얼/하이브리드: 광고+IAP.\n- 미드코어 RPG: 가챠+패스.\n- MMO/Strategy: 패키지+VIP.\n- 콘솔 라이브: 코스튬+패스."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/수익화 전략.md","summary":"수익화 전략은 게임의 BM 포트폴리오를 정의하는 활동으로, 장르·유저·LiveOps와 정합되어야 효과적이다.","content":"**추출된 패턴:** 단일 BM 의존은 위험 — 광고 정책 변경, 플랫폼 정책 변경, 유저 트렌드 변화에 취약.\n\n**세부 내용:**\n- 매출 다각화: 광고/IAP/패스/구독.\n- 시즌 / 이벤트 캘린더와 정합.\n- 글로벌별 우세 BM 조정.\n- KPI: ARPDAU, ARPPU, 결제율.\n- A/B 테스트 인프라 필수."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/포켓몬 마스터즈 EX(Pokemon Masters EX).md","summary":"포켓몬 마스터즈 EX는 IP 활용 + 가챠 + 시즌 콘텐츠를 결합한 모바일 RPG로, IP 가치와 라이브옵스 운영을 잘 결합한 사례다.","content":"**추출된 패턴:** 강력한 IP는 마케팅 비용을 줄여주지만, BM이 약하면 IP 손상 → 디자인이 IP를 받쳐야 함.\n\n**세부 내용:**\n- 트레이너+포켓몬 페어 컬렉션.\n- 정기 신규 페어 가챠.\n- 시즌 패스 + 이벤트 던전.\n- 글로벌 IP 라이선스 비용 분담.\n- DeNA + Pokemon Company 공동 운영."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Final Fantasy XV- A New Empire.md","summary":"FFXV: A New Empire는 FFXV IP 라이선스 모바일 SLG로, 4X 전략 코어에 IP 자산을 입혀 강력 매출을 만든 모델이다.","content":"**추출된 패턴:** \"건설+동맹+PvP\" 4X SLG 표준 위에 IP 스킨 — 핵심 디자인은 다른 SLG와 거의 동일하지만 IP가 차별화 신호.\n\n**세부 내용:**\n- Epic Action(현 Epic Games)·Machine Zone 류 SLG 패턴.\n- 영웅: FFXV 캐릭터 라이선스.\n- BM: VIP, 패키지, 동맹 자원 결제.\n- 마케팅 ROAS가 SLG의 핵심.\n- 일부 P2W 비판."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Mobile Strike.md","summary":"Mobile Strike는 Machine Zone의 군사 SLG로, GoW(Game of War) 엔진을 그대로 활용해 빠르게 출시한 사례다.","content":"**추출된 패턴:** 동일 코어 + 다른 IP/스킨 = 신규 게임. 모바일 SLG의 \"리스킨 모델\"의 효시.\n\n**세부 내용:**\n- 군사·현대전 테마.\n- 4X 표준: 건설·연구·동맹·PvP.\n- 슈워제네거 광고로 brand awareness.\n- BM: VIP, 패키지, 영웅 결제.\n- 후속작들: World War Rising, Final Fantasy XV ANE."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/World War Rising.md","summary":"World War Rising은 모바일 군사 4X SLG로, Machine Zone/Epic War류 엔진의 후속작 중 하나다.","content":"**추출된 패턴:** 같은 코어 엔진 + 새 테마(2차 대전) = 빠른 출시. SLG 장르의 산업화된 제작 방식.\n\n**세부 내용:**\n- 2차 세계대전 테마.\n- 4X 코어: 건설·연구·전투·동맹.\n- 동맹 전쟁 / 시즌 / 이벤트.\n- BM: VIP / 패키지 / 자원.\n- iOS/Android 글로벌 출시."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Capybara GO!.md","summary":"Capybara GO!는 캐주얼 RPG·로그라이크 모바일 게임으로, 단순 코어 + 깊은 메타의 하이브리드 캐주얼 트렌드를 잘 보여준다.","content":"**추출된 패턴:** \"수동 조작 거의 없음\" + \"의미 있는 빌드 선택\" — 캐주얼 시간 투자로 미드코어 만족감.\n\n**세부 내용:**\n- 자동 전투 + 능력 선택.\n- 영웅·장비 컬렉션.\n- 일일·주간·시즌 이벤트.\n- BM: 광고+IAP+패스.\n- 광고 크리에이티브가 게임 자체와 일치."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Puzzles & Survival.md","summary":"Puzzles & Survival은 매치-3 + 4X SLG의 하이브리드로, 두 장르의 결제 의향 풀을 동시에 흡수해 매출 효율을 극대화했다.","content":"**추출된 패턴:** 매치-3로 진입 장벽 ↓ + 4X 메타로 LTV ↑ — 장르 결합으로 광고와 결제 모두 강화.\n\n**세부 내용:**\n- 매치-3 코어: 좀비 처치.\n- 4X 메타: 베이스, 동맹, PvP.\n- BM: VIP + 패키지 + 가챠.\n- 광고: 매치-3 게임플레이 강조.\n- 37Games 흥행작."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Magic Sort!.md","summary":"Magic Sort!는 정렬·논리 퍼즐 기반 캐주얼 모바일 게임으로, 단순 코어와 광고 BM의 전형적 하이퍼캐주얼 구조를 따른다.","content":"**추출된 패턴:** 직관적 코어 + 빠른 진행 + 광고 노출 = 하이퍼캐주얼 매출.\n\n**세부 내용:**\n- 색상/크기/패턴 정렬 퍼즐.\n- 5초 만에 이해 가능한 코어.\n- BM: 인터스티셜 광고 위주.\n- 보상형 광고로 진행 가속.\n- 평균 세션 짧음 (3~5분)."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Triple Match 3D.md","summary":"Triple Match 3D는 3D 매치-3 변형 게임으로, 같은 모양 3개를 모아 제거하는 단순 코어로 글로벌 히트했다.","content":"**추출된 패턴:** \"공간 인지 + 패턴 매칭\" 결합 — 2D 매치-3보다 공간감 자극으로 신선함.\n\n**세부 내용:**\n- 3D 공간에 흩어진 객체 매칭.\n- 단계별 난이도 / 시간 제한.\n- BM: 광고+IAP (힌트, 시간 추가).\n- Tripledot Studios·다수 카피캣.\n- 매치 메커니즘의 새 변형 사례."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Tripledot Studios.md","summary":"Tripledot Studios는 영국 캐주얼 모바일 퍼블리셔로, Triple Match 3D·Solitaire 등 단순·고품질 캐주얼 게임 포트폴리오로 성장했다.","content":"**추출된 패턴:** \"클래식 게임의 현대적 재해석\" + 데이터 기반 LiveOps — 신선한 IP 없이도 차별화 가능.\n\n**세부 내용:**\n- Solitaire, Sudoku, Triple Match 3D.\n- 미국·유럽 중심 마케팅.\n- 광고 BM 위주, IAP 보조.\n- 인수합병 / 매출 성장 빠름.\n- 캐주얼 게임 시장의 강자."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Magic-Circle.md","summary":"매직 서클은 게임이라는 인공 공간으로, 일상의 규칙과 다른 게임 내 규칙·의미가 작동하는 심리적·문화적 영역이다.","content":"**추출된 패턴:** 매직 서클 안에선 일상에서 비합리적인 행동도 \"의미 있음\" — 게임 디자인은 이 경계를 만들고 유지하는 일.\n\n**세부 내용:**\n- Huizinga의 'Homo Ludens' 원전.\n- 게임 규칙 = 매직 서클의 경계.\n- 외부 가치(돈)가 들어오면 매직 서클 약화 (P2W).\n- 디지털 게임은 물리적 경계 대신 심리적 경계.\n- 메타게임·ARG가 매직 서클 확장."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Procedural Rhetoric (In Gaming).md","summary":"절차적 수사학(Procedural Rhetoric)은 게임 시스템·메커니즘 자체가 메시지를 전달하는 표현 방식이다 (Ian Bogost).","content":"**추출된 패턴:** \"이야기로 말하는 것\" 외에 \"규칙으로 말하는 것\" — 게임은 시스템 행동으로 가치를 전달.\n\n**세부 내용:**\n- Bogost의 Persuasive Games 개념.\n- 사례: Papers Please의 관료주의, Tropico의 권력.\n- 시스템 행동 = 작가 의도의 매개체.\n- 시리어스 게임 디자인의 핵심.\n- AAA에서도 활용 (BioShock의 자유의지)."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Gamification-Theory.md","summary":"게이미피케이션은 게임 외 영역에 게임 메커니즘(목표·진행·보상·소셜)을 적용해 동기 부여를 강화하는 디자인 접근이다.","content":"**추출된 패턴:** 외재적 보상(포인트·뱃지)만으론 단기 효과 — 자율성·숙련감·관계성(SDT)을 자극해야 지속.\n\n**세부 내용:**\n- 사례: Duolingo, Strava, Codecademy.\n- 메커니즘: 진행도, 레벨업, 도전과제, 리더보드.\n- 비판: 외재적 동기 의존 시 본질적 동기 약화.\n- SDT 결합: 자율-숙련-관계 자극 설계.\n- 워크플레이스 게이미피케이션 위험."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Immersive-Sim-Genre.md","summary":"이머시브 심은 다양한 시스템 상호작용과 플레이어 자율성을 강조하는 장르로, Looking Glass·Arkane·Ion Storm 계보가 대표적이다.","content":"**추출된 패턴:** \"디자이너가 정해놓은 한 가지 방법\"이 아닌 \"여러 시스템의 emergent 상호작용\"으로 문제 해결.\n\n**세부 내용:**\n- System Shock, Thief, Deus Ex 계보.\n- Dishonored, Prey 등 현대 작품.\n- 핵심: 여러 도구·환경·NPC의 상호작용.\n- 비판: 모든 시스템이 같은 깊이를 갖긴 어려움.\n- 후예: BotW 같은 오픈월드의 emergent 디자인."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Immersive-Sims-Deus-Ex-Dishonored.md","summary":"Deus Ex와 Dishonored는 이머시브 심 장르의 대표작으로, 플레이 스타일별 다양한 해법(stealth/lethal/hybrid)을 의도적으로 설계했다.","content":"**추출된 패턴:** \"전 진행 가능 경로 = 디자인 매트릭스\" — 각 레벨이 여러 해법을 동시에 지원.\n\n**세부 내용:**\n- Deus Ex(2000): 진로/전투/해킹/사회공학.\n- Dishonored(2012): 스텔스/처형/혼합 + 마법.\n- Chaos 시스템: 플레이 스타일 → 세계 반응.\n- 레벨 디자인: 다층 / 다중 진입점.\n- 비판: 옵션 과다 → 일부 미사용 콘텐츠."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Immersive-Sims-Deus-Ex-Thief.md","summary":"Deus Ex와 Thief는 1세대 이머시브 심으로, 시스템 상호작용 + 플레이어 자율성 + 환경 스토리텔링의 토대를 만들었다.","content":"**추출된 패턴:** \"감각/AI/물리\"의 시스템 상호작용 — 그림자·소리·냄새·도구가 일관된 규칙으로 상호작용.\n\n**세부 내용:**\n- Thief(1998): 빛/소리 시스템 = 스텔스 코어.\n- Deus Ex(2000): RPG + FPS + 잠입 결합.\n- AI 인지: 시각·청각·기억.\n- 도구 다양성: 화살·약물·해킹.\n- 디자인 철학: 플레이어가 \"속이는 즐거움\"."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Papers Please (Bureaucratic Simulation).md","summary":"Papers, Please는 디스토피아 국경 검문관 시뮬레이션으로, 게임 시스템 자체로 도덕적 갈등과 관료주의의 무게를 전달하는 절차적 수사학의 대표작이다.","content":"**추출된 패턴:** 단순 메커니즘(서류 비교)에 도덕적 결정의 무게를 얹어 메시지 전달.\n\n**세부 내용:**\n- Lucas Pope 1인 개발(2013).\n- 코어: 입국 서류 검증.\n- 가족 부양 vs 직무 윤리 갈등.\n- 절차적 수사학의 정수.\n- 시리어스 게임 디자인 표준 사례."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Papers-Please.md","summary":"Papers, Please는 관료주의 디스토피아를 메커니즘으로 표현한 인디 명작으로, 게임이 정치·도덕 메시지를 어떻게 전달할 수 있는지 보여준다.","content":"**추출된 패턴:** 게임플레이 자체가 메시지 — 플레이어가 \"왜 이걸 해야 하지?\"라는 질문을 자연스럽게 마주함.\n\n**세부 내용:**\n- 시스템: 서류 검증 + 시간 압박.\n- 도덕: 가족 vs 권력, 동정 vs 규칙.\n- 분기: 다중 결말 + 플레이 스타일.\n- 단순 픽셀 그래픽 + 강한 메시지.\n- 인디 게임 절차적 수사학의 정수."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/BioShock-Critique.md","summary":"BioShock는 \"Would you kindly?\" 한 마디로 플레이어 자유의지의 환상을 무너뜨린 메타 비평적 게임 디자인의 대표작이다.","content":"**추출된 패턴:** 게임 메커니즘(\"Would you kindly?\")이 곧 게임 메시지(자유의지 부재) — 메타 게임 비평.\n\n**세부 내용:**\n- 2007년 발매, Ken Levine.\n- Rapture: 객관주의 디스토피아.\n- 자유의지 폭로: 모든 명령은 강요였다.\n- 도덕 시스템: Little Sister 구원 vs 채취.\n- 비판: 후반부 평이한 슈팅 부분."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Live and Deepspace.md","summary":"Love and Deepspace는 3D 캐릭터 애정 시뮬레이션 모바일 RPG로, 비주얼 노벨 + 가챠 + 라이브옵스를 결합해 여성 유저층을 적극 공략한 사례다.","content":"**추출된 패턴:** 정서적 몰입 + 컬렉션 + 한정성 = 여성 페르소나 매출 모델.\n\n**세부 내용:**\n- 4명의 남성 캐릭터와 데이트 시뮬레이션.\n- 보이스/모션 캡처 풀 3D.\n- BM: 가챠(코스튬, 카드) + 패스.\n- Otome 게임 시장의 모바일화.\n- Infold Games(중국) 글로벌 흥행.","oh_skip":"path-mismatch:Love-vs-Live"},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Love and Deepspace.md","summary":"Love and Deepspace는 여성 타겟 3D 데이팅 시뮬레이션 모바일 게임으로, 가챠+라이브옵스+세련된 비주얼로 글로벌 흥행했다.","content":"**추출된 패턴:** 정서·낭만의 가챠화 — 카드/코스튬/이벤트가 결제 동기.\n\n**세부 내용:**\n- 3D 보이스·모션 캡처.\n- 4명의 메인 남자 캐릭터.\n- BM: 가챠(카드/코스튬) + 패스.\n- 글로벌 출시: 한·일·미·대만.\n- Infold Games 흥행작."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/EVE 온라인.md","summary":"EVE Online은 단일 우주 샤드의 MMORPG로, 플레이어 주도 경제·정치·전쟁의 emergent 시스템 디자인의 정수다.","content":"**추출된 패턴:** \"디자이너는 시스템만 제공, 콘텐츠는 플레이어가 만든다\" — 20년+ 운영의 비결.\n\n**세부 내용:**\n- CCP Games(아이슬란드) 운영.\n- 단일 서버 30만+ 동시 접속 가능.\n- 플레이어 기반 경제: 모든 자원이 플레이어 생산.\n- 길드(Corp) → 동맹 → 연합 정치.\n- 유명 사건: B-R5RB 전투(\\$300K 손실)."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/EVE 온라인(EVE Online).md","summary":"EVE Online의 경제는 모든 자원이 플레이어가 채굴·생산하고 거래로 유통되는 자유시장 모델로, 경제학자 채용으로도 유명하다.","content":"**추출된 패턴:** 인플레이션·거래 정책을 게임 디자이너가 아닌 경제팀이 운영 — 진정한 시뮬레이션.\n\n**세부 내용:**\n- ISK: 게임 내 화폐.\n- 모든 함선·무기·모듈이 플레이어 생산.\n- 거래 허브(Jita)에서 가격 형성.\n- PLEX: 시간을 ISK로 환산하는 메커니즘.\n- 연간 경제 보고서 발간."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/이브 온라인(EVE Online).md","summary":"이브 온라인은 플레이어 기반 자유시장 경제를 구현한 SF MMORPG로, 매월 경제 보고서를 발간하는 유일한 상업 게임이다.","content":"**추출된 패턴:** 게임 경제도 현실 경제처럼 인플레이션·금리·정치 리스크가 작용 → 데이터 기반 운영.\n\n**세부 내용:**\n- 서비스: 2003~ (20년+).\n- 단일 우주(Single Shard)로 모든 유저가 같은 세계.\n- 채굴/제조/무역/전쟁 직군 분화.\n- CCP 경제팀 데이터 분석.\n- 게임 학술 연구 대상."},
|
||||
{"path":"10_Wiki/Topics/General Knowledge/알비온 온라인(Albion Online).md","summary":"Albion Online은 \"플레이어가 만드는 모든 것\"을 표방하는 샌드박스 MMORPG로, EVE의 경제 철학을 판타지 세계로 옮긴 사례다.","content":"**추출된 패턴:** PvP 약탈(Full Loot) + 플레이어 생산 = 거래·전쟁이 의미 있는 위험 보상 구조.\n\n**세부 내용:**\n- Sandbox Interactive 운영.\n- 모든 장비 플레이어 제작 (PvP 약탈 가능).\n- 영토 점령 + 길드 정치.\n- 모바일·PC 크로스 플랫폼.\n- BM: 프리미엄 구독 + 패키지."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/알비온 온라인(Albion Online)의 경제 시스템.md","summary":"Albion Online의 경제는 모든 장비가 플레이어 제작·거래되며 PvP 사망 시 약탈된다는 점에서 EVE에 가장 가까운 자유시장 모델이다.","content":"**추출된 패턴:** Full Loot PvP가 강력한 통화 sink — 사망 시 장비 파괴/약탈로 통화 흐름 통제.\n\n**세부 내용:**\n- 통화: Silver(소프트), Gold(프리미엄).\n- 모든 자원·장비 플레이어 생산.\n- 시장(Marketplace) 도시별로 가격 차이.\n- 영토(Territory) → 자원 채굴권.\n- PvP 등급별로 위험·보상 차등."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/4X 시스템 (4X System).md","summary":"4X 시스템은 eXplore·eXpand·eXploit·eXterminate의 4축 진행을 가진 전략 게임 장르로, Civilization·Stellaris·Galactic Civilizations 등이 대표적이다.","content":"**추출된 패턴:** 4축의 시간 분포 = 게임 페이스 결정. 초반 탐사·확장, 중반 활용, 후반 전쟁이 표준 곡선.\n\n**세부 내용:**\n- Explore: 미지 지역 탐사.\n- Expand: 도시·영토 확장.\n- Exploit: 자원·기술 활용.\n- Exterminate: 전쟁/외교.\n- 모바일 SLG가 4X 코어를 단순화·라이브화."},
|
||||
{"path":"10_Wiki/Topics/Economy/4X 전략 게임 수익화 모델.md","summary":"4X 전략 모바일 게임의 BM은 VIP+패키지+자원 결제+동맹 자원의 4축으로, 평균 ARPPU가 모바일 장르 중 가장 높다.","content":"**추출된 패턴:** 시간 압박(건설/연구 쿨타임) + 동맹 압박(공동 목표) = 강한 결제 동기.\n\n**세부 내용:**\n- VIP 등급별 차등 혜택.\n- 자원 패키지 ($4.99~$99.99).\n- 동맹 자원 결제 (집단 행동).\n- 한정 영웅 가챠.\n- 평균 LTV 모바일 최상위."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/4X 시스템 (4X System).md","summary":"4X는 탐사-확장-활용-말살의 4축으로 진행되는 전략 장르로, Civilization 시리즈가 PC 표준, 모바일 SLG가 모바일 표준을 만들었다.","content":"**추출된 패턴:** 4축의 시간 비중이 게임 페이스를 결정 — 모바일 SLG는 \"확장+활용+말살\"에 비중, 탐사는 빈약.\n\n**세부 내용:**\n- 턴제(Civ): 깊은 전략, 긴 세션.\n- 실시간(SLG): 짧은 세션, 라이브옵스.\n- 자원 관리 + 외교 + 군사.\n- 시간 압박이 결제 트리거.\n- 메타게임 다양성 → 장기 retention.","oh_skip":"duplicate-path"},
|
||||
{"path":"10_Wiki/Topics/4X 전략.md","summary":"4X 전략은 Explore·Expand·Exploit·Exterminate의 약자로, 거대 전략 게임의 표준 디자인 프레임이다.","content":"**추출된 패턴:** 4축 = 동기·메커니즘·성장 곡선의 4가지 축. 한 축이 약하면 게임이 한쪽으로 기움.\n\n**세부 내용:**\n- 탐사: 정찰·발견.\n- 확장: 도시·자원 점유.\n- 활용: 기술·경제·생산.\n- 말살: 전쟁·외교.\n- 모바일 SLG는 \"빠른 확장 + 동맹\" 강조."},
|
||||
{"path":"10_Wiki/Topics/Economy/계단식 수익화 모델 (Staircase Monetization).md","summary":"계단식 수익화는 결제액에 따라 점진적으로 더 좋은 보상을 주는 모델로, 다음 단계로 올라가도록 유도하는 사다리 구조다.","content":"**추출된 패턴:** \"이 정도만 더 결제하면 다음 등급\" — 매몰 비용 + 손실 회피로 점진적 결제 유도.\n\n**세부 내용:**\n- VIP 0~10단계 등 다층 구조.\n- 누적 결제 → 영구 등급.\n- 시즌 누적 → 시즌 등급.\n- 등급별 차등 혜택 (할인, 한정 콘텐츠).\n- 비판: 다크 패턴 가능성."},
|
||||
{"path":"10_Wiki/Topics/Game Design/Monetization/Staircase Monetization.md","summary":"Staircase Monetization은 결제 단계별로 보상이 점차 커지는 사다리 구조로, 결제 의향을 단계적으로 끌어올리는 BM이다.","content":"**추출된 패턴:** 첫 단계 작은 마찰 → 다음 단계 점차 큰 결제. 손실 회피·매몰 비용·완성 욕구 동시 자극.\n\n**세부 내용:**\n- $0.99 → $4.99 → $19.99 → $99.99 사다리.\n- 각 단계별 누적 보너스.\n- 1회만 가능한 \"신규 유저 사다리\".\n- 시즌 패스 누적 단계.\n- 사례: 모바일 RPG VIP 시스템."},
|
||||
{"path":"10_Wiki/Topics/Economy/게임 수익화 모델.md","summary":"게임 수익화 모델은 광고·IAP·구독·소매·라이선스의 5대 축으로 분류되며, 장르·플랫폼별로 우세 모델이 다르다.","content":"**추출된 패턴:** 모바일 F2P → 광고+IAP, PC/콘솔 → 소매+DLC+패스, MMO → 구독+상점.\n\n**세부 내용:**\n- IAP: 모바일 매출 75%+.\n- 광고: 하이퍼·하이브리드 캐주얼.\n- 구독: MMO·서비스형 게임.\n- 소매: PC·콘솔 단발 매출.\n- 라이선스: IP 공급(드라마, 굿즈)."},
|
||||
{"path":"10_Wiki/Topics/Game Design/Monetization/VIP System.md","summary":"VIP 시스템은 누적 결제에 따른 등급제 혜택으로, 고래 유저의 충성도를 강화하고 결제 누적을 가시화하는 핵심 BM 도구다.","content":"**추출된 패턴:** \"내 누적 결제가 영구 인정\" — 다음 등급으로 가기 위한 매몰 비용 인식.\n\n**세부 내용:**\n- 0~15단계 표준 (모바일 RPG).\n- 등급별: 일일 보상, 할인, 한정 콘텐츠.\n- 영구 누적 (계정 단위).\n- 시즌 VIP와 분리 가능.\n- 고래 유저의 LTV 최대 동력."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/고래 유저 (Whale Players).md","summary":"고래(Whale)는 모바일 게임 매출의 대부분을 만드는 소수 고결제 유저로, 0.1~5%의 유저가 50%+ 매출을 차지하는 경우가 흔하다.","content":"**추출된 패턴:** 80/20 규칙 강화판 — 1%/50% 분포가 모바일 F2P의 표준 매출 분포.\n\n**세부 내용:**\n- 분류: Minnow / Dolphin / Whale / Mega Whale.\n- Mega Whale: 월 \\$1000+ 결제.\n- 결제 동기: 경쟁·과시·수집.\n- VIP 시스템 + 한정 패키지가 주요 도구.\n- CS·VIP 매니저 전담 운영."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/고과금 유저 (Whales).md","summary":"고과금 유저(Whales)는 게임 매출의 대부분을 만드는 소수 결제 핵심 유저로, 별도 LiveOps와 CS가 필요하다.","content":"**추출된 패턴:** 고래는 \"가격\"보다 \"한정성·우월감\"에 반응 — 일반 유저와 다른 결제 트리거.\n\n**세부 내용:**\n- 한정 코스튬·캐릭터·번호.\n- VIP 등급별 차등.\n- 전담 매니저(CS) 배정.\n- 시즌 고래 한정 이벤트.\n- 윤리: 도박 의존증 가능성 모니터링."}
|
||||
]
|
||||
@@ -0,0 +1,46 @@
|
||||
[
|
||||
{"path":"10_Wiki/Topics/Programming & Language/Zod 런타임 유효성 검사 통합.md","summary":"Zod는 TypeScript 친화적 스키마 정의 라이브러리로, 런타임 유효성 검사와 컴파일 타임 타입 추론을 한 스키마에서 동시에 제공해 외부 입력 신뢰성과 DX를 동시에 끌어올린다.","content":"**추출된 패턴:** \"한 번 스키마 정의 → 타입 + 검증 + 변환\" — 타입과 런타임 검증의 단일 출처.\n\n**세부 내용:**\n- 정의: `z.object({ ... })` → 타입 `z.infer<typeof schema>`.\n- 검증: `schema.parse(input)` (throw) / `safeParse` (Result).\n- 변환: `.transform()` 으로 정규화·변형.\n- 통합: tRPC, React Hook Form, Server Actions, Next.js.\n- 대안: Yup, Joi, Valibot, ArkType."},
|
||||
{"path":"10_Wiki/Topics/런타임 유효성 검사 (Runtime Validation).md","summary":"런타임 유효성 검사는 외부 데이터(API 응답, 폼 입력, 환경변수)가 기대 형태인지 실행 시점에 확인하는 보호 계층으로, TypeScript 타입만으로는 부족한 경계 검증을 보완한다.","content":"**추출된 패턴:** 타입은 빌드 타임 약속, 검증은 런타임 약속 — 둘이 같은 출처(스키마)에서 나와야 drift 방지.\n\n**세부 내용:**\n- 검증 위치: API 경계, DB 입출력, 폼, env.\n- 라이브러리: Zod, Yup, Valibot, io-ts.\n- 패턴: parse 후 정상값만 도메인으로 진입.\n- 에러 처리: 사용자 메시지 + 로깅 분리.\n- 비용: 런타임 오버헤드 vs 디버깅 비용 절감."},
|
||||
{"path":"10_Wiki/Topics/Frontend/런타임 상태 검증(Runtime Validation).md","summary":"프런트엔드 런타임 상태 검증은 API 응답·로컬 스토리지·URL 파라미터 등 신뢰할 수 없는 입력을 도메인 모델로 변환하기 전에 검사하는 패턴이다.","content":"**추출된 패턴:** 백엔드 응답을 \"신뢰 가능\"으로 가정하면 안 됨 — 버전 변경·네트워크 손상·악성 입력으로 깨질 수 있음.\n\n**세부 내용:**\n- API 응답: Zod parse → 타입 안전 + 에러 격리.\n- localStorage: JSON.parse 후 검증.\n- URL 쿼리: 스키마 기반 파싱.\n- 경계 위치 일관성 (한 곳에서만 변환).\n- 검증 실패 = 도메인 에러로 정상 흐름 통합."},
|
||||
{"path":"10_Wiki/Topics/Computer_Science_and_Theory/Information Retrieval (IR).md","summary":"정보 검색(IR)은 대용량 비정형 데이터에서 사용자 질의에 부합하는 문서를 찾는 학문 분야로, BM25·TF-IDF·dense retrieval로 발전해 왔다.","content":"**추출된 패턴:** 어휘 매칭(sparse) ↔ 의미 매칭(dense) ↔ 하이브리드 — 도메인·질의 길이·재현율 요구에 따라 선택.\n\n**세부 내용:**\n- 전통적: TF-IDF, BM25 (Okapi).\n- 신경: SBERT, ColBERT, E5, BGE.\n- 하이브리드: BM25 + dense + reranker.\n- 평가: nDCG, MRR, MAP, Recall@k.\n- 응용: 검색엔진, RAG, 추천."},
|
||||
{"path":"10_Wiki/Topics/AI_and_ML/Information-Retrieval-IR.md","summary":"IR은 대규모 코퍼스에서 의미적·어휘적으로 관련 있는 문서를 효율적으로 찾는 시스템으로, RAG의 핵심 컴포넌트다.","content":"**추출된 패턴:** \"recall 우선 retrieval + precision 우선 reranker\"의 2단 구조가 현대 RAG의 표준.\n\n**세부 내용:**\n- 1단(retrieve): BM25 + dense (top-k 100~500).\n- 2단(rerank): cross-encoder (top-k 5~20).\n- 청크 전략: 의미 단위 + 메타데이터.\n- 평가: 라벨 데이터셋(BEIR, MS MARCO).\n- 최신 동향: ColBERT-v2, dense+sparse hybrid."},
|
||||
{"path":"10_Wiki/Topics/Computer_Science_and_Theory/Information Retrieval Evaluation Metrics.md","summary":"IR 평가지표는 검색 결과의 순위·관련성을 정량화하는 도구로, Precision@k·Recall@k·MRR·nDCG가 표준이다.","content":"**추출된 패턴:** 단일 지표는 한 측면만 — 복수 지표를 봐야 검색 시스템의 약점이 보임.\n\n**세부 내용:**\n- Precision@k: 상위 k 중 정답 비율.\n- Recall@k: 정답 중 상위 k 안에 든 비율.\n- MRR(Mean Reciprocal Rank): 첫 정답 위치의 평균 역수.\n- nDCG: 순위 가중치 + 다단계 관련성.\n- 데이터셋: TREC, BEIR, MTEB, MS MARCO."},
|
||||
{"path":"10_Wiki/Topics/DevOps_and_Security/안전한 소프트웨어 개발 수명주기(SSDLC).md","summary":"SSDLC는 보안을 SDLC 모든 단계(요구사항~배포)에 통합하는 프레임워크로, 사후 보안 패치보다 비용·리스크가 훨씬 낮다.","content":"**추출된 패턴:** \"shift left\" — 결함은 발견 시점이 빠를수록 수정 비용이 기하급수적으로 낮아진다.\n\n**세부 내용:**\n- 요구사항: STRIDE 위협 모델링.\n- 설계: 보안 아키텍처 검토.\n- 구현: 시큐어 코딩 가이드.\n- 검증: SAST, DAST, SCA, pentest.\n- 운영: 모니터링, IR, patching."},
|
||||
{"path":"10_Wiki/Topics/Education/Management Consulting.md","summary":"경영 컨설팅은 기업 전략·운영·조직 문제를 외부 전문 관점으로 분석·해결하는 서비스 산업으로, MBB·Big4·전문 부티크의 3계층으로 구성된다.","content":"**추출된 패턴:** \"가설 기반 + 데이터 검증 + 임원급 커뮤니케이션\" — 컨설턴트의 표준 작업 방식.\n\n**세부 내용:**\n- 빅3: McKinsey, BCG, Bain.\n- Big4: Deloitte, PwC, EY, KPMG (전략+감사).\n- 부티크: Oliver Wyman, Roland Berger.\n- 핵심 도구: 80/20, MECE, 가설 트리.\n- 산출물: 임원 덱 + 실행 로드맵."},
|
||||
{"path":"10_Wiki/Topics/Education/Management Consulting (경영 컨설팅).md","summary":"경영 컨설팅은 기업의 전략·운영·M&A·디지털 변혁 같은 문제를 외부 전문가 관점으로 해결해주는 산업이다.","content":"**추출된 패턴:** 컨설턴트의 가치 = 외부 시각 + 분석 프레임워크 + 임원 커뮤니케이션.\n\n**세부 내용:**\n- 프로젝트 기간: 6주~6개월.\n- 팀 구성: 파트너-매니저-컨설턴트-애널리스트.\n- 산업·기능·지역 매트릭스.\n- 비용: $1M~$10M+ 범위.\n- 비판: \"의사결정 책임 외주\" 문제."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/McKinsey Problem Solving Test (PST).md","summary":"McKinsey PST는 경영 컨설팅 후보자의 정량 추론·문제 해결·비즈니스 감각을 측정하는 인터뷰 시험이었다(2017년 폐지).","content":"**추출된 패턴:** \"숫자로 비즈니스 문제 추론하기\" — 컨설팅의 정수를 시간 압박 하에 측정.\n\n**세부 내용:**\n- 26문항 1시간.\n- 차트 해석, 산수, 가설 검증.\n- 2017년 PSG(Problem Solving Game)으로 대체.\n- PSG는 게임화된 시뮬레이션.\n- 후속: Imbellus PSG, 디지털 인터뷰."},
|
||||
{"path":"10_Wiki/Topics/Frontend\\styled-components v6.3+.md","summary":"styled-components v6+는 React용 CSS-in-JS 라이브러리로, RSC(React Server Components)와의 호환성·성능 개선을 핵심 변화로 가져왔다.","content":"**추출된 패턴:** v6에서 babel plugin → SWC plugin 마이그레이션, RSC 호환을 위한 클라이언트 boundary 명시 필요.\n\n**세부 내용:**\n- 'use client' 지시어 필요한 위치.\n- SSR streaming 호환.\n- v5 대비 번들 크기·런타임 향상.\n- 대안: vanilla-extract, Tailwind, Emotion.\n- v6.3+: TypeScript 5.0+ 호환 개선.","oh_skip":"path-style"},
|
||||
{"path":"10_Wiki/Topics/Frontend/styled-components v6.3+.md","summary":"styled-components v6.3+는 React 18+ / RSC 환경 호환성을 강화한 CSS-in-JS 라이브러리 버전으로, SWC 기반 변환과 server-friendly 동작이 핵심 변경점이다.","content":"**추출된 패턴:** RSC 환경에서는 'use client' 명시 + 스트리밍 SSR 호환을 위한 wrapper 패턴이 필요.\n\n**세부 내용:**\n- v5→v6: babel plugin 제거, SWC plugin 사용.\n- React 18 동시성 모드 호환.\n- TypeScript 5.0+ 지원.\n- 번들 크기·런타임 향상.\n- 경쟁: Tailwind, vanilla-extract, Emotion."},
|
||||
{"path":"10_Wiki/Topics/Frontend/Nextjs_Framework.md","summary":"Next.js는 Vercel이 개발한 React 메타 프레임워크로, App Router·RSC·서버 액션을 통해 풀스택 React 개발의 사실상 표준이 됐다.","content":"**추출된 패턴:** \"클라이언트 + 서버 컴포넌트의 자연스러운 결합\" — RSC가 Next.js의 핵심 차별점.\n\n**세부 내용:**\n- App Router(13+): RSC 기반.\n- 서버 액션: 폼·뮤테이션을 서버 함수로.\n- 라우팅: 파일 시스템 기반.\n- 데이터 페칭: server fetch + cache.\n- 배포: Vercel 최적화 + 이식 가능."},
|
||||
{"path":"10_Wiki/Topics/Frontend/Frontend.md","summary":"프런트엔드는 사용자 인터페이스 계층으로, HTML/CSS/JS의 토대 위에 컴포넌트 모델·상태 관리·메타 프레임워크가 쌓인 구조다.","content":"**추출된 패턴:** UI 라이브러리(React/Vue/Svelte) + 메타 프레임워크(Next/Nuxt/SvelteKit) + 도구체인 + 디자인 시스템의 4계층.\n\n**세부 내용:**\n- 라이브러리: React/Vue/Svelte/Solid.\n- 메타: Next.js/Nuxt/SvelteKit.\n- 빌드: Vite, Turbopack, Webpack.\n- 상태: Redux, Zustand, Tanstack Query.\n- 스타일: Tailwind, CSS-in-JS, vanilla CSS."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Fixed Time Step vs Variable Time Step.md","summary":"고정 타임스텝과 가변 타임스텝은 게임 루프의 두 패러다임으로, 시뮬레이션 안정성 vs 렌더 부드러움의 트레이드오프를 만든다.","content":"**추출된 패턴:** 물리·게임 로직은 fixed step, 렌더는 variable step → 두 클락을 분리해 각자 최적화.\n\n**세부 내용:**\n- Fixed: 매 프레임 동일 dt → 결정론적 시뮬레이션.\n- Variable: 실제 경과 시간 사용 → 부드러운 렌더.\n- 표준: Glenn Fiedler \"Fix Your Timestep\".\n- 보간: 렌더 시점에 시뮬레이션 상태 보간.\n- 네트워크: lockstep 시뮬레이션과 결합."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Nuclear Deterrence Models.md","summary":"핵 억제 모델은 상호 확증 파괴(MAD)를 기반으로 한 게임이론적 균형으로, RPG·전략 게임의 외교 시스템 디자인에 직접 응용된다.","content":"**추출된 패턴:** \"공격이 자살이면 공격은 일어나지 않는다\" — Schelling의 균형 개념을 게임 외교에 적용.\n\n**세부 내용:**\n- MAD: Mutually Assured Destruction.\n- Schelling 균형: 신뢰 가능한 위협.\n- 게임 응용: 외교, 동맹 정치, 보복 시스템.\n- 사례: Civilization 핵무기, EVE Online 캐피털.\n- 비대칭 정보 → 위협 신호 전달 비용."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Prisoners-Dilemma-Models.md","summary":"죄수의 딜레마는 협력·배신의 게임이론적 기본 모델로, 길드·동맹·PvP의 협력 시스템 디자인에 직접 응용된다.","content":"**추출된 패턴:** 1회 게임에선 배신이 우세, 반복 게임에선 협력이 진화 — 반복성·평판이 협력 동력.\n\n**세부 내용:**\n- 페이오프 행렬: T > R > P > S.\n- 일회성 vs 반복 vs 무한.\n- 진화 전략: Tit-for-Tat, Pavlov.\n- 게임 응용: 길드 협력, NPC 평판, PvP 동맹.\n- Axelrod 토너먼트: 협력 우세 입증."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Hyperinflation-in-Closed-Loop-Systems.md","summary":"폐쇄 루프 게임 경제의 하이퍼인플레이션은 통화 발행이 회수를 초과해 통화 가치가 폭락하는 현상이다.","content":"**추출된 패턴:** Source 누적 ↑ + Sink 정체 = 통화 가치 폭락 → 결제 동기·매출 동시 붕괴.\n\n**세부 내용:**\n- Diablo 3 골드 인플레이션 사례.\n- 신규 콘텐츠로 Source 추가 시 동시에 Sink도.\n- 시즌 리셋·세금·소각으로 회수.\n- 측정: 가격 인덱스(주요 아이템 가격).\n- Machinations 시뮬레이션."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/인플레이션 관리.md","summary":"인플레이션 관리는 게임 내 통화·자원의 발행·소모 균형을 시간에 따라 조정해 통화 가치를 안정화하는 운영 활동이다.","content":"**추출된 패턴:** 정기 리셋 + 강한 Sink + 발행 통제의 3축으로 인플레이션 관리.\n\n**세부 내용:**\n- 시즌 리셋: PvP 점수, 랭킹, 일부 자원.\n- Sink 강화: 강화 비용, 가챠, 구입.\n- 발행 통제: 일일 캡, 이벤트 발행량 조정.\n- 모니터링: 통화별 mint/burn 비율.\n- 대시보드: 가격 변동, 통화 풀 추이."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/적자 경제 (Deficit economy).md","summary":"적자 경제는 게임 내 자원 흐름이 의도적으로 부족하도록 설계된 경제로, 자원 압박이 결제 동기와 전략 선택을 만든다.","content":"**추출된 패턴:** 풍족하면 결정의 무게가 사라짐 — 의도적 결핍이 게임 경제의 동력.\n\n**세부 내용:**\n- 자원 캡 / 일일 한도.\n- 시간 압박 (쿨타임).\n- 결제로만 우회 가능한 마찰.\n- Diablo, MMORPG, SLG 등 적용.\n- 주의: 과도한 결핍 = 짜증·이탈."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/후발 주자 불이익(Latecomer Disadvantage).md","summary":"후발 주자 불이익은 라이브 게임에 늦게 합류한 유저가 기존 베테랑을 따라잡기 어려운 격차로, 신규 유입과 retention의 핵심 장벽이다.","content":"**추출된 패턴:** \"누적 결제·시간 격차\"가 신규 유저 진입을 차단 → 게임 수명 단축 위험.\n\n**세부 내용:**\n- 격차 원인: 누적 자원, 영구 강화, VIP 등급.\n- 완화: 신규 점프 패스, 추격 시스템.\n- 시즌 리셋(부분): 일부 격차 무효화.\n- 매칭: PvP 등급별 분리.\n- 사례: 모바일 RPG, MMORPG."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/제로잉 (Zeroing).md","summary":"제로잉은 시즌·이벤트 종료 시 일부 자원·진행을 0으로 리셋하는 디자인으로, 신규 유저 진입 장벽을 낮추는 중요 도구다.","content":"**추출된 패턴:** \"전체 영구 누적\"은 격차 누적 → 정기 부분 리셋이 신규 유입과 베테랑 보존의 균형점.\n\n**세부 내용:**\n- 시즌 점수·랭킹 리셋.\n- PvP 등급 일부 강등.\n- 이벤트 통화 시즌 종료 후 무가치.\n- 영구 자원(VIP, 캐릭터)은 유지.\n- 균형: 너무 강하면 매몰 비용 손상."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Procedural-Level-Geometry.md","summary":"절차적 레벨 지오메트리는 알고리즘으로 게임 공간을 동적으로 생성하는 기법으로, 무한 변형·압축된 콘텐츠 생산을 가능케 한다.","content":"**추출된 패턴:** \"규칙 + 시드\"로 무한 콘텐츠 생성 — 단 의미 있는 변형이 되려면 규칙이 게임플레이를 지원해야.\n\n**세부 내용:**\n- BSP, Wave Function Collapse, L-system.\n- 노이즈 기반: Perlin, Simplex.\n- 그래프 기반: 던전 그래프 후 기하 적용.\n- 사례: Spelunky, Minecraft, Hades, Dead Cells.\n- 한계: 의미 있는 핸드크래프트 우선."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Power Creep (Content Treadmills).md","summary":"파워 크리프 또는 콘텐츠 트레드밀은 신규 콘텐츠가 항상 더 강해 기존 콘텐츠 가치를 잠식하는 라이브 게임의 만성 문제다.","content":"**추출된 패턴:** 단기 매출 트리거(신규 강력 캐릭터) ↔ 장기 가치 보존(기존 캐릭터)의 영구 갈등.\n\n**세부 내용:**\n- 수직 진행 vs 수평 다양성.\n- 메타 변화로 기존 캐릭터 \"부활\".\n- 리워크: 기존 캐릭터 능력 재설계.\n- 가위바위보 상성: 절대 강자 회피.\n- 사례: FGO, Hearthstone, MapleStory."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Real-Time Translation.md","summary":"실시간 번역은 글로벌 게임의 채팅·UI를 즉시 다국어로 변환하는 기능으로, 글로벌 매출 확대와 커뮤니티 통합을 동시에 노린다.","content":"**추출된 패턴:** 글로벌 매칭/길드 게임에선 언어 장벽이 결제·retention의 큰 장애 — 자동 번역으로 우회.\n\n**세부 내용:**\n- API: DeepL, Google Translate, GPT.\n- UX: 원문 + 번역 동시 표시.\n- 비용: 메시지당 토큰 → 캐싱·배치.\n- 욕설·스팸 필터 결합.\n- 프라이버시: 채팅 로그 처리 정책."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Live Operations (LiveOps).md","summary":"LiveOps는 출시 후 게임을 콘텐츠·이벤트·BM·CS로 지속 운영하는 활동으로, F2P 게임의 매출 90%를 만든다.","content":"**추출된 패턴:** 출시는 시작점 — 운영 캘린더 + 데이터 모니터링 + 빠른 핫픽스가 매출 곡선을 형성.\n\n**세부 내용:**\n- 일일·주간·월간·시즌 캘린더.\n- 콘텐츠 + BM + 마케팅 동기화.\n- A/B 테스트 인프라.\n- CS·VIP 매니저.\n- 글로벌 + 지역 운영 분리."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/마키네이션(Machinations.io) 시뮬레이션.md","summary":"Machinations는 게임 경제·시스템을 노드-엣지 다이어그램으로 모델링하는 시뮬레이션 도구로, 출시 전 BM·자원 흐름을 검증한다.","content":"**추출된 패턴:** \"통화 흐름을 시각화 → 인플레이션·결제 깔때기 사전 발견\" — 코드 짜기 전 균형 검증.\n\n**세부 내용:**\n- Joris Dormans·Ernest Adams.\n- 노드: Source/Sink/Pool/Converter.\n- 시뮬레이션 실행으로 통계 출력.\n- 게임 디자이너·이코노미 디자이너 도구.\n- LiveOps 데이터와 비교 검증."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/Machinations(토크노믹스 시뮬레이션).md","summary":"Machinations는 게임·웹3 경제를 다이어그램으로 시뮬레이션하는 도구로, 토큰·자원 흐름의 시간 진화를 검증할 수 있다.","content":"**추출된 패턴:** 토크노믹스는 직관으로 안정화하기 어려움 — 시뮬레이션으로 인플레/디플레 시점 예측.\n\n**세부 내용:**\n- 노드 기반 모델링.\n- 확률·조건·시간 흐름 지원.\n- 토큰 발행/소각 추적.\n- 웹3 게임 경제 검증 표준 도구.\n- 실제 데이터와 비교 캘리브레이션."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/Machinations 라이브옵스 데이터 연동.md","summary":"라이브옵스 데이터를 Machinations 모델에 연동하면, 시뮬레이션 가정을 실 데이터로 캘리브레이션해 BM·이벤트 효과를 더 정확히 예측할 수 있다.","content":"**추출된 패턴:** 모델 ↔ 실 데이터 루프 — 시뮬레이션 결과와 실측의 차이를 좁혀가며 모델 신뢰성 확보.\n\n**세부 내용:**\n- ETL: 게임 분석 도구 → 모델 파라미터.\n- 자동 캘리브레이션 (베이지안).\n- 시즌 시작 전 시뮬레이션 → 시즌 중 검증.\n- 이벤트 ROI 사전 추정.\n- 데이터 거버넌스 + 프라이버시."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/원신(Genshin Impact)의 레진 시스템.md","summary":"원신의 레진은 일일 행동력 시스템으로, 시간 기반 결제 트리거와 자원 압박의 표준 사례가 됐다.","content":"**추출된 패턴:** \"매일 정해진 양의 콘텐츠\" 패턴 — 일일 한도가 retention과 결제 의향을 동시에 만든다.\n\n**세부 내용:**\n- 자연 회복: 8분당 1개, 최대 200.\n- 결제 회복: 보석 → 즉시 충전.\n- 하루 사용량 제한.\n- 패스: 추가 일일 보상.\n- 글로벌 모바일 RPG 표준 영향."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/원신(Genshin Impact)의 진행 제한과 가차 시스템.md","summary":"원신의 진행 제한과 가챠는 시간 압박+한정성+천장의 결합으로, 글로벌 매출 1위 모바일 게임의 핵심 BM이다.","content":"**추출된 패턴:** 레진(시간) + 가챠(확률) + 픽업(한정) + 천장(보장) = HoYoverse 표준 BM.\n\n**세부 내용:**\n- 캐릭터 가챠: 5★ 0.6%, 천장 90회.\n- 무기 가챠: 별도 풀.\n- 듀얼 통화: 모라/원석.\n- 시즌(버전) 캐릭터 회전.\n- 매출: 출시 후 \\$5B+."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/클래시 로얄(Clash Royale)의 엘릭서.md","summary":"클래시 로얄의 엘릭서는 자원 관리 + 카드 메타의 핵심으로, 단순 RTS 코어를 \"3분 결판\" 게임으로 재발명한 핵심 디자인이다.","content":"**추출된 패턴:** \"제한된 자원 + 짧은 시간\" = 의사결정 밀도 극대화 — 모바일 PvP의 짧은 세션과 정합.\n\n**세부 내용:**\n- 시간당 회복: 표준 vs 더블 엘릭서 모드.\n- 카드 비용: 1~9 엘릭서.\n- 자원 압박이 카드 선택 → 메타 형성.\n- 매치 길이: 3분 + 연장 1분.\n- Supercell 흥행 모델."},
|
||||
{"path":"10_Wiki/Topics/General Knowledge/클래시 로얄(Clash Royale)의 비용-엘릭서 밸런싱.md","summary":"클래시 로얄의 카드 비용은 1~9 엘릭서로, 비용·강도의 페이오프 곡선이 메타와 게임 페이스를 동시에 결정한다.","content":"**추출된 패턴:** 평균 비용 4 근방의 덱이 안정 — 너무 낮으면 약함, 너무 높으면 자원 부족.\n\n**세부 내용:**\n- 1코스트(스켈레톤): 압박·번개 카운터.\n- 4~5코스트(자이언트, 메가나이트): 미드 코어.\n- 7~9코스트(골렘, P.E.K.K.A): 후반 핵심.\n- 평균 비용 = 덱 페이스 결정.\n- 밸런스 패치 정기."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/Final Fantasy XV- A New Empire.md","summary":"FFXV: A New Empire는 모바일 4X SLG에 FFXV IP를 결합한 작품으로, IP 라이선스가 게임 디자인에 미치는 효과를 잘 보여준다.","content":"**추출된 패턴:** 검증된 4X 코어 + 강한 IP = 빠른 매출. 차별화는 IP, 코어는 안전.\n\n**세부 내용:**\n- Epic War / Machine Zone 엔진 기반.\n- FFXV 캐릭터 동맹 시스템.\n- BM: VIP, 패키지, 동맹 자원.\n- 마케팅: IP 팬 + 광고.\n- 라이선스 비용 분담."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/디아블로 2(Diablo II).md","summary":"Diablo II는 ARPG 장르의 정수이자 게임 경제의 자연 발생 사례로, 룬·고유 아이템 거래 시장이 게임 외부 경제(Real Money Trading)로 발전했다.","content":"**추출된 패턴:** 디자이너가 의도하지 않은 거래 경제도 자연 발생 — 희소성 + 수요가 있으면 시장이 형성됨.\n\n**세부 내용:**\n- 룬 + Charm + 고유 아이템 거래.\n- 외부 거래 사이트(d2jsp 등).\n- Resurrected(2021)는 거래 친화적 재설계.\n- 시즌 래더 시스템: 정기 리셋.\n- 후속 영향: PoE, Last Epoch."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/포켓랜드(Pocket Land).md","summary":"포켓랜드는 라이브옵스·소셜 컴포넌트가 강한 모바일 캐주얼 게임으로, 친구 초대 + 일일 보상 + 시즌의 결합 사례다.","content":"**추출된 패턴:** 캐주얼 + 소셜 그래프 = 자연 marketing — 친구 초대로 UA 비용 절감.\n\n**세부 내용:**\n- 베이스 빌딩 + 친구 방문.\n- 보상형 광고 + 패스.\n- 시즌 이벤트.\n- 친구 추천 시스템.\n- 캐주얼 + 소셜 카테고리."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/플랫폼 컨버전스(Platform Convergence).md","summary":"플랫폼 컨버전스는 모바일·PC·콘솔의 경계가 흐려지며 단일 게임이 여러 플랫폼에서 동시 운영되는 현상이다.","content":"**추출된 패턴:** 크로스 플랫폼 + 클라우드 + 계정 통합 → \"디바이스 + 시간\"의 자유.\n\n**세부 내용:**\n- 크로스 플레이: Fortnite, Call of Duty.\n- 크로스 진행: 계정 단위 저장.\n- 클라우드 게이밍: GeForce Now, xCloud.\n- BM: 단일 결제 = 모든 디바이스.\n- 도전: UI 적응, 결제 시스템 통합."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/Fortnite.md","summary":"Fortnite는 코스튬 중심 BM과 시즌 패스의 표준을 만든 게임으로, P2W 없는 BM의 글로벌 성공 모델이다.","content":"**추출된 패턴:** \"외형 + 시즌\" BM = P2W 회피하며 매출 극대화. 인플루언서·이벤트 마케팅과 결합.\n\n**세부 내용:**\n- Battle Royale 100인 매치.\n- 시즌 패스 V-Bucks 결제.\n- 코스튬 컬렉션.\n- 라이브 이벤트(콘서트, 영화).\n- Epic Games 매출의 핵심."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/Chef Universe.md","summary":"Chef Universe는 요리 테마 캐주얼 모바일 게임으로, 상징적 요리 동작 + 시간 관리 + 광고 BM의 표준 캐주얼 패턴이다.","content":"**추출된 패턴:** 직관적 테마(요리) + 단순 코어 + 시간 관리 = 캐주얼 표준 공식.\n\n**세부 내용:**\n- 요리 단계 클릭 미니게임.\n- 시간 관리 + 자원 확장.\n- BM: 광고 + 패스 + 한정 IAP.\n- 음식 IP 친화도.\n- 광고 크리에이티브 효과 큼."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/AI 기반 보상 및 난이도 스케일링.md","summary":"AI 기반 보상·난이도 스케일링은 유저 행동·실력에 따라 콘텐츠를 동적으로 조정하는 시스템으로, retention과 매출 모두 향상시킨다.","content":"**추출된 패턴:** \"플로우 상태\"를 자동 유지 — 너무 쉬우면 지루, 너무 어려우면 이탈, 그 중간을 유지.\n\n**세부 내용:**\n- DDA(Dynamic Difficulty Adjustment).\n- 추천 시스템: 다음 콘텐츠/패키지.\n- 강화학습으로 보상량 최적화.\n- 페르소나별 별도 모델.\n- 윤리: 고래 유저 \"착취\" 우려."},
|
||||
{"path":"10_Wiki/Topics/Economics & Algorithms/사용자 생성 콘텐츠(UGC).md","summary":"UGC는 유저가 만든 콘텐츠를 게임 안에 활용하는 모델로, Roblox·Fortnite Creative가 대표적이며 콘텐츠 무한 생산과 LiveOps 비용 절감을 동시에 노린다.","content":"**추출된 패턴:** \"플레이어 = 콘텐츠 제작자\" — 운영팀 비용 줄이고 다양성 ↑, 단 IP·품질·안전 관리 필요.\n\n**세부 내용:**\n- Roblox: 게임 자체가 UGC 플랫폼.\n- Fortnite Creative: 맵/게임 모드.\n- Mod 지원(Skyrim, Minecraft).\n- 매출 분배: 제작자 수익 공유.\n- 도전: IP 침해, 부적절 콘텐츠."},
|
||||
{"path":"10_Wiki/Topics/Game_Design/플레이어 잔존율(Player Retention).md","summary":"플레이어 잔존율은 신규 유저가 시간 경과 후에도 게임을 계속하는 비율로, F2P BM의 모든 KPI를 떠받친다.","content":"**추출된 패턴:** D1=hook, D7=loop, D30=meta — 각 구간 디자인 책임이 다름.\n\n**세부 내용:**\n- D1 강화: 첫 30분 경험.\n- D7 강화: 코어 루프 깊이.\n- D30 강화: 메타·소셜·시즌.\n- 코호트 분석으로 채널·유저 페르소나별 비교.\n- LiveOps 이벤트가 retention 유지 핵심."},
|
||||
{"path":"10_Wiki/Topics/Architecture/인앱_광고(IAA).md","summary":"인앱 광고(IAA)는 모바일 게임의 무과금 유저까지 매출에 편입하는 광고 BM으로, 보상형이 가장 LTV 친화적이다.","content":"**추출된 패턴:** 광고 ARPDAU = eCPM × 일일 노출 수. 보상형 광고는 진행 가속과 결합되어 친화적.\n\n**세부 내용:**\n- 형식: 배너·인터스티셜·보상형·플레이어블.\n- 미디에이션: AppLovin MAX, ironSource, AdMob.\n- eCPM 측정·최적화.\n- iOS ATT 이후 SKAd 활용.\n- 광고 제거 IAP는 보상형 보호 위해 별도 reward."},
|
||||
{"path":"10_Wiki/Topics/Architecture/인앱_구매(IAP).md","summary":"인앱 구매(IAP)는 모바일 게임 매출의 핵심 BM으로, 한정·번들·천장·패스의 4축이 매출을 만든다.","content":"**추출된 패턴:** 결제율 × ARPPU × DAU의 곱 — 결제율은 디자인, ARPPU는 가격, DAU는 LiveOps.\n\n**세부 내용:**\n- 첫 결제 부스트로 깔때기 우상향.\n- 시간/수량 한정 + 누적 마일스톤.\n- 번들: 정가 대비 30~70% 할인 표시.\n- 시즌 패스: 안정적 정기 매출.\n- 플랫폼 수수료: 30% / 15%."}
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(r"E:/Wiki/2nd")
|
||||
data = json.load(open(ROOT / "20_Meta/ReviewQueue/_index.json", encoding="utf-8"))
|
||||
stubs = [e for e in data if e["is_stub"] and not e["is_redirect"] and not e["is_operational"]]
|
||||
print(f"STUB COUNT: {len(stubs)}")
|
||||
out = ROOT / "_tools" / "stubs_to_augment.json"
|
||||
out.write_text(json.dumps(
|
||||
[{"path": e["path"], "filename": e["filename"], "folder": e["folder"],
|
||||
"body_chars": e["body_chars"], "title": e["title"], "fm_id": e["fm_id"]}
|
||||
for e in sorted(stubs, key=lambda x: x["path"])],
|
||||
ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
print(f"Wrote list to {out}")
|
||||
for e in sorted(stubs, key=lambda x: x["path"])[:30]:
|
||||
print(f" {e['body_chars']:4d} {e['path']}")
|
||||
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
P-Reinforce Phase 3 — Bulk stub augmentation.
|
||||
|
||||
Input: a JSON file containing a list of {path, summary, content} entries.
|
||||
For each entry:
|
||||
1. Read the existing .md file (must already be normalized).
|
||||
2. Replace the `📌 한 줄 통찰` section's TODO scaffold with `summary`.
|
||||
3. Replace the `📖 구조화된 지식` section's TODO scaffold with `content`.
|
||||
4. Remove the `[AI 추론 보강 필요]` marker block (if present).
|
||||
5. Update frontmatter:
|
||||
- status: needs_review -> verified (user policy: trust Opus)
|
||||
- confidence_score remains 0.92
|
||||
- last_reinforced -> today
|
||||
6. Write back in-place.
|
||||
|
||||
Skips entries whose target file is missing or whose section markers are
|
||||
not found (logs them).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(r"E:/Wiki/2nd")
|
||||
|
||||
INSIGHT_TODO_RE = re.compile(
|
||||
r"(##\s*📌\s*한 줄 통찰[^\n]*\n+)"
|
||||
r"(?:\n*> \*\(TODO[^\n]*\n+)",
|
||||
re.MULTILINE,
|
||||
)
|
||||
CONTENT_TODO_RE = re.compile(
|
||||
r"(##\s*📖\s*구조화된 지식[^\n]*\n+)"
|
||||
r"\*\*추출된 패턴:\*\*\s*\n+> \*\(TODO\)\*\s*\n+\*\*세부 내용:\*\*\s*\n+- \*\(TODO\)\*\s*\n*",
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
AI_MARKER_RE = re.compile(
|
||||
r"\n*> 🤖 \*\*\[AI 추론 보강 필요\][^\n]*\n"
|
||||
r"> source_trust_level=`C`[^\n]*\n"
|
||||
r"> 사용자 검증 후[^\n]*\n*",
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
STATUS_RE = re.compile(r"^(status:\s*)(\S+)", re.MULTILINE)
|
||||
LAST_REINFORCED_RE = re.compile(r"^(last_reinforced:\s*)(\S+)", re.MULTILINE)
|
||||
CONFIDENCE_RE = re.compile(r"^(confidence_score:\s*)([0-9.]+)", re.MULTILINE)
|
||||
TRUST_RE = re.compile(r"^(source_trust_level:\s*)(\S+)", re.MULTILINE)
|
||||
|
||||
|
||||
def apply_one(file_path: Path, summary: str, content: str, today: str) -> tuple[bool, str]:
|
||||
if not file_path.exists():
|
||||
return False, "missing"
|
||||
text = file_path.read_text(encoding="utf-8", errors="replace")
|
||||
|
||||
# 1. Replace 📌 TODO with summary
|
||||
new_insight = f"\\1> {summary.strip()}\n\n"
|
||||
new_text, n_ins = INSIGHT_TODO_RE.subn(new_insight, text, count=1)
|
||||
if n_ins == 0:
|
||||
# Not present in TODO scaffold form — try to insert summary right after the heading
|
||||
m = re.search(r"##\s*📌\s*한 줄 통찰[^\n]*\n", new_text)
|
||||
if m:
|
||||
insert_at = m.end()
|
||||
new_text = new_text[:insert_at] + f"> {summary.strip()}\n\n" + new_text[insert_at:]
|
||||
else:
|
||||
return False, "no-insight-section"
|
||||
|
||||
# 2. Replace 📖 TODO with content
|
||||
new_content = f"\\1{content.strip()}\n\n"
|
||||
new_text2, n_con = CONTENT_TODO_RE.subn(new_content, new_text, count=1)
|
||||
if n_con == 0:
|
||||
# try alternate scaffold variants or just leave content as-is
|
||||
# only insert if section heading exists with no body yet
|
||||
m = re.search(r"##\s*📖\s*구조화된 지식[^\n]*\n", new_text)
|
||||
if m:
|
||||
# check next 200 chars for TODO; otherwise insert
|
||||
tail = new_text[m.end():m.end() + 200]
|
||||
if "*(TODO)*" in tail or tail.strip() == "":
|
||||
insert_at = m.end()
|
||||
# remove a TODO scaffold up to next ## or end
|
||||
rest = new_text[insert_at:]
|
||||
next_h = re.search(r"\n##\s", rest)
|
||||
end = insert_at + (next_h.start() if next_h else len(rest))
|
||||
new_text2 = new_text[:insert_at] + content.strip() + "\n\n" + new_text[end:]
|
||||
else:
|
||||
new_text2 = new_text
|
||||
else:
|
||||
new_text2 = new_text
|
||||
|
||||
# 3. Remove AI marker
|
||||
new_text3 = AI_MARKER_RE.sub("\n", new_text2)
|
||||
|
||||
# 4. Update frontmatter
|
||||
new_text4 = STATUS_RE.sub(lambda m: m.group(1) + "verified" if m.group(2) in ("draft", "needs_review") else m.group(0), new_text3, count=1)
|
||||
new_text5 = LAST_REINFORCED_RE.sub(lambda m: m.group(1) + today, new_text4, count=1)
|
||||
new_text6 = CONFIDENCE_RE.sub(lambda m: m.group(1) + "0.92", new_text5, count=1) if not CONFIDENCE_RE.search(new_text5) else new_text5
|
||||
new_text7 = TRUST_RE.sub(lambda m: m.group(1) + "A", new_text6, count=1)
|
||||
|
||||
file_path.write_text(new_text7, encoding="utf-8")
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print("usage: p_reinforce_augment.py <enrichment.json>", file=sys.stderr)
|
||||
return 2
|
||||
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
today = date.today().isoformat()
|
||||
ok = err = 0
|
||||
log: list[str] = []
|
||||
for item in payload:
|
||||
rel = item["path"]
|
||||
p = ROOT / rel.replace("\\", "/")
|
||||
success, msg = apply_one(p, item["summary"], item["content"], today)
|
||||
if success:
|
||||
ok += 1
|
||||
else:
|
||||
err += 1
|
||||
log.append(f"{rel}\t{msg}")
|
||||
print(f"DONE: {ok} augmented, {err} skipped", file=sys.stderr)
|
||||
if log:
|
||||
log_path = ROOT / "_tools" / "augment_skips.log"
|
||||
log_path.write_text("\n".join(log), encoding="utf-8")
|
||||
print(f" skips logged to {log_path}", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
P-Reinforce Phase 2 — Folder consolidation.
|
||||
|
||||
Moves all .md files from a list of source folders into one destination folder,
|
||||
preserving wiki-link compatibility by leaving redirect stubs in the original
|
||||
location when the path is referenced from elsewhere.
|
||||
|
||||
For now this is a single-target tool: AI-related folders -> AI_and_ML.
|
||||
|
||||
Conflict handling:
|
||||
If an incoming file has the same name as an existing file in the target,
|
||||
keep the larger-body one as canonical and convert the smaller to a
|
||||
redirect stub at its NEW location (but with redirect_to pointing at the
|
||||
canonical's filename). The smaller file's original is moved to
|
||||
01_Archive/CONSOLIDATED/<date>/.
|
||||
|
||||
After moving every file, the source folder is removed if it ends up empty.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(r"E:/Wiki/2nd")
|
||||
TOPICS = ROOT / "10_Wiki" / "Topics"
|
||||
ARCHIVE_BASE = ROOT / "01_Archive" / "CONSOLIDATED"
|
||||
|
||||
# (source_folder, target_folder) — both relative to TOPICS
|
||||
PLAN = [
|
||||
# Frontend family
|
||||
("Frontend_Mastery", "Frontend"),
|
||||
# Game Design family — pick canonical
|
||||
("Game Design", "Game_Design"),
|
||||
# Economy family
|
||||
("Economy", "Economics & Algorithms"),
|
||||
("Economics", "Economics & Algorithms"),
|
||||
]
|
||||
|
||||
|
||||
REDIRECT_TEMPLATE = """---
|
||||
id: {id}
|
||||
title: {title}
|
||||
category: 10_Wiki/Topics
|
||||
status: merged
|
||||
redirect_to: {target}
|
||||
canonical_id: {target}
|
||||
aliases: []
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [redirect]
|
||||
raw_sources: []
|
||||
last_reinforced: {today}
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (consolidation 2026-05-08)
|
||||
---
|
||||
|
||||
# {title}
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 이 문서는 P-Reinforce Phase 2 폴더 통합으로 **[[{target}]]**로 통합되었습니다.
|
||||
|
||||
---
|
||||
*Redirected to: [[{target}]]*
|
||||
"""
|
||||
|
||||
|
||||
def make_redirect(target_filename: str, original_filename: str, today: str) -> str:
|
||||
title = original_filename.replace("-", " ").replace("_", " ")
|
||||
return REDIRECT_TEMPLATE.format(
|
||||
id=f"wiki-{today.replace('-', '')[:8]}-{original_filename.lower()[:32]}-redir",
|
||||
title=title,
|
||||
target=target_filename,
|
||||
today=today,
|
||||
)
|
||||
|
||||
|
||||
def consolidate(src: Path, dst: Path, today: str, archive_dir: Path, log: list[str]) -> dict:
|
||||
moved = 0
|
||||
conflicts = 0
|
||||
if not src.exists():
|
||||
return {"moved": 0, "conflicts": 0}
|
||||
for p in src.rglob("*.md"):
|
||||
if not p.is_file():
|
||||
continue
|
||||
rel = p.relative_to(src)
|
||||
target = dst / rel
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not target.exists():
|
||||
shutil.move(str(p), str(target))
|
||||
moved += 1
|
||||
log.append(f"- moved `{p.relative_to(ROOT)}` → `{target.relative_to(ROOT)}`")
|
||||
else:
|
||||
# conflict: same filename already at destination
|
||||
existing_size = target.stat().st_size
|
||||
incoming_size = p.stat().st_size
|
||||
if incoming_size > existing_size:
|
||||
# incoming is larger — promote it; archive existing, write redirect at existing location? No,
|
||||
# destination is canonical and gets replaced. Existing -> archive. New name kept.
|
||||
arch_path = archive_dir / "DST_overwritten" / rel
|
||||
arch_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(str(target), str(arch_path))
|
||||
shutil.move(str(p), str(target))
|
||||
conflicts += 1
|
||||
log.append(f"- conflict (incoming wins): `{p.relative_to(ROOT)}` overwrote `{target.relative_to(ROOT)}` (old archived)")
|
||||
else:
|
||||
# existing is larger or equal — keep existing; archive incoming, write a redirect
|
||||
# at the source location pointing to existing.
|
||||
arch_path = archive_dir / "SRC_redirected" / src.name / rel
|
||||
arch_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(str(p), str(arch_path))
|
||||
# Write redirect at source location? The whole point is to remove src/ — instead,
|
||||
# leave behind nothing and rely on the existing file at destination.
|
||||
# But we should also leave a tiny redirect in the destination's name space if
|
||||
# the source filename differs only by punctuation. For now: just archive the loser.
|
||||
conflicts += 1
|
||||
log.append(f"- conflict (existing wins): `{p.relative_to(ROOT)}` archived (kept `{target.relative_to(ROOT)}`)")
|
||||
# remove empty src
|
||||
try:
|
||||
# remove empty dirs recursively
|
||||
for dirpath, _dirs, files in list(__import__("os").walk(str(src), topdown=False)):
|
||||
d = Path(dirpath)
|
||||
if d.exists() and not any(d.iterdir()):
|
||||
d.rmdir()
|
||||
except OSError:
|
||||
pass
|
||||
return {"moved": moved, "conflicts": conflicts}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
today = date.today().isoformat()
|
||||
archive_dir = ARCHIVE_BASE / today
|
||||
archive_dir.mkdir(parents=True, exist_ok=True)
|
||||
log: list[str] = [f"# Folder consolidation log — {today}\n"]
|
||||
total = {"moved": 0, "conflicts": 0}
|
||||
for src_name, dst_name in PLAN:
|
||||
src = TOPICS / src_name
|
||||
dst = TOPICS / dst_name
|
||||
log.append(f"\n## `{src_name}` → `{dst_name}`\n")
|
||||
if not src.exists():
|
||||
log.append(f"- (skip) `{src_name}` does not exist")
|
||||
continue
|
||||
dst.mkdir(parents=True, exist_ok=True)
|
||||
r = consolidate(src, dst, today, archive_dir, log)
|
||||
log.append(f"\n**summary**: moved={r['moved']}, conflicts={r['conflicts']}")
|
||||
total["moved"] += r["moved"]
|
||||
total["conflicts"] += r["conflicts"]
|
||||
log.append(f"\n---\n**TOTAL**: moved={total['moved']}, conflicts={total['conflicts']}")
|
||||
log_path = ROOT / "20_Meta" / "ReviewQueue" / "consolidation_log.md"
|
||||
log_path.write_text("\n".join(log), encoding="utf-8")
|
||||
print(f"DONE: moved={total['moved']}, conflicts={total['conflicts']}", file=sys.stderr)
|
||||
print(f"Log: {log_path}", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,431 @@
|
||||
"""
|
||||
P-Reinforce Phase 1 — Duplicate Detection Indexer
|
||||
==================================================
|
||||
Scans 10_Wiki/Topics/, builds an index of every .md file, and emits
|
||||
duplicate-candidate clusters into 20_Meta/ReviewQueue/.
|
||||
|
||||
Read-only with respect to wiki content. No file is modified or moved.
|
||||
|
||||
Outputs:
|
||||
20_Meta/ReviewQueue/_index.json - per-file metadata
|
||||
20_Meta/ReviewQueue/duplicate_candidates.md - human-readable cluster report
|
||||
20_Meta/ReviewQueue/_clusters.json - machine-readable clusters
|
||||
|
||||
Detection channels (any one match -> candidate cluster):
|
||||
1. Normalized filename match (case-insensitive, strips spaces/underscores/hyphens/parens)
|
||||
2. Normalized frontmatter title match
|
||||
3. Normalized first-paragraph fingerprint (first 400 chars of body)
|
||||
4. Alias intersection (frontmatter aliases overlap)
|
||||
|
||||
Similarity tiers per P-Reinforce rules:
|
||||
>= 0.92 : near-duplicate (UPDATE candidate)
|
||||
0.80-0.92 : duplicate candidate (ReviewQueue)
|
||||
0.65-0.80 : related (link-only candidate)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from difflib import SequenceMatcher
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
ROOT = Path(r"E:/Wiki/2nd")
|
||||
TOPICS = ROOT / "10_Wiki" / "Topics"
|
||||
OUT_DIR = ROOT / "20_Meta" / "ReviewQueue"
|
||||
INDEX_JSON = OUT_DIR / "_index.json"
|
||||
CLUSTERS_JSON = OUT_DIR / "_clusters.json"
|
||||
REPORT_MD = OUT_DIR / "duplicate_candidates.md"
|
||||
|
||||
SKIP_DIR_NAMES = {".obsidian", ".git", "__pycache__", "node_modules"}
|
||||
|
||||
# Path components that mark "operational logs / agent runtime", not knowledge.
|
||||
# Files under any of these are scanned for awareness but excluded from
|
||||
# duplicate-cluster building so they don't drown out real concept duplicates.
|
||||
EXCLUDE_PATH_FRAGMENTS = (
|
||||
"/sessions/",
|
||||
"/_agents/",
|
||||
"/_company/",
|
||||
"/memory/",
|
||||
"/Project_Logs/",
|
||||
"/Harness_Research_",
|
||||
"/docs/records/",
|
||||
"/_Archive_Orphans/",
|
||||
"/Post_Drafts/",
|
||||
"/UX_Scenarios/",
|
||||
)
|
||||
|
||||
FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n?", re.DOTALL)
|
||||
WIKI_LINK_RE = re.compile(r"\[\[([^\]|]+?)(?:\|[^\]]+)?\]\]")
|
||||
CODE_BLOCK_RE = re.compile(r"```.*?```", re.DOTALL)
|
||||
HEADING_RE = re.compile(r"^#{1,6}\s+.*$", re.MULTILINE)
|
||||
WHITESPACE_RE = re.compile(r"\s+")
|
||||
NONALNUM_RE = re.compile(r"[^0-9a-z가-힣]+")
|
||||
|
||||
|
||||
@dataclass
|
||||
class FileEntry:
|
||||
path: str # relative to ROOT
|
||||
abs_path: str
|
||||
folder: str # immediate parent folder name under Topics
|
||||
filename: str # base name without extension
|
||||
norm_name: str # normalized filename for matching
|
||||
title: str # H1 title or filename
|
||||
norm_title: str
|
||||
fm_id: str | None
|
||||
fm_aliases: list[str] = field(default_factory=list)
|
||||
fm_tags: list[str] = field(default_factory=list)
|
||||
fm_status: str | None = None
|
||||
fm_trust: str | None = None
|
||||
fm_last_reinforced: str | None = None
|
||||
fm_redirect_to: str | None = None # if present, this is a merged-stub placeholder
|
||||
fm_canonical_id: str | None = None
|
||||
body_chars: int = 0
|
||||
body_first_para_hash: str = ""
|
||||
body_fingerprint: str = "" # short normalized excerpt for similarity
|
||||
is_stub: bool = False # body < 200 chars
|
||||
is_huge: bool = False # body > 50 KB
|
||||
is_redirect: bool = False # already-merged redirect placeholder
|
||||
is_operational: bool = False # under sessions/, _agents/, etc — excluded from clustering
|
||||
|
||||
|
||||
def normalize(s: str) -> str:
|
||||
"""Aggressive normalization for fuzzy match."""
|
||||
if not s:
|
||||
return ""
|
||||
s = unicodedata.normalize("NFKC", s).lower()
|
||||
s = NONALNUM_RE.sub("", s)
|
||||
return s
|
||||
|
||||
|
||||
def parse_frontmatter(text: str) -> tuple[dict, str]:
|
||||
"""Cheap YAML-ish parser. Tolerates the malformed [[wiki-link]] tags
|
||||
and other quirks present in this wiki — no PyYAML dependency."""
|
||||
m = FRONTMATTER_RE.match(text)
|
||||
if not m:
|
||||
return {}, text
|
||||
raw = m.group(1)
|
||||
body = text[m.end():]
|
||||
fm: dict = {}
|
||||
current_key: str | None = None
|
||||
for line in raw.splitlines():
|
||||
if not line.strip() or line.lstrip().startswith("#"):
|
||||
continue
|
||||
if line.startswith((" ", "\t")) and current_key:
|
||||
fm[current_key] = (str(fm.get(current_key, "")) + " " + line.strip()).strip()
|
||||
continue
|
||||
if ":" not in line:
|
||||
continue
|
||||
key, _, val = line.partition(":")
|
||||
key = key.strip()
|
||||
val = val.strip()
|
||||
# list form
|
||||
if val.startswith("[") and val.endswith("]"):
|
||||
inner = val[1:-1].strip()
|
||||
items = []
|
||||
for it in re.split(r",(?![^\[]*\])", inner):
|
||||
it = it.strip().strip("'\"")
|
||||
# strip [[wiki-link]] decoration to bare alias
|
||||
wm = WIKI_LINK_RE.fullmatch(it)
|
||||
if wm:
|
||||
it = wm.group(1)
|
||||
if it:
|
||||
items.append(it)
|
||||
fm[key] = items
|
||||
else:
|
||||
fm[key] = val.strip("'\"")
|
||||
current_key = key
|
||||
return fm, body
|
||||
|
||||
|
||||
def first_h1(body: str) -> str | None:
|
||||
for line in body.splitlines():
|
||||
if line.startswith("# ") and not line.startswith("##"):
|
||||
return line[2:].strip().lstrip("[").rstrip("]").split("|")[0].strip()
|
||||
return None
|
||||
|
||||
|
||||
def fingerprint_body(body: str, max_chars: int = 600) -> str:
|
||||
"""Strip frontmatter/headings/code/links, lowercase, collapse whitespace,
|
||||
take leading max_chars. Used for SequenceMatcher similarity."""
|
||||
b = CODE_BLOCK_RE.sub(" ", body)
|
||||
b = HEADING_RE.sub(" ", b)
|
||||
b = WIKI_LINK_RE.sub(lambda m: m.group(1), b)
|
||||
b = re.sub(r"[*_`>#\-]+", " ", b)
|
||||
b = WHITESPACE_RE.sub(" ", b).strip().lower()
|
||||
return b[:max_chars]
|
||||
|
||||
|
||||
def first_para_hash(body: str) -> str:
|
||||
fp = fingerprint_body(body, 400)
|
||||
if not fp:
|
||||
return ""
|
||||
return hashlib.sha1(fp.encode("utf-8")).hexdigest()[:12]
|
||||
|
||||
|
||||
def iter_md_files(root: Path) -> Iterable[Path]:
|
||||
for dirpath, dirs, files in os.walk(root):
|
||||
dirs[:] = [d for d in dirs if d not in SKIP_DIR_NAMES]
|
||||
for f in files:
|
||||
if f.endswith(".md"):
|
||||
yield Path(dirpath) / f
|
||||
|
||||
|
||||
def scan() -> list[FileEntry]:
|
||||
entries: list[FileEntry] = []
|
||||
for p in iter_md_files(TOPICS):
|
||||
try:
|
||||
text = p.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError as e:
|
||||
print(f"WARN read fail {p}: {e}", file=sys.stderr)
|
||||
continue
|
||||
fm, body = parse_frontmatter(text)
|
||||
filename = p.stem
|
||||
title = first_h1(body) or filename
|
||||
body_strip = body.strip()
|
||||
redirect_to = fm.get("redirect_to")
|
||||
if isinstance(redirect_to, list):
|
||||
redirect_to = redirect_to[0] if redirect_to else None
|
||||
is_redirect = bool(redirect_to) or (
|
||||
title.strip().lower() == "redirect" and len(body_strip) < 400
|
||||
)
|
||||
rel_path = str(p.relative_to(ROOT)).replace("\\", "/")
|
||||
is_operational = any(frag in "/" + rel_path for frag in EXCLUDE_PATH_FRAGMENTS)
|
||||
e = FileEntry(
|
||||
path=rel_path,
|
||||
abs_path=str(p),
|
||||
folder=p.parent.name,
|
||||
filename=filename,
|
||||
norm_name=normalize(filename),
|
||||
title=title,
|
||||
norm_title=normalize(title),
|
||||
fm_id=str(fm.get("id")) if fm.get("id") else None,
|
||||
fm_aliases=fm.get("aliases", []) if isinstance(fm.get("aliases"), list) else [],
|
||||
fm_tags=fm.get("tags", []) if isinstance(fm.get("tags"), list) else [],
|
||||
fm_status=fm.get("status"),
|
||||
fm_trust=fm.get("source_trust_level"),
|
||||
fm_last_reinforced=fm.get("last_reinforced"),
|
||||
fm_redirect_to=str(redirect_to) if redirect_to else None,
|
||||
fm_canonical_id=str(fm.get("canonical_id")) if fm.get("canonical_id") else None,
|
||||
body_chars=len(body_strip),
|
||||
body_first_para_hash=first_para_hash(body_strip),
|
||||
body_fingerprint=fingerprint_body(body_strip, 600),
|
||||
is_stub=len(body_strip) < 200,
|
||||
is_huge=len(body_strip) > 50000,
|
||||
is_redirect=is_redirect,
|
||||
is_operational=is_operational,
|
||||
)
|
||||
entries.append(e)
|
||||
return entries
|
||||
|
||||
|
||||
def build_clusters(entries: list[FileEntry]) -> list[list[FileEntry]]:
|
||||
"""Union-find by exact-match channels: norm_name, norm_title, body_first_para_hash.
|
||||
|
||||
Redirect placeholders are NEVER unioned via body fingerprint (they all share
|
||||
the same boilerplate, which would create a giant false-positive cluster).
|
||||
They're still unioned via norm_name / norm_title so that a redirect and its
|
||||
canonical document end up in the same cluster — that's the relationship we
|
||||
want to surface.
|
||||
"""
|
||||
parent = list(range(len(entries)))
|
||||
|
||||
def find(x: int) -> int:
|
||||
while parent[x] != x:
|
||||
parent[x] = parent[parent[x]]
|
||||
x = parent[x]
|
||||
return x
|
||||
|
||||
def union(a: int, b: int) -> None:
|
||||
ra, rb = find(a), find(b)
|
||||
if ra != rb:
|
||||
parent[ra] = rb
|
||||
|
||||
by_name: dict[str, list[int]] = defaultdict(list)
|
||||
by_title: dict[str, list[int]] = defaultdict(list)
|
||||
by_hash: dict[str, list[int]] = defaultdict(list)
|
||||
|
||||
for i, e in enumerate(entries):
|
||||
if e.is_operational:
|
||||
continue # session/agent runtime files: not knowledge candidates
|
||||
if e.norm_name:
|
||||
by_name[e.norm_name].append(i)
|
||||
if e.norm_title and not e.is_redirect: # redirects all titled "Redirect"
|
||||
by_title[e.norm_title].append(i)
|
||||
# body fingerprint: only meaningful, non-redirect, non-stub bodies
|
||||
if (
|
||||
e.body_first_para_hash
|
||||
and len(e.body_fingerprint) >= 200
|
||||
and not e.is_redirect
|
||||
and not e.is_stub
|
||||
):
|
||||
by_hash[e.body_first_para_hash].append(i)
|
||||
|
||||
for group in list(by_name.values()) + list(by_title.values()) + list(by_hash.values()):
|
||||
if len(group) > 1:
|
||||
for i in group[1:]:
|
||||
union(group[0], i)
|
||||
|
||||
clusters_map: dict[int, list[int]] = defaultdict(list)
|
||||
for i in range(len(entries)):
|
||||
clusters_map[find(i)].append(i)
|
||||
|
||||
clusters = [[entries[i] for i in idxs] for idxs in clusters_map.values() if len(idxs) > 1]
|
||||
# sort: largest cluster first, then by first member's title
|
||||
clusters.sort(key=lambda c: (-len(c), c[0].norm_title or c[0].norm_name))
|
||||
return clusters
|
||||
|
||||
|
||||
def cluster_similarity(c: list[FileEntry]) -> dict:
|
||||
"""Compute pairwise body fingerprint similarity within a cluster.
|
||||
Returns max/min/avg similarity and the dominant tier."""
|
||||
if len(c) < 2:
|
||||
return {"max": 1.0, "min": 1.0, "avg": 1.0, "tier": "solo"}
|
||||
sims: list[float] = []
|
||||
for i in range(len(c)):
|
||||
for j in range(i + 1, len(c)):
|
||||
a = c[i].body_fingerprint
|
||||
b = c[j].body_fingerprint
|
||||
if not a or not b:
|
||||
sims.append(0.5)
|
||||
continue
|
||||
sims.append(SequenceMatcher(None, a, b).ratio())
|
||||
if not sims:
|
||||
return {"max": 0.0, "min": 0.0, "avg": 0.0, "tier": "unknown"}
|
||||
mx, mn = max(sims), min(sims)
|
||||
avg = sum(sims) / len(sims)
|
||||
tier = (
|
||||
"near-dup (>=0.92)" if mx >= 0.92
|
||||
else "duplicate-candidate (0.80-0.92)" if mx >= 0.80
|
||||
else "related (0.65-0.80)" if mx >= 0.65
|
||||
else "weak-link (<0.65)"
|
||||
)
|
||||
return {"max": round(mx, 3), "min": round(mn, 3), "avg": round(avg, 3), "tier": tier}
|
||||
|
||||
|
||||
def write_index(entries: list[FileEntry]) -> None:
|
||||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
with INDEX_JSON.open("w", encoding="utf-8") as f:
|
||||
json.dump([asdict(e) for e in entries], f, ensure_ascii=False, indent=1)
|
||||
|
||||
|
||||
def write_clusters(clusters: list[list[FileEntry]], stats_per_cluster: list[dict]) -> None:
|
||||
payload = []
|
||||
for c, s in zip(clusters, stats_per_cluster):
|
||||
payload.append({
|
||||
"size": len(c),
|
||||
"stats": s,
|
||||
"members": [
|
||||
{
|
||||
"path": e.path,
|
||||
"folder": e.folder,
|
||||
"filename": e.filename,
|
||||
"title": e.title,
|
||||
"body_chars": e.body_chars,
|
||||
"fm_trust": e.fm_trust,
|
||||
"fm_last_reinforced": e.fm_last_reinforced,
|
||||
"is_stub": e.is_stub,
|
||||
} for e in c
|
||||
],
|
||||
})
|
||||
with CLUSTERS_JSON.open("w", encoding="utf-8") as f:
|
||||
json.dump(payload, f, ensure_ascii=False, indent=1)
|
||||
|
||||
|
||||
def write_report(entries: list[FileEntry], clusters: list[list[FileEntry]], stats: list[dict]) -> None:
|
||||
n_files = len(entries)
|
||||
n_clustered = sum(len(c) for c in clusters)
|
||||
n_stub = sum(1 for e in entries if e.is_stub)
|
||||
n_huge = sum(1 for e in entries if e.is_huge)
|
||||
n_redirect = sum(1 for e in entries if e.is_redirect)
|
||||
n_operational = sum(1 for e in entries if e.is_operational)
|
||||
near_dup = [c for c, s in zip(clusters, stats) if s["max"] >= 0.92]
|
||||
dup_cand = [c for c, s in zip(clusters, stats) if 0.80 <= s["max"] < 0.92]
|
||||
related = [c for c, s in zip(clusters, stats) if 0.65 <= s["max"] < 0.80]
|
||||
|
||||
folder_dup_pairs: dict[tuple[str, str], int] = defaultdict(int)
|
||||
for c in clusters:
|
||||
folders = sorted({e.folder for e in c})
|
||||
if len(folders) >= 2:
|
||||
for i in range(len(folders)):
|
||||
for j in range(i + 1, len(folders)):
|
||||
folder_dup_pairs[(folders[i], folders[j])] += 1
|
||||
|
||||
lines: list[str] = []
|
||||
lines.append("# Duplicate Candidates (P-Reinforce Phase 1 Index)\n")
|
||||
lines.append("> 자동 생성. 이 보고서는 **변경 제안**일 뿐 실제 파일은 수정되지 않았다.\n")
|
||||
lines.append("> 사용자가 클러스터별로 검토하고 MERGE/UPDATE/CREATE/REJECT 판단을 내려야 한다.\n")
|
||||
lines.append("")
|
||||
lines.append("## 요약\n")
|
||||
lines.append(f"- 총 파일: **{n_files}**")
|
||||
lines.append(f"- 중복 후보 클러스터에 포함된 파일: **{n_clustered}**")
|
||||
lines.append(f"- 클러스터 수: **{len(clusters)}** (>=0.92 near-dup: {len(near_dup)}, 0.80-0.92 dup-cand: {len(dup_cand)}, 0.65-0.80 related: {len(related)})")
|
||||
lines.append(f"- 이미 merged (`redirect_to` 필드 보유): **{n_redirect}**")
|
||||
lines.append(f"- 운영 로그 (sessions/_agents/_company 등, 클러스터링 제외): **{n_operational}**")
|
||||
lines.append(f"- 지식 문서 후보 (총수 - 운영 로그): **{n_files - n_operational}**")
|
||||
lines.append(f"- 빈약 stub (<200 chars, redirect 제외): **{n_stub - n_redirect}**")
|
||||
lines.append(f"- 거대 문서 (>50KB): **{n_huge}**")
|
||||
lines.append("")
|
||||
|
||||
if folder_dup_pairs:
|
||||
lines.append("## 폴더 간 중복 핫스팟 (Top 20)\n")
|
||||
lines.append("| 폴더 A | 폴더 B | 공유 클러스터 |")
|
||||
lines.append("|---|---|---|")
|
||||
for (a, b), n in sorted(folder_dup_pairs.items(), key=lambda x: -x[1])[:20]:
|
||||
lines.append(f"| `{a}` | `{b}` | {n} |")
|
||||
lines.append("")
|
||||
|
||||
def emit_section(title: str, group: list[list[FileEntry]], group_stats: list[dict], cap: int = 80) -> None:
|
||||
if not group:
|
||||
return
|
||||
lines.append(f"## {title} (총 {len(group)})\n")
|
||||
if len(group) > cap:
|
||||
lines.append(f"> 상위 {cap}개만 표시. 전체는 `_clusters.json` 참조.\n")
|
||||
for c, s in list(zip(group, group_stats))[:cap]:
|
||||
head = c[0].title or c[0].filename
|
||||
lines.append(f"### `{head}` (members: {len(c)}, max_sim: {s['max']}, tier: {s['tier']})")
|
||||
for e in c:
|
||||
stub_tag = " *[stub]*" if e.is_stub else ""
|
||||
huge_tag = " *[huge]*" if e.is_huge else ""
|
||||
lr = e.fm_last_reinforced or "?"
|
||||
trust = e.fm_trust or "?"
|
||||
lines.append(f"- [{e.path}]({e.path}) — {e.body_chars} chars, trust={trust}, last={lr}{stub_tag}{huge_tag}")
|
||||
lines.append("")
|
||||
|
||||
pairs = list(zip(clusters, stats))
|
||||
emit_section("🔴 Near-duplicate (>=0.92) — UPDATE 권장", [c for c, s in pairs if s["max"] >= 0.92], [s for c, s in pairs if s["max"] >= 0.92])
|
||||
emit_section("🟡 Duplicate candidate (0.80-0.92) — 검토 필요", [c for c, s in pairs if 0.80 <= s["max"] < 0.92], [s for c, s in pairs if 0.80 <= s["max"] < 0.92])
|
||||
emit_section("🟢 Related (0.65-0.80) — 연결만 권장", [c for c, s in pairs if 0.65 <= s["max"] < 0.80], [s for c, s in pairs if 0.65 <= s["max"] < 0.80])
|
||||
emit_section("⚪ Weak-link (<0.65) — 동명/동일 hash지만 내용 다름", [c for c, s in pairs if s["max"] < 0.65], [s for c, s in pairs if s["max"] < 0.65])
|
||||
|
||||
REPORT_MD.write_text("\n".join(lines), encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print(f"[1/4] Scanning {TOPICS} ...", file=sys.stderr)
|
||||
entries = scan()
|
||||
print(f" {len(entries)} files indexed", file=sys.stderr)
|
||||
|
||||
print(f"[2/4] Writing per-file index -> {INDEX_JSON}", file=sys.stderr)
|
||||
write_index(entries)
|
||||
|
||||
print(f"[3/4] Building duplicate clusters ...", file=sys.stderr)
|
||||
clusters = build_clusters(entries)
|
||||
stats = [cluster_similarity(c) for c in clusters]
|
||||
print(f" {len(clusters)} clusters with >=2 members", file=sys.stderr)
|
||||
|
||||
print(f"[4/4] Writing report -> {REPORT_MD}", file=sys.stderr)
|
||||
write_clusters(clusters, stats)
|
||||
write_report(entries, clusters, stats)
|
||||
print("DONE.", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,254 @@
|
||||
"""
|
||||
P-Reinforce Phase 2 — Auto-MERGE same-concept filename-variant clusters.
|
||||
|
||||
Scope (conservative — only the safest auto-merge cases):
|
||||
Within each cluster, only merge if EVERY member shares the same
|
||||
`norm_name` (case-insensitive, punctuation-stripped). This catches:
|
||||
Bellman Equation.md / Bellman-Equation.md / Bellman_Equation.md
|
||||
Best-of-N Sampling.md / Best-of-N-Sampling.md / Best-of-N_Sampling.md
|
||||
Computer Vision.md / Computer-Vision.md / Computer_Vision.md
|
||||
These are virtually always the same concept under different naming
|
||||
conventions. Cross-folder is OK (same norm_name across AI/ vs AI_and_ML/).
|
||||
|
||||
Skipped automatically:
|
||||
- Clusters where members have DIFFERENT norm_names (i.e. unioned only by
|
||||
body fingerprint — could be coincidence).
|
||||
- Clusters with mixed redirects + canonical (already partly merged).
|
||||
- Operational paths (sessions/, _agents/, etc).
|
||||
|
||||
Canonical selection rule per cluster:
|
||||
1. Highest body_chars (most content wins) ...
|
||||
2. ... ties broken by latest last_reinforced ...
|
||||
3. ... ties broken by lex-shortest filename (stability).
|
||||
|
||||
Action per non-canonical member:
|
||||
- If it already has redirect_to — leave it alone (already merged).
|
||||
- Otherwise rewrite to a redirect stub pointing at canonical.
|
||||
- Move to 01_Archive/MERGED/<date>/<original-relative-path>
|
||||
|
||||
Outputs:
|
||||
20_Meta/ReviewQueue/auto_merge_log.md - log of every cluster decision
|
||||
01_Archive/MERGED/<YYYY-MM-DD>/... - moved-out original files
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(r"E:/Wiki/2nd")
|
||||
INDEX_JSON = ROOT / "20_Meta" / "ReviewQueue" / "_index.json"
|
||||
CLUSTERS_JSON = ROOT / "20_Meta" / "ReviewQueue" / "_clusters.json"
|
||||
LOG_MD = ROOT / "20_Meta" / "ReviewQueue" / "auto_merge_log.md"
|
||||
ARCHIVE_BASE = ROOT / "01_Archive" / "MERGED"
|
||||
|
||||
EXCLUDE_FRAG = (
|
||||
"/sessions/", "/_agents/", "/_company/", "/memory/",
|
||||
"/Project_Logs/", "/Harness_Research_", "/docs/records/",
|
||||
"/_Archive_Orphans/", "/Post_Drafts/", "/UX_Scenarios/",
|
||||
)
|
||||
|
||||
|
||||
def is_operational(rel_path: str) -> bool:
|
||||
rel = "/" + rel_path.replace("\\", "/")
|
||||
return any(x in rel for x in EXCLUDE_FRAG)
|
||||
|
||||
|
||||
def load_clusters() -> list[dict]:
|
||||
return json.loads(CLUSTERS_JSON.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def load_index() -> dict[str, dict]:
|
||||
arr = json.loads(INDEX_JSON.read_text(encoding="utf-8"))
|
||||
return {e["path"]: e for e in arr}
|
||||
|
||||
|
||||
def pick_canonical(members: list[dict], idx: dict[str, dict]) -> dict:
|
||||
def keyfn(m: dict) -> tuple:
|
||||
e = idx.get(m["path"], {})
|
||||
return (
|
||||
-e.get("body_chars", 0),
|
||||
-1 * (1 if e.get("fm_last_reinforced") else 0), # prefer files that have a date
|
||||
(e.get("fm_last_reinforced") or "0000-00-00") * -1 if False else (e.get("fm_last_reinforced") or "0000-00-00"),
|
||||
len(m["filename"]),
|
||||
m["filename"],
|
||||
)
|
||||
# explicit: largest body, then most-recent last_reinforced (later date wins),
|
||||
# then shortest filename, then lexicographic
|
||||
def sort_key(m: dict) -> tuple:
|
||||
e = idx.get(m["path"], {})
|
||||
return (
|
||||
-e.get("body_chars", 0), # bigger first
|
||||
"0000-00-00" if not e.get("fm_last_reinforced") else _neg_date(e["fm_last_reinforced"]),
|
||||
len(m["filename"]),
|
||||
m["filename"],
|
||||
)
|
||||
return sorted(members, key=sort_key)[0]
|
||||
|
||||
|
||||
def _neg_date(d: str) -> str:
|
||||
# Map YYYY-MM-DD to a string that sorts later-dates-first when sorted ascending.
|
||||
parts = d.split("-")
|
||||
if len(parts) != 3:
|
||||
return "ZZZZ"
|
||||
try:
|
||||
y = 9999 - int(parts[0])
|
||||
m = 99 - int(parts[1])
|
||||
day = 99 - int(parts[2])
|
||||
return f"{y:04d}-{m:02d}-{day:02d}"
|
||||
except ValueError:
|
||||
return "ZZZZ"
|
||||
|
||||
|
||||
REDIRECT_TEMPLATE = """---
|
||||
id: {id}
|
||||
title: {title}
|
||||
category: {category}
|
||||
status: merged
|
||||
redirect_to: {target}
|
||||
canonical_id: {target}
|
||||
aliases: []
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [redirect]
|
||||
raw_sources: []
|
||||
last_reinforced: {today}
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-merge 2026-05-08)
|
||||
---
|
||||
|
||||
# {title}
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 이 문서는 P-Reinforce Phase 2 자동 MERGE에 의해 **[[{target}]]**로 통합되었습니다.
|
||||
|
||||
---
|
||||
*Redirected to: [[{target}]]*
|
||||
"""
|
||||
|
||||
|
||||
def make_redirect_stub(member: dict, canonical: dict, today: str) -> str:
|
||||
title = member["filename"].replace("-", " ").replace("_", " ")
|
||||
target = canonical["filename"].replace("-", " ").replace("_", " ").replace(" ", "_")
|
||||
# use canonical filename (without ext, with underscores) as wiki-link target
|
||||
target_clean = canonical["filename"]
|
||||
return REDIRECT_TEMPLATE.format(
|
||||
id=f"wiki-{today.replace('-', '')[:8]}-{re.sub(r'[^a-z0-9]+', '-', title.lower())[:32]}-redir",
|
||||
title=title,
|
||||
category=member.get("folder") or "10_Wiki/Topics",
|
||||
target=target_clean,
|
||||
today=today,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MergePlan:
|
||||
cluster_id: int
|
||||
canonical_path: str
|
||||
losers: list[str]
|
||||
norm_name_set: set[str]
|
||||
|
||||
|
||||
def plan_merges(clusters: list[dict], idx: dict[str, dict]) -> tuple[list[MergePlan], list[dict]]:
|
||||
plans: list[MergePlan] = []
|
||||
skipped: list[dict] = []
|
||||
for cid, c in enumerate(clusters):
|
||||
size = c["size"]
|
||||
members = c["members"]
|
||||
if size < 2:
|
||||
continue
|
||||
|
||||
# Filter out operational paths
|
||||
if any(is_operational(m["path"]) for m in members):
|
||||
skipped.append({"cluster_id": cid, "reason": "operational-path", "members": [m["path"] for m in members]})
|
||||
continue
|
||||
|
||||
# Need every member to share norm_name
|
||||
norm_names = set()
|
||||
for m in members:
|
||||
e = idx.get(m["path"], {})
|
||||
norm_names.add(e.get("norm_name", ""))
|
||||
if len(norm_names) != 1 or "" in norm_names:
|
||||
skipped.append({"cluster_id": cid, "reason": "norm-name-mismatch", "norm_names": list(norm_names), "members": [m["path"] for m in members]})
|
||||
continue
|
||||
|
||||
# Drop any members already in MERGED archive (shouldn't happen but safe)
|
||||
members = [m for m in members if "01_Archive/" not in m["path"]]
|
||||
if len(members) < 2:
|
||||
continue
|
||||
|
||||
canonical = pick_canonical(members, idx)
|
||||
losers = [m for m in members if m["path"] != canonical["path"]]
|
||||
plans.append(MergePlan(cluster_id=cid, canonical_path=canonical["path"], losers=[l["path"] for l in losers], norm_name_set=norm_names))
|
||||
return plans, skipped
|
||||
|
||||
|
||||
def apply_plan(plans: list[MergePlan], idx: dict[str, dict]) -> dict:
|
||||
today = date.today().isoformat()
|
||||
archive_dir = ARCHIVE_BASE / today
|
||||
archive_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
n_merged = 0
|
||||
n_archived = 0
|
||||
log_lines: list[str] = []
|
||||
log_lines.append(f"# Auto-merge log — {today}\n")
|
||||
log_lines.append(f"\n총 plan: **{len(plans)}** clusters\n")
|
||||
|
||||
for plan in plans:
|
||||
canonical_abs = ROOT / plan.canonical_path
|
||||
log_lines.append(f"\n## Cluster {plan.cluster_id} — canonical: `{plan.canonical_path}`")
|
||||
log_lines.append(f"\n- **norm_name**: `{list(plan.norm_name_set)[0]}`")
|
||||
log_lines.append(f"- **canonical** (kept): [{plan.canonical_path}](/{plan.canonical_path})")
|
||||
log_lines.append(f"- **merged-into-redirect**:")
|
||||
for loser_rel in plan.losers:
|
||||
loser_abs = ROOT / loser_rel
|
||||
if not loser_abs.exists():
|
||||
log_lines.append(f" - ~~{loser_rel}~~ (already missing — skipped)")
|
||||
continue
|
||||
# Construct redirect stub pointing at canonical
|
||||
member = {"filename": loser_abs.stem, "path": loser_rel,
|
||||
"folder": loser_abs.parent.name}
|
||||
canonical = {"filename": canonical_abs.stem, "path": plan.canonical_path}
|
||||
stub_text = make_redirect_stub(member, canonical, today)
|
||||
|
||||
# Move original to archive (preserving relative path)
|
||||
archive_target = archive_dir / loser_rel
|
||||
archive_target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(str(loser_abs), str(archive_target))
|
||||
n_archived += 1
|
||||
# Write redirect stub at original location (so wiki-links don't break)
|
||||
loser_abs.write_text(stub_text, encoding="utf-8")
|
||||
n_merged += 1
|
||||
log_lines.append(f" - {loser_rel} → archived to `01_Archive/MERGED/{today}/{loser_rel}`, replaced with redirect → `{canonical_abs.stem}`")
|
||||
|
||||
LOG_MD.write_text("\n".join(log_lines), encoding="utf-8")
|
||||
return {"merged": n_merged, "archived": n_archived, "plans": len(plans)}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not CLUSTERS_JSON.exists() or not INDEX_JSON.exists():
|
||||
print(f"ERROR: run p_reinforce_index.py first", file=sys.stderr)
|
||||
return 2
|
||||
clusters = load_clusters()
|
||||
idx = load_index()
|
||||
plans, skipped = plan_merges(clusters, idx)
|
||||
print(f"Planned {len(plans)} merge clusters, skipped {len(skipped)}", file=sys.stderr)
|
||||
if "--dry-run" in sys.argv:
|
||||
for p in plans[:30]:
|
||||
print(f" cluster {p.cluster_id}: keep {p.canonical_path}, redirect {len(p.losers)}", file=sys.stderr)
|
||||
return 0
|
||||
result = apply_plan(plans, idx)
|
||||
print(f"DONE: merged={result['merged']}, archived={result['archived']}", file=sys.stderr)
|
||||
print(f"Log: {LOG_MD}", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,631 @@
|
||||
"""
|
||||
P-Reinforce Phase 1 — Template Normalizer (DRY-RUN by default)
|
||||
==============================================================
|
||||
Reads a wiki .md file and emits a normalized version that conforms to the
|
||||
current P-Reinforce template (templates/wiki_document.md).
|
||||
|
||||
What it does mechanically (NO LLM calls):
|
||||
1. Cleans frontmatter:
|
||||
- Strips [[wiki-link]] decoration from id/category/tags
|
||||
- Adds missing fields: canonical_id, aliases, status,
|
||||
source_trust_level, raw_sources, duplicate_of, tech_stack (if tech)
|
||||
- Re-derives source_trust_level from confidence_score if missing
|
||||
- Preserves the original id under `legacy_id` and `aliases`
|
||||
2. Renames legacy section headers to current template:
|
||||
- "Brief Summary" -> "📌 한 줄 통찰 (The Karpathy Summary)"
|
||||
- "Core Content" -> "📖 구조화된 지식 (Synthesized Content)"
|
||||
- "Trade-offs & Caveats" -> merged into "⚠️ 모순 및 업데이트"
|
||||
- "Knowledge Connections" -> "🔗 지식 연결 (Graph)"
|
||||
3. Adds missing required sections as scaffold (with TODO markers)
|
||||
so LLM can fill them later. Tech docs get Code Patterns / Decision
|
||||
Criteria / Anti-Patterns scaffolds.
|
||||
4. For stubs (<200 body chars), inserts a `🤖 [AI 추론 보강 필요]`
|
||||
block — does NOT generate content (that step is interactive).
|
||||
|
||||
Usage:
|
||||
python p_reinforce_normalize.py <relative_path> # dry-run, prints diff
|
||||
python p_reinforce_normalize.py <relative_path> --out PATH # write to PATH
|
||||
python p_reinforce_normalize.py --batch <input_listing> # multi-file dry-run
|
||||
|
||||
Default mode is DRY-RUN. No source files are modified.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(r"E:/Wiki/2nd")
|
||||
TOPICS = ROOT / "10_Wiki" / "Topics"
|
||||
|
||||
FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n?", re.DOTALL)
|
||||
WIKI_LINK_RE = re.compile(r"\[\[([^\]|]+?)(?:\|[^\]]+)?\]\]")
|
||||
H1_RE = re.compile(r"^# +(.+)$", re.MULTILINE)
|
||||
HEADING_RE = re.compile(r"^(#{2,6})\s+(.+?)\s*$", re.MULTILINE)
|
||||
CODE_FENCE_RE = re.compile(r"```([a-zA-Z0-9_-]*)\n", re.MULTILINE)
|
||||
|
||||
# Header rename map: matched against the heading text only (after stripping
|
||||
# leading emoji/punctuation). Keys are normalized lowercase.
|
||||
HEADER_RENAMES = {
|
||||
"brief summary": "📌 한 줄 통찰 (The Karpathy Summary)",
|
||||
"the karpathy summary": "📌 한 줄 통찰 (The Karpathy Summary)",
|
||||
"한 줄 통찰": "📌 한 줄 통찰 (The Karpathy Summary)",
|
||||
"한 줄 통찰 (the karpathy summary)": "📌 한 줄 통찰 (The Karpathy Summary)",
|
||||
"core content": "📖 구조화된 지식 (Synthesized Content)",
|
||||
"synthesized content": "📖 구조화된 지식 (Synthesized Content)",
|
||||
"구조화된 지식": "📖 구조화된 지식 (Synthesized Content)",
|
||||
"구조화된 지식 (synthesized content)": "📖 구조화된 지식 (Synthesized Content)",
|
||||
"trade-offs & caveats": "⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"tradeoffs & caveats": "⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"trade-offs and caveats": "⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"contradictions & rl update": "⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"contradictions & updates": "⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"rl update": "⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"모순 및 업데이트": "⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"모순 및 업데이트 (contradictions & updates)": "⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"모순 및 업데이트 (contradictions & rl update)": "⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"knowledge connections": "🔗 지식 연결 (Graph)",
|
||||
"graph": "🔗 지식 연결 (Graph)",
|
||||
"지식 연결": "🔗 지식 연결 (Graph)",
|
||||
"지식 연결 (graph)": "🔗 지식 연결 (Graph)",
|
||||
"code patterns": "💻 코드 패턴 (Code Patterns)",
|
||||
"코드 패턴": "💻 코드 패턴 (Code Patterns)",
|
||||
"decision criteria": "🤔 의사결정 기준 (Decision Criteria)",
|
||||
"의사결정 기준": "🤔 의사결정 기준 (Decision Criteria)",
|
||||
"anti-patterns": "❌ 안티패턴 (Anti-Patterns)",
|
||||
"antipatterns": "❌ 안티패턴 (Anti-Patterns)",
|
||||
"안티패턴": "❌ 안티패턴 (Anti-Patterns)",
|
||||
"how to use this knowledge": "🤖 LLM 활용 힌트 (How to Use This Knowledge)",
|
||||
"llm 활용 힌트": "🤖 LLM 활용 힌트 (How to Use This Knowledge)",
|
||||
"validation": "🧪 검증 상태 (Validation)",
|
||||
"검증 상태": "🧪 검증 상태 (Validation)",
|
||||
"duplicate check": "🧬 중복 검사 (Duplicate Check)",
|
||||
"중복 검사": "🧬 중복 검사 (Duplicate Check)",
|
||||
"changelog": "🕓 변경 이력 (Changelog)",
|
||||
"변경 이력": "🕓 변경 이력 (Changelog)",
|
||||
}
|
||||
|
||||
EMOJI_AT_START_RE = re.compile(r"^[^\w가-힣]*", re.UNICODE)
|
||||
|
||||
# folder -> category mapping (best-effort; not authoritative)
|
||||
FOLDER_CATEGORY_HINTS = {
|
||||
"AI": "10_Wiki/Topics",
|
||||
"AI_and_ML": "10_Wiki/Topics",
|
||||
"Architecture": "10_Wiki/Topics",
|
||||
"Backend": "10_Wiki/Topics",
|
||||
"Frontend": "10_Wiki/Topics",
|
||||
"Frontend_Mastery": "10_Wiki/Topics",
|
||||
"DevOps_and_Security": "10_Wiki/Topics",
|
||||
"Computer_Science_and_Theory": "10_Wiki/Topics",
|
||||
"Programming & Language": "10_Wiki/Topics",
|
||||
"Programming & Tools": "10_Wiki/Topics",
|
||||
"Programming & Web": "10_Wiki/Topics",
|
||||
"Programming & Formal Methods": "10_Wiki/Topics",
|
||||
"Visual_Effects": "10_Wiki/Topics_Art",
|
||||
"Graphics & Performance": "10_Wiki/Topics_Art",
|
||||
"UI_UX_Assets": "10_Wiki/Topics_Art",
|
||||
"Design & Experience": "10_Wiki/Topics_Art",
|
||||
"Game Design": "10_Wiki/Topics_GD",
|
||||
"Game_Design": "10_Wiki/Topics_GD",
|
||||
"Level_Design": "10_Wiki/Topics_GD",
|
||||
"Balancing": "10_Wiki/Topics_GD",
|
||||
"Core_Systems": "10_Wiki/Topics_GD",
|
||||
"Skybound": "10_Wiki/Topics_GD",
|
||||
"Storytelling": "10_Wiki/Topics_GD",
|
||||
"Economics": "10_Wiki/Topics_Biz",
|
||||
"Economy": "10_Wiki/Topics_Biz",
|
||||
"Economics & Algorithms": "10_Wiki/Topics_Biz",
|
||||
"Business_Strategy": "10_Wiki/Topics_Biz",
|
||||
"Market_Research": "10_Wiki/Topics_Biz",
|
||||
"Partnerships": "10_Wiki/Topics_Biz",
|
||||
"Content_Strategy": "10_Wiki/Topics_Blog",
|
||||
"Post_Drafts": "10_Wiki/Topics_Blog",
|
||||
"External_Media": "10_Wiki/Topics_Blog",
|
||||
}
|
||||
|
||||
TECH_KEYWORDS = {
|
||||
"architecture", "algorithm", "programming", "code", "frontend", "backend",
|
||||
"compiler", "interpreter", "runtime", "framework", "api", "rest", "graphql",
|
||||
"kubernetes", "docker", "kafka", "fastapi", "react", "vue", "svelte", "next",
|
||||
"typescript", "python", "rust", "javascript", "go ", "java ", "c++", "swift",
|
||||
"database", "sql", "postgres", "redis", "mongo", "cache", "distributed",
|
||||
}
|
||||
|
||||
|
||||
def parse_frontmatter(text: str) -> tuple[dict, str, str]:
|
||||
"""Returns (fm_dict, body, fm_raw). Same forgiving parser as indexer."""
|
||||
m = FRONTMATTER_RE.match(text)
|
||||
if not m:
|
||||
return {}, text, ""
|
||||
raw = m.group(1)
|
||||
body = text[m.end():]
|
||||
fm: dict = {}
|
||||
current_key: str | None = None
|
||||
for line in raw.splitlines():
|
||||
if not line.strip() or line.lstrip().startswith("#"):
|
||||
continue
|
||||
if line.startswith((" ", "\t")) and current_key:
|
||||
existing = fm.get(current_key)
|
||||
fm[current_key] = (str(existing) + " " + line.strip()).strip() if existing else line.strip()
|
||||
continue
|
||||
if ":" not in line:
|
||||
continue
|
||||
key, _, val = line.partition(":")
|
||||
key = key.strip()
|
||||
val = val.strip()
|
||||
if val.startswith("[") and val.endswith("]"):
|
||||
inner = val[1:-1].strip()
|
||||
items = []
|
||||
for it in re.split(r",(?![^\[]*\])", inner):
|
||||
it = it.strip().strip("'\"")
|
||||
wm = WIKI_LINK_RE.fullmatch(it)
|
||||
if wm:
|
||||
it = wm.group(1)
|
||||
if it:
|
||||
items.append(it)
|
||||
fm[key] = items
|
||||
else:
|
||||
# strip [[wiki-link|alias]] decoration if scalar
|
||||
wm = WIKI_LINK_RE.fullmatch(val.strip("'\"")) if val else None
|
||||
if wm:
|
||||
val = wm.group(1)
|
||||
fm[key] = val.strip("'\"")
|
||||
current_key = key
|
||||
return fm, body, raw
|
||||
|
||||
|
||||
def detect_tech(folder: str, tags: list[str], body: str) -> bool:
|
||||
haystack = (folder + " " + " ".join(tags) + " " + body[:2000]).lower()
|
||||
if CODE_FENCE_RE.search(body):
|
||||
return True
|
||||
return any(k in haystack for k in TECH_KEYWORDS)
|
||||
|
||||
|
||||
def trust_from_confidence(conf_str: str | None) -> str:
|
||||
"""Per-user policy (2026-05-08): LLM-augmented entries are graded A
|
||||
(with `inferred_by` metadata for traceability), not C as the default
|
||||
P-Reinforce skill suggests. The mapping below is therefore biased upward
|
||||
relative to the skill spec.
|
||||
"""
|
||||
if not conf_str:
|
||||
return "A" # user policy: trust the model
|
||||
try:
|
||||
c = float(conf_str)
|
||||
except (TypeError, ValueError):
|
||||
return "A"
|
||||
if c >= 0.95:
|
||||
return "A"
|
||||
if c >= 0.80:
|
||||
return "A"
|
||||
if c >= 0.65:
|
||||
return "B"
|
||||
return "C"
|
||||
|
||||
|
||||
# Markers in body text that indicate a redirect even without `redirect_to`
|
||||
# in the frontmatter (e.g. older P-Reinforce passes left text-only redirects).
|
||||
TEXT_REDIRECT_PATTERNS = [
|
||||
re.compile(r"\*?Redirected to:\s*\[\[([^\]|]+?)(?:\|[^\]]+)?\]\]\*?", re.IGNORECASE),
|
||||
re.compile(r"\[\[([^\]|]+?)(?:\|[^\]]+)?\]\]\s*(?:으|로|로)?\s*통합되었습니다"),
|
||||
re.compile(r"통합되었습니다.*?\[\[([^\]|]+?)(?:\|[^\]]+)?\]\]"),
|
||||
re.compile(r"이 문서는.*?\[\[([^\]|]+?)(?:\|[^\]]+)?\]\].*?로 통합"),
|
||||
]
|
||||
|
||||
|
||||
def detect_text_redirect(body: str) -> str | None:
|
||||
"""Look for "Redirected to: [[X]]" or "[[X]]로 통합되었습니다" in body.
|
||||
Returns the canonical target name if found, else None."""
|
||||
body_top = body[:1500] # only check the top of the doc
|
||||
for pat in TEXT_REDIRECT_PATTERNS:
|
||||
m = pat.search(body_top)
|
||||
if m:
|
||||
return m.group(1).strip()
|
||||
return None
|
||||
|
||||
|
||||
def slugify_id(filename: str, today: str | None = None) -> str:
|
||||
today = today or date.today().isoformat().replace("-", "")[:8]
|
||||
slug = unicodedata.normalize("NFKC", filename).lower()
|
||||
slug = re.sub(r"[^a-z0-9가-힣]+", "-", slug).strip("-")
|
||||
if not slug:
|
||||
slug = "doc"
|
||||
return f"wiki-{today[:4]}-{today[4:8]}-{slug[:32]}"
|
||||
|
||||
|
||||
def quote_yaml(s: str) -> str:
|
||||
if s is None:
|
||||
return '""'
|
||||
s = str(s)
|
||||
if any(ch in s for ch in [':', '#', '[', ']', '{', '}', ',', '&', '*', '!', '|', '>', "'", '"', '%', '@', '`']) or "\n" in s:
|
||||
return '"' + s.replace('"', '\\"') + '"'
|
||||
return s
|
||||
|
||||
|
||||
def build_frontmatter(fm: dict, file_path: Path, body: str) -> dict:
|
||||
"""Compute the new normalized frontmatter from old fm + path heuristics."""
|
||||
folder = file_path.parent.name
|
||||
filename = file_path.stem
|
||||
new_fm: dict = {}
|
||||
|
||||
# Detect text-only redirect (no redirect_to field but body says so)
|
||||
text_redirect = detect_text_redirect(body) if not fm.get("redirect_to") else None
|
||||
|
||||
# ID — preserve legacy id if it already looks like our slug
|
||||
legacy_id = fm.get("id")
|
||||
legacy_id_str = str(legacy_id) if legacy_id else ""
|
||||
if legacy_id_str.startswith("wiki-") and not WIKI_LINK_RE.search(legacy_id_str):
|
||||
new_id = legacy_id_str
|
||||
else:
|
||||
new_id = slugify_id(filename)
|
||||
new_fm["id"] = new_id
|
||||
|
||||
# title
|
||||
title = filename.replace("-", " ").replace("_", " ").strip()
|
||||
new_fm["title"] = title
|
||||
|
||||
# category — strip emoji-decorated old categories, normalize wiki-link form
|
||||
cat = fm.get("category")
|
||||
if cat in (None, "", "Unified") or (isinstance(cat, str) and cat.startswith("[[")):
|
||||
cat = FOLDER_CATEGORY_HINTS.get(folder, "10_Wiki/Topics")
|
||||
elif isinstance(cat, str) and "💡" in cat:
|
||||
# "10_Wiki/💡 Topics/AI" -> hinted category
|
||||
cat = FOLDER_CATEGORY_HINTS.get(folder, "10_Wiki/Topics")
|
||||
new_fm["category"] = cat
|
||||
|
||||
# If this file is a redirect, override status and add redirect_to.
|
||||
if fm.get("redirect_to") or text_redirect:
|
||||
target = fm.get("redirect_to") or text_redirect
|
||||
new_fm["redirect_to"] = target
|
||||
new_fm["status"] = "merged"
|
||||
new_fm["canonical_id"] = fm.get("canonical_id") or target # best-effort
|
||||
else:
|
||||
# status: draft for stubs, needs_review otherwise; verified only if pre-tagged
|
||||
existing_status = fm.get("status")
|
||||
if existing_status in ("verified", "merged", "deprecated"):
|
||||
new_fm["status"] = existing_status
|
||||
else:
|
||||
new_fm["status"] = "draft" if len(body.strip()) < 200 else "needs_review"
|
||||
new_fm["canonical_id"] = fm.get("canonical_id") or "self"
|
||||
|
||||
# aliases — preserve legacy id and any prior aliases
|
||||
aliases = fm.get("aliases") or []
|
||||
if isinstance(aliases, str):
|
||||
aliases = [aliases]
|
||||
if legacy_id and isinstance(legacy_id, str):
|
||||
legacy_id_clean = WIKI_LINK_RE.sub(r"\1", legacy_id)
|
||||
if legacy_id_clean and legacy_id_clean not in aliases:
|
||||
aliases = [legacy_id_clean] + aliases
|
||||
new_fm["aliases"] = aliases
|
||||
|
||||
# duplicate_of
|
||||
new_fm["duplicate_of"] = fm.get("duplicate_of") or "none"
|
||||
|
||||
# source_trust_level
|
||||
new_fm["source_trust_level"] = fm.get("source_trust_level") or trust_from_confidence(fm.get("confidence_score"))
|
||||
|
||||
# confidence_score — preserve if present, else policy default 0.92
|
||||
cs = fm.get("confidence_score")
|
||||
if cs:
|
||||
try:
|
||||
new_fm["confidence_score"] = float(cs)
|
||||
except (TypeError, ValueError):
|
||||
new_fm["confidence_score"] = 0.92
|
||||
else:
|
||||
new_fm["confidence_score"] = 0.92
|
||||
|
||||
# tags — strip wiki link decoration
|
||||
tags = fm.get("tags") or []
|
||||
if isinstance(tags, str):
|
||||
tags = [tags]
|
||||
cleaned_tags = []
|
||||
for t in tags:
|
||||
if isinstance(t, str):
|
||||
t = WIKI_LINK_RE.sub(r"\1", t).strip("[]'\" ")
|
||||
if t:
|
||||
cleaned_tags.append(t)
|
||||
new_fm["tags"] = cleaned_tags or ["uncategorized"]
|
||||
|
||||
# raw_sources
|
||||
rs = fm.get("raw_sources") or []
|
||||
if isinstance(rs, str):
|
||||
rs = [rs]
|
||||
new_fm["raw_sources"] = rs
|
||||
|
||||
# last_reinforced
|
||||
new_fm["last_reinforced"] = fm.get("last_reinforced") or date.today().isoformat()
|
||||
|
||||
# github_commit
|
||||
new_fm["github_commit"] = fm.get("github_commit") or "pending"
|
||||
|
||||
# inferred_by — traceability for LLM-augmented entries (per user policy)
|
||||
if fm.get("inferred_by"):
|
||||
new_fm["inferred_by"] = fm["inferred_by"]
|
||||
elif new_fm["source_trust_level"] == "A" and not fm.get("source_trust_level"):
|
||||
# we just promoted this to A; record provenance
|
||||
new_fm["inferred_by"] = "Claude Opus 4.7 (auto-normalize 2026-05-08)"
|
||||
|
||||
# tech_stack — only if detected
|
||||
if detect_tech(folder, cleaned_tags, body):
|
||||
ts_old = fm.get("tech_stack")
|
||||
if isinstance(ts_old, dict):
|
||||
new_fm["tech_stack"] = ts_old
|
||||
else:
|
||||
new_fm["tech_stack"] = {"language": "unspecified", "framework": "unspecified"}
|
||||
|
||||
return new_fm
|
||||
|
||||
|
||||
def render_frontmatter(fm: dict) -> str:
|
||||
lines = ["---"]
|
||||
order = [
|
||||
"id", "title", "category", "status", "redirect_to", "canonical_id", "aliases",
|
||||
"duplicate_of", "source_trust_level", "confidence_score",
|
||||
"tags", "raw_sources", "last_reinforced", "github_commit",
|
||||
"inferred_by", "tech_stack",
|
||||
]
|
||||
for k in order:
|
||||
if k not in fm:
|
||||
continue
|
||||
v = fm[k]
|
||||
if isinstance(v, list):
|
||||
if not v:
|
||||
lines.append(f"{k}: []")
|
||||
else:
|
||||
items = ", ".join(quote_yaml(x) for x in v)
|
||||
lines.append(f"{k}: [{items}]")
|
||||
elif isinstance(v, dict):
|
||||
lines.append(f"{k}:")
|
||||
for kk, vv in v.items():
|
||||
lines.append(f" {kk}: {quote_yaml(vv)}")
|
||||
elif isinstance(v, float):
|
||||
lines.append(f"{k}: {v}")
|
||||
else:
|
||||
lines.append(f"{k}: {quote_yaml(v)}")
|
||||
lines.append("---")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def normalize_headers(body: str) -> str:
|
||||
"""Rewrite legacy section headers to match the template names."""
|
||||
def repl(m: re.Match) -> str:
|
||||
hashes = m.group(1)
|
||||
text = m.group(2).strip()
|
||||
# strip leading emoji/punctuation/numbering for matching
|
||||
norm = EMOJI_AT_START_RE.sub("", text).strip()
|
||||
norm = re.sub(r"\s*\(.*?\)\s*$", "", norm).strip() # drop trailing paren
|
||||
key = norm.lower()
|
||||
# also try without punctuation
|
||||
if key not in HEADER_RENAMES:
|
||||
key2 = re.sub(r"[^\w가-힣 -]", "", key).strip()
|
||||
if key2 in HEADER_RENAMES:
|
||||
key = key2
|
||||
if key in HEADER_RENAMES:
|
||||
return f"{hashes} {HEADER_RENAMES[key]}"
|
||||
return m.group(0)
|
||||
return HEADING_RE.sub(repl, body)
|
||||
|
||||
|
||||
def find_section_starts(body: str) -> dict[str, int]:
|
||||
"""Return {section_canonical_name: line_offset}"""
|
||||
found = {}
|
||||
for m in HEADING_RE.finditer(body):
|
||||
text = m.group(2).strip()
|
||||
norm = EMOJI_AT_START_RE.sub("", text).strip().lower()
|
||||
norm = re.sub(r"\s*\(.*?\)\s*$", "", norm).strip()
|
||||
for k, canonical in HEADER_RENAMES.items():
|
||||
if norm == k.lower() or text == canonical:
|
||||
if canonical not in found:
|
||||
found[canonical] = m.start()
|
||||
return found
|
||||
|
||||
|
||||
REQUIRED_SECTIONS_BASE = [
|
||||
"📌 한 줄 통찰 (The Karpathy Summary)",
|
||||
"📖 구조화된 지식 (Synthesized Content)",
|
||||
"🤖 LLM 활용 힌트 (How to Use This Knowledge)",
|
||||
"🧪 검증 상태 (Validation)",
|
||||
"🧬 중복 검사 (Duplicate Check)",
|
||||
"⚠️ 모순 및 업데이트 (Contradictions & Updates)",
|
||||
"🔗 지식 연결 (Graph)",
|
||||
"🕓 변경 이력 (Changelog)",
|
||||
]
|
||||
REQUIRED_SECTIONS_TECH_EXTRA = [
|
||||
"💻 코드 패턴 (Code Patterns)",
|
||||
"🤔 의사결정 기준 (Decision Criteria)",
|
||||
"❌ 안티패턴 (Anti-Patterns)",
|
||||
]
|
||||
|
||||
|
||||
def append_missing_sections(body: str, is_tech: bool, is_stub: bool, fm: dict) -> str:
|
||||
"""For each required section not present, append a scaffold with TODO marker."""
|
||||
found = find_section_starts(body)
|
||||
missing = []
|
||||
base = REQUIRED_SECTIONS_BASE + (REQUIRED_SECTIONS_TECH_EXTRA if is_tech else [])
|
||||
for s in base:
|
||||
if s not in found:
|
||||
missing.append(s)
|
||||
|
||||
if not missing and not is_stub:
|
||||
return body
|
||||
|
||||
out = [body.rstrip()]
|
||||
if is_stub:
|
||||
out.append("\n\n> 🤖 **[AI 추론 보강 필요]** — 본문이 200자 미만이라 P-Reinforce가 빈약 stub으로 분류했습니다.")
|
||||
out.append(f"> source_trust_level=`C` (AI 보강분), confidence_score=`{fm.get('confidence_score', 0.7)}`로 표시되어 있습니다.")
|
||||
out.append("> 사용자 검증 후 trust_level 상향 조정 가능.\n")
|
||||
for s in missing:
|
||||
out.append(f"\n## {s}\n")
|
||||
out.append(_scaffold_for(s, fm))
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def _scaffold_for(section: str, fm: dict) -> str:
|
||||
if section.startswith("📌"):
|
||||
return "> *(TODO: 한 문장으로 핵심 통찰을 작성. \"X는 Y 조건에서 Z 효과를 낸다\" 구조 권장.)*"
|
||||
if section.startswith("📖"):
|
||||
return "**추출된 패턴:**\n> *(TODO)*\n\n**세부 내용:**\n- *(TODO)*"
|
||||
if section.startswith("💻"):
|
||||
return "**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*\n\n```text\n# TODO\n```"
|
||||
if section.startswith("🤔"):
|
||||
return "**선택 A를 써야 할 때:**\n- *(TODO)*\n\n**선택 B를 써야 할 때:**\n- *(TODO)*\n\n**기본값:**\n> *(TODO)*"
|
||||
if section.startswith("❌"):
|
||||
return "- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*"
|
||||
if section.startswith("🤖") and "활용 힌트" in section:
|
||||
return "**언제 이 지식을 쓰는가:**\n- *(TODO)*\n\n**언제 쓰면 안 되는가:**\n- *(TODO)*"
|
||||
if section.startswith("🧪"):
|
||||
return (
|
||||
f"- **정보 상태:** {fm.get('status', 'draft')}\n"
|
||||
f"- **출처 신뢰도:** {fm.get('source_trust_level', 'C')}\n"
|
||||
f"- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*"
|
||||
)
|
||||
if section.startswith("🧬"):
|
||||
return (
|
||||
"- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*\n"
|
||||
"- **처리 방식:** UPDATE (자동 정규화)\n"
|
||||
"- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강."
|
||||
)
|
||||
if section.startswith("⚠️"):
|
||||
return "- **과거 데이터와의 충돌:** 없음\n- **정책 변화:** 없음"
|
||||
if section.startswith("🔗"):
|
||||
return (
|
||||
"- **Parent:** [[10_Wiki/Topics]]\n"
|
||||
"- **Related:** *(TODO: 최소 2개)*\n"
|
||||
"- **Opposite / Trade-off:** *(TODO)*\n"
|
||||
"- **Raw Source:** 직접 입력"
|
||||
)
|
||||
if section.startswith("🕓"):
|
||||
today = date.today().isoformat()
|
||||
trust = fm.get("source_trust_level", "C")
|
||||
return (
|
||||
"| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |\n"
|
||||
"|------|-----------|-----------|--------|\n"
|
||||
f"| {today} | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | {trust} |"
|
||||
)
|
||||
return "*(TODO)*"
|
||||
|
||||
|
||||
def normalize_file(file_path: Path) -> str:
|
||||
text = file_path.read_text(encoding="utf-8", errors="replace")
|
||||
fm_old, body, _raw = parse_frontmatter(text)
|
||||
fm_new = build_frontmatter(fm_old, file_path, body)
|
||||
|
||||
# Redirect documents stay minimal — don't add scaffold sections.
|
||||
if fm_new.get("redirect_to"):
|
||||
return render_frontmatter(fm_new) + "\n\n" + body.lstrip("\n")
|
||||
|
||||
# Header renames first
|
||||
body2 = normalize_headers(body)
|
||||
is_tech = detect_tech(file_path.parent.name, fm_new.get("tags", []), body2)
|
||||
is_stub = len(body2.strip()) < 200
|
||||
body3 = append_missing_sections(body2, is_tech, is_stub, fm_new)
|
||||
|
||||
return render_frontmatter(fm_new) + "\n\n" + body3.lstrip("\n")
|
||||
|
||||
|
||||
def iter_knowledge_files() -> list[Path]:
|
||||
"""All .md under Topics/, excluding operational paths (sessions, _agents,
|
||||
_company, etc) and non-content dirs."""
|
||||
EXCLUDE_FRAG = (
|
||||
"/sessions/", "/_agents/", "/_company/", "/memory/",
|
||||
"/Project_Logs/", "/Harness_Research_", "/docs/records/",
|
||||
"/_Archive_Orphans/", "/Post_Drafts/", "/UX_Scenarios/",
|
||||
)
|
||||
SKIP_DIRS = {".obsidian", ".git", "__pycache__", "node_modules"}
|
||||
out = []
|
||||
import os as _os
|
||||
for dirpath, dirs, files in _os.walk(TOPICS):
|
||||
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
|
||||
for f in files:
|
||||
if not f.endswith(".md"):
|
||||
continue
|
||||
p = Path(dirpath) / f
|
||||
rel = "/" + str(p.relative_to(ROOT)).replace("\\", "/")
|
||||
if any(x in rel for x in EXCLUDE_FRAG):
|
||||
continue
|
||||
out.append(p)
|
||||
return out
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("path", nargs="?", help="Relative path under E:/Wiki/2nd/ or absolute")
|
||||
parser.add_argument("--out", help="Write normalized output to PATH instead of stdout")
|
||||
parser.add_argument("--batch", help="Path to a text file with one input path per line; outputs go under _tools/sample_normalized/")
|
||||
parser.add_argument("--apply-all", action="store_true", help="Normalize ALL knowledge files in-place (operational paths excluded). DESTRUCTIVE.")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.apply_all:
|
||||
files = iter_knowledge_files()
|
||||
ok = err = 0
|
||||
errors: list[tuple[str, str]] = []
|
||||
for i, p in enumerate(files, 1):
|
||||
try:
|
||||
normalized = normalize_file(p)
|
||||
p.write_text(normalized, encoding="utf-8")
|
||||
ok += 1
|
||||
except Exception as e:
|
||||
err += 1
|
||||
errors.append((str(p), str(e)))
|
||||
if i % 200 == 0:
|
||||
print(f" ...{i}/{len(files)}", file=sys.stderr)
|
||||
print(f"DONE: {ok} OK, {err} errors out of {len(files)} files", file=sys.stderr)
|
||||
if errors:
|
||||
log = ROOT / "_tools" / "normalize_errors.log"
|
||||
log.write_text("\n".join(f"{p}\t{e}" for p, e in errors), encoding="utf-8")
|
||||
print(f" errors written to {log}", file=sys.stderr)
|
||||
return 0 if err == 0 else 1
|
||||
|
||||
if args.batch:
|
||||
listing = Path(args.batch).read_text(encoding="utf-8").splitlines()
|
||||
out_dir = ROOT / "_tools" / "sample_normalized"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
results = []
|
||||
for line in listing:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
p = Path(line)
|
||||
if not p.is_absolute():
|
||||
p = ROOT / p
|
||||
if not p.exists():
|
||||
print(f"SKIP missing: {p}", file=sys.stderr)
|
||||
continue
|
||||
try:
|
||||
normalized = normalize_file(p)
|
||||
except Exception as e:
|
||||
print(f"FAIL {p}: {e}", file=sys.stderr)
|
||||
continue
|
||||
out_path = out_dir / (p.stem + ".normalized.md")
|
||||
out_path.write_text(normalized, encoding="utf-8")
|
||||
results.append({"src": str(p.relative_to(ROOT)).replace("\\", "/"),
|
||||
"out": str(out_path.relative_to(ROOT)).replace("\\", "/"),
|
||||
"src_chars": len(p.read_text(encoding="utf-8", errors="replace")),
|
||||
"out_chars": len(normalized)})
|
||||
(out_dir / "_manifest.json").write_text(json.dumps(results, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
print(f"Wrote {len(results)} normalized samples to {out_dir}", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
if not args.path:
|
||||
parser.error("path or --batch required")
|
||||
p = Path(args.path)
|
||||
if not p.is_absolute():
|
||||
p = ROOT / p
|
||||
if not p.exists():
|
||||
print(f"FAIL: {p} does not exist", file=sys.stderr)
|
||||
return 2
|
||||
out = normalize_file(p)
|
||||
if args.out:
|
||||
Path(args.out).write_text(out, encoding="utf-8")
|
||||
print(f"Wrote {args.out}", file=sys.stderr)
|
||||
else:
|
||||
sys.stdout.write(out)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,26 @@
|
||||
# Phase 1 normalization preview — 8 representative samples
|
||||
# Picked to cover the diversity in the corpus:
|
||||
|
||||
# (1) Old template ("Brief Summary"/"Core Content") — typical Architecture/ doc
|
||||
10_Wiki/Topics/Architecture/BPM.md
|
||||
|
||||
# (2) Mid-old template ("Knowledge Connections"/"RL Update") — typical AI/ doc
|
||||
10_Wiki/Topics/AI/Event-Driven-Architecture.md
|
||||
|
||||
# (3) Already mostly-current ("📌 한 줄 통찰" + "📖") — AI_and_ML
|
||||
10_Wiki/Topics/AI_and_ML/Intellectual-Property-in-AI.md
|
||||
|
||||
# (4) Auto-reinforced large doc with broken tags
|
||||
10_Wiki/Topics/Frontend/Timestamp Queries.md
|
||||
|
||||
# (5) Stub <200 chars — needs AI augmentation marker
|
||||
10_Wiki/Topics/AI/Bellman Equation.md
|
||||
|
||||
# (6) Stub variant — punctuation mismatch sibling
|
||||
10_Wiki/Topics/AI/Bellman-Equation.md
|
||||
|
||||
# (7) Tech doc with code blocks (likely)
|
||||
10_Wiki/Topics/AI_and_ML/Bellman_Equation.md
|
||||
|
||||
# (8) Non-tech (design / experience)
|
||||
10_Wiki/Topics/UI_UX_Assets/Design & Experience/Ludo-narrative Dissonance.md
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
id: wiki-2026-0508-bpm
|
||||
title: BPM
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
canonical_id: self
|
||||
aliases: [P-REINFORCE-WIKI-0C43BD75]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
tags: [bpm, event-driven-architecture, mediator-topology, bpel, jbpm, architecture-principles]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-02
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
tech_stack:
|
||||
language: unspecified
|
||||
framework: unspecified
|
||||
---
|
||||
|
||||
# [[BPM]]
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
BPM(Business Process Management) 실행 엔진은 이벤트 기반 아키텍처(Event-Driven Architecture)의 메디에이터 토폴로지(Mediator Topology) 내에서, 주로 인간의 개입이 필요하거나 실행 시간이 긴 복잡한 이벤트 조정 및 오류 처리를 수행하는 데 사용되는 정교한 프로세스 자동화 엔진입니다 [1].
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
소스에서 제공하는 BPM에 대한 핵심 내용은 이벤트 기반 아키텍처의 메디에이터(Mediator) 구현 방식과 연관되어 제한적으로 설명되어 있습니다.
|
||||
|
||||
* **인간의 개입과 장기 실행 프로세스 처리:** 이벤트 조정 및 오류 처리 과정에서 인간의 상호작용(human intervention)이 필요하여 처리 시간이 길어지는(long run times) 경우, BPEL(Business Process Execution Language) 매니저보다 더 고도화된 BPM 실행 엔진을 사용하는 것이 적합합니다 [1].
|
||||
* **고도화된 프로세스 자동화:** BPM 엔진은 다수의 도메인 특화 언어(DSL, Domain Specific Language)를 사용하여 보다 정교한 프로세스 자동화 기능을 제공합니다 [1].
|
||||
* **구현 인프라:** 이러한 BPM 기반의 이벤트 메디에이터 구현을 지원하는 인프라 라이브러리의 대표적인 예시로 jBPM이 활용됩니다 [1].
|
||||
|
||||
*참고: BPM의 세부적인 작동 원리나 구조, 그 외 아키텍처적 특성에 대해서는 소스에 관련 정보가 부족합니다.*
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
*소스에 관련 정보가 부족합니다.*
|
||||
|
||||
(다만 소스 내용을 통해 추론할 때, 단순한 프로그래밍 기반 메디에이터나 BPEL로는 처리하기 어려운 '인간의 개입' 및 '긴 실행 시간'을 다루기 위해 더 정교한(sophisticated) 다중 DSL 기반의 엔진이 필요하다는 제약에서 BPM이 도입됨을 알 수 있습니다 [1]. 구체적인 단점이나 부작용에 대한 언급은 소스에 포함되어 있지 않습니다.)
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
### Related Concepts
|
||||
#### [아키텍처/기반 기술]
|
||||
- [[Event-Driven Architecture]]
|
||||
- 연결 이유: BPM 엔진은 이벤트 기반 아키텍처 생태계 내에서 복잡한 비즈니스 프로세스와 이벤트의 흐름을 통제하는 목적으로 활용되기 때문입니다 [1-3].
|
||||
- 이 개념을 통해 더 깊게 이해할 수 있는 부분: BPM이 비동기 통신 환경에서 이벤트를 어떻게 소비하고 후속 처리를 트리거하는지에 대한 구조적 배경을 이해할 수 있습니다.
|
||||
|
||||
- [[Mediator Topology]]
|
||||
- 연결 이유: BPM은 이벤트 흐름을 중앙에서 통제하고 에러 처리를 담당하는 이벤트 메디에이터(Event Mediator)의 한 구현 형태로 사용됩니다 [1, 3].
|
||||
- 이 개념을 통해 더 깊게 이해할 수 있는 부분: 중앙 집중식 이벤트 조정, 프로세스 상태 유지(State management), 그리고 복잡한 로직 처리 방법을 깊이 있게 학습할 수 있습니다.
|
||||
|
||||
#### [구현/활용 도구]
|
||||
- [[BPEL]]
|
||||
- 연결 이유: BPEL 역시 복잡한 이벤트 메디에이터를 선언적으로 구현하는 데 사용되지만, 인간의 개입이 필요한 장기 실행 프로세스에서는 BPM이 더 적합하다는 점에서 직접적인 비교 대상이 됩니다 [1].
|
||||
- 이 개념을 통해 더 깊게 이해할 수 있는 부분: 이벤트 처리 자동화를 위한 언어적 접근법과 복잡도에 따른 도구 선택 기준을 비교할 수 있습니다.
|
||||
|
||||
- [[jBPM]]
|
||||
- 연결 이유: 소스에서 명시적으로 언급된 BPM 이벤트 메디에이터 구현을 위한 인프라 라이브러리입니다 [1].
|
||||
- 이 개념을 통해 더 깊게 이해할 수 있는 부분: 이론적인 BPM 개념이 실제 시스템 설계 및 코드 레벨에서 어떻게 인프라로 통합되는지 확인할 수 있습니다.
|
||||
|
||||
### Deeper Research Questions
|
||||
- 이벤트 기반 아키텍처의 메디에이터 토폴로지에서 BPEL을 사용하는 것과 BPM 실행 엔진을 사용하는 것을 결정하는 정확한 복잡도 임계점(Threshold)은 무엇인가?
|
||||
- 인간의 개입이 필요한 장기 실행 프로세스(long run times)에서 BPM 엔진은 시스템 장애 시 상태(State) 손실을 막기 위해 어떠한 복구 및 저장 메커니즘을 사용하는가?
|
||||
- 다수의 DSL(Domain Specific Language)을 활용하는 BPM 엔진의 특성이 시스템 개발 및 유지보수 학습 곡선(Learning Curve)에 미치는 영향은 무엇인가?
|
||||
- 고도로 분산된 마이크로서비스 아키텍처 환경에서 중앙 집중형 구조인 BPM 기반 메디에이터를 도입할 때 발생할 수 있는 병목 현상(Bottleneck)과 해결책은 무엇인가?
|
||||
- jBPM과 같은 BPM 라이브러리를 이벤트 브로커(Event Broker) 패턴과 혼합한 하이브리드 아키텍처에서 사용할 때 이벤트 통신 지연(Latency)은 어떻게 관리되는가?
|
||||
|
||||
### Practical Application Contexts
|
||||
- **Implementation:** 비즈니스 요구사항 중 인간의 승인 절차(결재 등)가 포함되어 즉각적인 처리가 불가능한 워크플로우를 구현할 때, jBPM과 같은 라이브러리를 인프라로 도입하여 이벤트를 제어합니다 [1].
|
||||
- **System Design:** 단순 라우팅 이상의 복잡한 조건 분기 및 프로세스 오케스트레이션이 필요한 이벤트 스트림이 있을 경우, 시스템 설계 시 중앙 통제 역할을 하는 BPM Event Mediator를 배치합니다 [1, 4].
|
||||
- **Operation / Maintenance:** *소스에 관련 정보가 부족합니다.*
|
||||
- **Learning Path:** 이벤트 기반 아키텍처의 기본 원리 학습 -> 메디에이터 및 브로커 토폴로지 비교 -> 프로세스 조정을 위한 BPEL 학습 -> 더 정교한 상호작용 처리를 위한 BPM 실행 엔진(jBPM) 순으로 시스템 설계 지식을 확장합니다.
|
||||
- **My Project Relevance:** 복잡한 비즈니스 로직과 수동 검토 과정이 얽혀 있는 이벤트 파이프라인을 설계할 때, 시스템의 프로세스 상태 추적과 처리를 자동화하기 위한 핵심 기술로 BPM 도입을 검토할 수 있습니다.
|
||||
|
||||
### Adjacent Topics
|
||||
- [[Microservices Architecture]]
|
||||
- 확장 방향: MSA 환경에서는 각 서비스가 독립적인 데이터베이스를 가지므로(분산 시스템), 여러 마이크로서비스에 걸친 복잡한 비즈니스 트랜잭션을 조정할 때 BPM과 같은 오케스트레이터(Orchestrator)가 어떻게 활용될 수 있는지 탐구할 수 있습니다.
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-02*
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
|
||||
## 💻 코드 패턴 (Code Patterns)
|
||||
|
||||
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
|
||||
|
||||
```text
|
||||
# TODO
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준 (Decision Criteria)
|
||||
|
||||
**선택 A를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
|
||||
**선택 B를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
|
||||
**기본값:**
|
||||
> *(TODO)*
|
||||
|
||||
## ❌ 안티패턴 (Anti-Patterns)
|
||||
|
||||
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
id: wiki-2026-0508-bellman-equation
|
||||
title: Bellman Equation
|
||||
category: 10_Wiki/Topics/AI
|
||||
status: merged
|
||||
redirect_to: Reinforcement_Learning_and_Decision_Making
|
||||
canonical_id: Reinforcement_Learning_and_Decision_Making
|
||||
aliases: [P-Reinforce-REDIRECT-BELLMAN-SPACE]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [redirect]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-08
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
---
|
||||
|
||||
# [[Bellman Equation]]
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 이 문서는 고밀도 지식 자산 통합 정책에 따라 **[[Reinforcement_Learning_and_Decision_Making]]**으로 통합되었습니다.
|
||||
|
||||
---
|
||||
*Redirected to: [[Reinforcement_Learning_and_Decision_Making]]*
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
id: wiki-2026-0508-bellman-equation
|
||||
title: Bellman Equation
|
||||
category: 10_Wiki/Topics/AI
|
||||
status: merged
|
||||
redirect_to: Reinforcement_Learning_and_Decision_Making
|
||||
canonical_id: Reinforcement_Learning_and_Decision_Making
|
||||
aliases: [P-Reinforce-REDIRECT-BELLMAN-DASH]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [redirect]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-08
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
---
|
||||
|
||||
# [[Bellman-Equation]]
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 이 문서는 고밀도 지식 자산 통합 정책에 따라 **[[Reinforcement_Learning_and_Decision_Making]]**으로 통합되었습니다.
|
||||
|
||||
---
|
||||
*Redirected to: [[Reinforcement_Learning_and_Decision_Making]]*
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
id: wiki-2026-0508-bellman-equation
|
||||
title: Bellman Equation
|
||||
category: 10_Wiki/Topics/AI_and_ML
|
||||
status: merged
|
||||
redirect_to: Reinforcement_Learning_and_Decision_Making
|
||||
canonical_id: Reinforcement_Learning_and_Decision_Making
|
||||
aliases: [P-Reinforce-REDIRECT-BELLMAN-UNDERSCORE]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [redirect]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-08
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
---
|
||||
|
||||
# [[Bellman_Equation]]
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 이 문서는 고밀도 지식 자산 통합 정책에 따라 **[[Reinforcement_Learning_and_Decision_Making]]**으로 통합되었습니다.
|
||||
|
||||
---
|
||||
*Redirected to: [[Reinforcement_Learning_and_Decision_Making]]*
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
id: wiki-2026-0508-event-driven-architecture
|
||||
title: Event Driven Architecture
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
canonical_id: self
|
||||
aliases: [P-Reinforce-AI-EVENT-DRIVEN]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.97
|
||||
tags: [Architecture, EventDriven, Async, PubSub]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-04-20
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
tech_stack:
|
||||
language: unspecified
|
||||
framework: unspecified
|
||||
---
|
||||
|
||||
# [[Event-Driven-Architecture]] (이벤트 주도 아키텍처)
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
> "말 걸지 마, 그냥 공지사항을 확인해." 상태 변화(이벤트)를 발행하고 구독하는 방식으로 시스템을 구성하여, 서비스 간의 직접적인 호출을 없애고 유연한 확장을 가능하게 하는 설계다.
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
- **Components**:
|
||||
- **Event Producer**: 상태 변화를 감지하고 이벤트를 발행함.
|
||||
- **Event Bus / Broker**: 발행된 이벤트를 전달함 (Kafka, RabbitMQ 등).
|
||||
- **Event Consumer**: 필요한 이벤트를 구독하여 로직을 실행함.
|
||||
- **Benefits**:
|
||||
- **Decoupling**: 생산자는 소비자가 누구인지 알 필요가 없다.
|
||||
- **[[Scalability]]**: 트래픽 급증 시 메시지 큐를 통해 부하를 분산 처리할 수 있다.
|
||||
- **Responsiveness**: 비동기 처리를 통해 즉각적인 사용자 피드백이 가능하다.
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
- 이벤트 주도는 시스템 흐름을 파악하기 어렵게 만든다(Where did this event come from?). 또한 '결과적 일관성(Eventual Consistency)'을 수용해야 하므로, 금융 거래처럼 원자성이 중요한 작업에는 설계 난이도가 급상승한다. 분산 추적(Distributed Tracing) 도구 없이는 재앙이 될 수 있다.
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
- Related: [[Microservices-Architecture]] , Message-Queue-Design
|
||||
- Pattern: Observer-Pattern
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
|
||||
## 💻 코드 패턴 (Code Patterns)
|
||||
|
||||
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
|
||||
|
||||
```text
|
||||
# TODO
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준 (Decision Criteria)
|
||||
|
||||
**선택 A를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
|
||||
**선택 B를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
|
||||
**기본값:**
|
||||
> *(TODO)*
|
||||
|
||||
## ❌ 안티패턴 (Anti-Patterns)
|
||||
|
||||
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
id: wiki-2026-0508-intellectual-property-in-ai
|
||||
title: Intellectual Property in AI
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
canonical_id: self
|
||||
aliases: [ETH-IP-001]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 1.0
|
||||
tags: [ai, intellectual-property, copyright, ai-ethics, law, Generative-AI]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-04-26
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
---
|
||||
|
||||
# Intellectual Property in AI (AI와 지식 재산권)
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
> "기계의 창작물에 누구의 이름을 새길 것인가, 그리고 거인의 어깨 위에 올라타는 과정에서 타인의 권리를 어떻게 존중할 것인가" — 인공지능 학습 데이터의 정당한 사용(Fair Use)과 AI 생성 콘텐츠의 저작권 보호 여부를 둘러싼 법적, 윤리적 논의의 총체.
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
- **추출된 패턴:** "Ownership Paradox" — 인간의 창의성이 가미되지 않은 기계의 순수 출력물은 현재의 법체계 하에서 저작권을 인정받기 어려우며, 방대한 데이터를 학습하는 행위와 창작자의 권익 보호 사이의 충돌을 해결하려는 권리 조정 패턴.
|
||||
- **주요 쟁점:**
|
||||
- **Training Data:** 공개된 데이터를 학습에 사용하는 것이 '공정 이용'에 해당하는가? (Opt-in vs Opt-out).
|
||||
- **AI Authorship:** AI가 단독으로 생성한 시, 그림, 코드의 저작권자는 누구인가? (인간 프롬프트 작성자 vs 모델 개발사 vs 없음).
|
||||
- **Derivative Works:** AI 생성물이 특정 작가의 화풍이나 문체를 모방했을 때 발생하는 침해 문제.
|
||||
- **의의:** AI 산업의 상업적 토대를 결정짓는 핵심 변수이며, 지식의 공유와 창작자의 권리 사이의 새로운 사회적 계약이 필요함을 시사.
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
- **과거 데이터와의 충돌:** 지식 재산권이 인간만의 전유물이라 믿던 전통적 관념이 흔들리며, 전 세계적으로 AI 관련 저작권 가이드라인이 실시간으로 수립되고 있음.
|
||||
- **정책 변화:** Antigravity 프로젝트는 외부 지식 인덱싱 시 데이터의 출처(Provenance)를 명확히 기록하며, 상업적 이용이 제한된 소스로부터 생성된 지식은 내부 연구용으로만 격리하여 관리함.
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
- AI-Ethics, [[Generative-AI-Impact|Generative-AI-Impact]], [[Deepfake-Technology|Deepfake-Technology]], Data-Privacy-Foundations
|
||||
- **Raw Source:** 10_Wiki/Topics/AI/Intellectual-Property-in-AI.md
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id: wiki-2026-0508-ludo-narrative-dissonance
|
||||
title: Ludo narrative Dissonance
|
||||
category: 10_Wiki/Topics_Art
|
||||
status: needs_review
|
||||
canonical_id: self
|
||||
aliases: [P-REINFORCE-AUTO-2B9D2D]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
tags: [auto-reinforced]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-04-20
|
||||
github_commit: "[P-Reinforce] Continuous Worker - Ludo-narrative Dissonance"
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
---
|
||||
|
||||
# [[Ludo-narrative Dissonance]]
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
> 지식 요약 정보 추출 중...
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
본문 구조화 작업 중...
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
- **과거 데이터와의 충돌:** 자동화 엔진에 의해 매핑된 지식으로, 추후 정밀 검증 필요.
|
||||
- **정책 변화:** Design & Experience 분야의 자동 자산화 수행.
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
- Raw Source: [[00_Raw/2026-04-20/Ludo-narrative Dissonance.md]]
|
||||
---
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
id: wiki-2026-0508-timestamp-queries
|
||||
title: Timestamp Queries
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
canonical_id: self
|
||||
aliases: [P-Reinforce-AUTO-77D993]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
tags: [auto-reinforced]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-04-20
|
||||
github_commit: "[P-Reinforce] Continuous Worker - Timestamp Queries"
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
tech_stack:
|
||||
language: unspecified
|
||||
framework: unspecified
|
||||
---
|
||||
|
||||
# [[Timestamp Queries|Timestamp Queries]]
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
> Timestamp Queries(타임스탬프 쿼리)는 [[WebGL|WebGL]] 및 WebGPU와 같은 웹 그래픽 파이프라인에서 GPU 명령 세트의 경과 시간을 나노초 단위까지 정밀하게 측정할 수 있게 해주는 기능입니다 [1-3]. 렌더링 파이프라인을 지연시키지 않으면서 GPU 작업 부하의 성능과 동작에 대한 깊은 통찰력을 제공하는 데 필수적입니다 [3, 4]. 그러나 고정밀 타이머가 사이드 채널 공격(예: Spectre 및 Meltdown)에 악용될 수 있다는 보안 취약점 때문에, 최신 브라우저 환경에서는 타이머의 정밀도를 의도적으로 낮추는 양자화([[Quantization|Quantization]]) 기법이 적용됩니다 [2, 5, 6].
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
* **WebGL에서의 타이머 쿼리 한계:**
|
||||
기존 WebGL은 `EXT_disjoint_timer_query` 확장을 통해 렌더링 파이프라인의 중단 없이 GL 명령의 지속 시간을 측정할 수 있었습니다 [1, 4]. 하지만 이 고정밀 타이밍 기능이 캐시 적중률을 관찰하여 Spectre 및 Meltdown과 같은 사이드 채널 공격에 악용될 수 있음이 밝혀지면서, 대부분의 브라우저 공급업체는 이 확장을 비활성화하거나 타이머 값에 엄격한 양자화 및 클램핑을 적용했습니다 [2].
|
||||
* **WebGPU 타임스탬프 쿼리 도입:**
|
||||
WebGPU는 `timestamp-query` 기능과 `GPUQuerySet`을 통해 렌더링 및 컴퓨트 패스의 시작과 끝에서 매우 정밀한(나노초 단위) 측정을 지원합니다 [2, 3]. 이 기능은 텐서플로우(Tensorflow.js)와 같은 복잡한 GPU 작업 부하의 동작을 파악하고 최적화하는 데 널리 활용됩니다 [7].
|
||||
* **보안을 위한 양자화(Quantization) 및 거칠기(Coarsening) 적용:**
|
||||
고정밀 타이머를 통한 지문 수집(Fingerprinting) 및 타이밍 공격을 방지하기 위해 WebGPU는 타임스탬프 값을 양자화합니다 [2, 5]. WebGPU 제안 초기에는 사이트 격리(Site isolation) 여부에 따라 격리된 컨텍스트에서는 100 마이크로초(µs) 해상도로, 격리되지 않은 컨텍스트에서는 쿼리 자체를 노출하지 않는 방식을 고려했습니다 [5, 8].
|
||||
* **교차 출처(Cross-origin) 및 하드웨어 특성을 반영한 정책 변경:**
|
||||
이후 논의 과정에서 GPU 타임스탬프는 고립된 컨텍스트 간에도 공유될 수 있으며 GPU 캐시를 통한 타이밍 공격 위험이 여전히 존재함이 확인되었습니다 [6]. 상호 운용성(Interop)을 높이고 보안을 유지하기 위해, 사이트의 교차 출처 격리(cross-origin isolated) 상태와 무관하게 항상 고해상도 시간 사양(HR-time)에 맞추어 100 마이크로초(µs)로 해상도를 거칠게 만드는(coarsening) 것으로 최종 정책이 합의되었습니다 [6, 9].
|
||||
* **개발자를 위한 예외 처리:**
|
||||
성능 프로파일링을 위해 실제 나노초 단위의 정밀한 측정값이 필요한 개발자는 로컬 환경에서 "WebGPU Developer Features" 또는 "Unsafe WebGPU [[Support|Support]]" 같은 특정 브라우저 플래그를 활성화하여 이러한 양자화 제한을 일시적으로 우회할 수 있습니다 [8, 10].
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
- **과거 데이터와의 충돌:** 자동화 엔진에 의해 매핑된 지식으로, 추후 정밀 검증 필요.
|
||||
- **정책 변화:** Graphics & Performance 분야의 자동 자산화 수행.
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
- **Related Topics:** WebGL Timer Queries, [[Spectre and Meltdown|Spectre and Meltdown]], WebGPU, [[Timestamp Quantization|Timestamp Quantization]]
|
||||
- **Projects/Contexts:** EXT_disjoint_timer_query, High Re[[Solution|Solution]] Time Spec
|
||||
- **Contradictions/Notes:** 초기 WebGPU 구현 제안에서는 사이트 격리 상태에 따라 타임스탬프 쿼리의 노출 여부와 해상도를 다르게 적용하려고 했으나(격리 시 100µs, 비격리 시 미노출) [5], 브라우저 간의 상호 운용성 부족 및 GPU의 격리 한계를 이유로 격리 상태와 관계없이 모든 GPU 작업에 대해 일괄적으로 100µs 해상도를 적용하도록 사양이 수정되었습니다 [6, 9].
|
||||
|
||||
---
|
||||
*Last updated: 2026-04-19*
|
||||
|
||||
---
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
|
||||
## 💻 코드 패턴 (Code Patterns)
|
||||
|
||||
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
|
||||
|
||||
```text
|
||||
# TODO
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준 (Decision Criteria)
|
||||
|
||||
**선택 A를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
|
||||
**선택 B를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
|
||||
**기본값:**
|
||||
> *(TODO)*
|
||||
|
||||
## ❌ 안티패턴 (Anti-Patterns)
|
||||
|
||||
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
|
||||
@@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"src": "10_Wiki/Topics/Architecture/BPM.md",
|
||||
"out": "_tools/sample_normalized/BPM.normalized.md",
|
||||
"src_chars": 3574,
|
||||
"out_chars": 4718
|
||||
},
|
||||
{
|
||||
"src": "10_Wiki/Topics/AI/Event-Driven-Architecture.md",
|
||||
"out": "_tools/sample_normalized/Event-Driven-Architecture.normalized.md",
|
||||
"src_chars": 1102,
|
||||
"out_chars": 2254
|
||||
},
|
||||
{
|
||||
"src": "10_Wiki/Topics/AI_and_ML/Intellectual-Property-in-AI.md",
|
||||
"out": "_tools/sample_normalized/Intellectual-Property-in-AI.normalized.md",
|
||||
"src_chars": 1412,
|
||||
"out_chars": 2208
|
||||
},
|
||||
{
|
||||
"src": "10_Wiki/Topics/Frontend/Timestamp Queries.md",
|
||||
"out": "_tools/sample_normalized/Timestamp Queries.normalized.md",
|
||||
"src_chars": 2650,
|
||||
"out_chars": 3751
|
||||
},
|
||||
{
|
||||
"src": "10_Wiki/Topics/AI/Bellman Equation.md",
|
||||
"out": "_tools/sample_normalized/Bellman Equation.normalized.md",
|
||||
"src_chars": 351,
|
||||
"out_chars": 674
|
||||
},
|
||||
{
|
||||
"src": "10_Wiki/Topics/AI/Bellman-Equation.md",
|
||||
"out": "_tools/sample_normalized/Bellman-Equation.normalized.md",
|
||||
"src_chars": 350,
|
||||
"out_chars": 673
|
||||
},
|
||||
{
|
||||
"src": "10_Wiki/Topics/AI_and_ML/Bellman_Equation.md",
|
||||
"out": "_tools/sample_normalized/Bellman_Equation.normalized.md",
|
||||
"src_chars": 363,
|
||||
"out_chars": 686
|
||||
},
|
||||
{
|
||||
"src": "10_Wiki/Topics/UI_UX_Assets/Design & Experience/Ludo-narrative Dissonance.md",
|
||||
"out": "_tools/sample_normalized/Ludo-narrative Dissonance.normalized.md",
|
||||
"src_chars": 617,
|
||||
"out_chars": 1371
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
Reference in New Issue
Block a user