Step 281. ★ CTF #2: 이번엔 팀으로 — 분업의 효율과 소통의 비용을 동시에 배우는 날

Step 281. ★ CTF #2: 이번엔 팀으로 — 분업의 효율과 소통의 비용을 동시에 배우는 날

Level 3 — CTF 실전과 공격 스킬 심화 | 난이도 ★★★☆☆ | 예상 소요 시간 1개 주말 (대회 24~48시간 + 팀 회고 1시간)

전제: Step 279의 솔로 대회 완주, Step 280의 복기 루틴, Step 190의 팀 규칙 초안.

  • 준비물: 팀 2~4명(Step 190에서 만든 팀 또는 커뮤니티 합류), 디스코드 서버(음성+문자 채널), 공유 스프레드시트, 개인별 ctf_log.py, 그리고 오늘 만드는 팀 상태표 병합 스크립트.
  • ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. CTFtime에 등록된 CTF 대회는 주최 측이 참가하라고 연 합법 플랫폼입니다 — 대회가 제공하는 문제 서버 외에는 어떤 대상도 공격하지 않으며, 대회 중 타 팀과의 풀이 공유는 규정 위반임을 기억합니다.
  • 주의: 대회 플랫폼과 디스코드 화면은 전부 화면 예시입니다. 팀원 로그 병합 스크립트의 출력만 로컬 실측입니다.

CTF는 원래 팀 스포츠입니다. 혼자서는 물리적으로 불가능한 분야 커버리지를 팀이 만들어 줍니다. 그런데 두 번째 대회에서 여러분을 기다리는 새로운 발견이 있습니다 — 팀은 분업의 효율만큼 소통의 비용을 청구한다는 사실. 오늘의 목표는 우승이 아니라, 이 두 가지를 같은 대회에서 동시에 체험하는 것입니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • 킥오프 미팅 30분으로 분야 배정과 단서 공유 규칙을 합의한다
  • 공유 상태표(문제 목록/담당/상태)를 만들고 1시간마다 갱신한다
  • "이 단서 쓸 사람?" 커뮤니케이션으로 분야 간 협업을 만든다
  • 2시간 스왑 규칙으로 막힌 문제의 담당을 교체한다
  • 팀원 개별 로그를 합쳐 팀 상태표와 합산 성적을 산출한다
  • 팀 회고로 분업의 잘된 점과 소통 미스를 구분해 기록한다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 디스코드(음성/문자), 공유 스프레드시트, 개인 로그 CSV, 파이썬(병합 스크립트)
오늘의 화면·도구 팀 킥오프 안건, 상태표 템플릿, merge_board.py(로그 병합 + 중복 착수 경고)
필요한 개념 분야 배정, 단서 공유 규칙, 1시간 갱신, 2시간 스왑, 중복 착수, 팀 회고
오늘의 산출물 팀 상태표 + team_log.csv(팀 합산 로그) + 협업 회고 메모

2-1. 솔로와 팀의 차이 — 같은 대회, 다른 게임

솔로 대회의 최적 전략은 "내 시간의 배분"이었습니다. 팀 대회는 여기에 두 개의 축이 더해집니다.

솔로
커버리지 내가 아는 분야만 팀원 전원의 분야 합집합
막힘의 처리 2시간 규칙으로 drop 다른 눈으로 스왑 가능
지식의 흐름 내 플레이북 안에서만 "이 단서 쓸 사람?"으로 실시간 이동
비용 없음 소통·조율·중복 착수의 비용

마지막 행이 오늘의 핵심 긴장입니다. 팀의 이득(커버리지, 스왑, 지식 공유)은 전부 소통이 작동할 때만 실현됩니다. 소통이 무너진 팀은 "점심을 같이 먹는 솔로 3명"일 뿐이고, 그런 팀은 솔로 3명의 합계보다 못합니다 — 중복 착수라는 새 손실이 생기기 때문입니다.

2-2. 사전 준비 — 채널, 상태표, 규칙의 삼종 세트

대회 전날까지 준비할 것은 세 가지입니다.

① 디스코드 채널 구조. 최소 구성은 이 정도면 충분합니다.

📢 announce     — 대회 공지·중요 합의 사항 고정
🚩 flags        — 플래그 인증 성공 보고 (문제명 + 누가)
💡 clues        — "이 단서 쓸 사람?" 단서 던지기 전용
🔊 voice-lounge — 상시 접속 음성 채널

채널을 나누는 이유는 대회 중반의 채팅 폭주 때문입니다. 모든 것이 한 채널에 섞이면, 플래그 인증 소식이 잡담에 묻히고 결정적인 단서가 스크롤 너머로 사라집니다.

② 공유 상태표. 스프레드시트에 열은 다섯 개면 됩니다 — 문제명 | 분야 | 배점 | 담당 | 상태(미착수/진행/막힘/해결). 대회 시작과 동시에 전체 문제 목록을 옮겨 적는 것이 첫 작업입니다.

③ 규칙 합의. Step 190의 팀 규칙 초안을 실전용으로 다듬습니다. 최소 세 가지에 합의가 필요합니다 — 착수 즉시 상태표 기록, 단서 발견 즉시 💡 채널 투척, 1시간마다 상태표 갱신.

2-3. 킥오프 미팅 30분 — 대회 시작 전의 합의

대회 시작 30분 전, 음성 채널에 모입니다. 안건은 정해져 있습니다.

[킥오프 안건 — 30분]
 5분: 문제 목록 전체 훑기 (다 함께 상태표에 옮기며)
10분: 분야 배정 — 각자 주력 분야 선언, 공백 분야 확인
 5분: 단서 공유 규칙 합의 — "발견 즉시 던지기, 판단은 받는 사람이"
 5분: 스왑 규칙 합의 — 한 문제 2시간 막히면 채널에 "스왑 구함" 선언
 5분: 수면 교대 — 누가 언제 자는지 (48시간 대회라면 필수)

분야 배정의 원칙은 Step 190과 같습니다 — 겹치지 않게. 공백 분야가 나오면(예: Pwn 담당이 없음) 정직하게 선언합니다: "Pwn은 건드리지 않는다"가 아니라 "Pwn 쉬운 문제 하나는 맨 마지막 날 오전에 다 같이 본다"처럼 공백의 처리도 계획의 일부입니다.

2-4. 대회 중 운영 — 세 개의 리듬

대회가 시작되면 팀은 세 개의 시계로 돌아갑니다.

  1. 1시간 리듬 — 상태표 갱신. "지금 무엇을 보고 있는가"가 항상 표에 있어야 합니다. 이 리듬이 무너지면 팀은 솔로들의 합으로 퇴화합니다.
  2. 2시간 리듬 — 스왑. 솔로의 2시간 규칙이 drop이었다면, 팀에서는 스왑이 먼저입니다. 내가 2시간 본 문제는 다른 눈에는 10분짜리일 수 있습니다 — 배경 지식이 다르기 때문입니다. 스왑할 때의 예의: 지금까지의 시도를 세 줄로 정리해 넘깁니다.
  3. 식사·수면 리듬 — 2-3에서 합의한 교대표대로. 팀원이 사라질 시간을 미리 알면 남은 사람이 담당 공백을 메울 수 있습니다.

2-5. "이 단서 쓸 사람?" — 팀 고유의 점수 발생 장치

솔로에게는 없는 점수 발생 장치가 있습니다. Web 문제의 응답에서 이상한 헤더를 발견했는데 내 문제와 무관해 보일 때 — 솔로는 그냥 흘립니다. 팀은 💡 채널에 던집니다.

💡 clues — 화면 예시
minho: jwt-forgery 응답에 X-Debug: /backup/config.bak 헤더 보임. 내 문제랑은 무관한 듯. 쓸 사람?
alice: 그거 내 file-upload-rce의 업로드 경로 힌트일 수도. 받음.

이 한 줄의 대화가 점수가 되는 메커니즘: 한 문제의 부산물이 다른 문제의 열쇠인 경우가 대회마다 반드시 있습니다. 주최 측도 그렇게 설계합니다. 그래서 "판단은 받는 사람이 한다"는 규칙이 중요합니다 — 던지는 사람이 "이건 쓸모없겠지"라고 스스로 검열하는 순간 이 장치는 멈춥니다.


3. 따라 하기

3-1. 팀 소집과 사전 준비 (화면 예시)

Step 190의 팀(또는 커뮤니티에서 합류한 팀)에 대회 참가를 제안합니다. 대회는 2-1의 첫 대회와 비슷한 등급의 주말 대회로 고릅니다 — 팀 운영 자체가 새 변수인데 대회 난이도까지 올리면 변수가 둘이 됩니다.

준비는 2-2의 삼종 세트를 만드는 것으로 끝입니다. 상태표 템플릿을 미리 만들어 두고, 팀원 전원이 편집 권한을 갖는지 확인하세요 — 대회 시작 후 권한 요청을 기다리는 것만큼 어색한 시간 낭비가 없습니다.

3-2. 개인 로그에 이름표 달기

팀 대회에서도 개인 로그 습관(Step 279)은 그대로입니다. 다만 나중에 합칠 것을 생각해, 각자의 로그 파일명을 이름으로 저장합니다 — alice.csv, bob.csv, minho.csv처럼. 규칙은 하나뿐입니다: 이벤트 양식을 팀 전원이 똑같이ctf_log.py의 출력 형식 그대로 쓰면 자동으로 맞춰집니다.

3-3. 팀 로그 병합 스크립트 (실측)

대회 중 또는 종료 후, 팀원의 로그를 합쳐 팀 전체 상태표와 합산 성적을 뽑는 스크립트입니다. merge_board.py로 저장하세요.

# merge_board.py — 팀원 개별 로그를 합쳐 공유 상태표와 팀 합산 로그를 만든다
import csv, sys
from datetime import datetime

FMT = "%m-%d %H:%M"
rows = []
for path in sys.argv[1:]:
    owner = path.split(".")[0]          # 파일명 = 팀원 이름
    with open(path, newline="", encoding="utf-8") as f:
        for r in csv.DictReader(f):
            r["담당"] = owner
            rows.append(r)

rows.sort(key=lambda r: r["시각"])      # 파일 순서가 아니라 시간순으로 상태를 쌓는다
problems = {}
for r in rows:
    p = problems.setdefault(r["문제"], {"분야": r["분야"], "배점": r["배점"],
                                        "담당": set(), "상태": "미착수", "start": None, "end": None})
    p["담당"].add(r["담당"])
    t = datetime.strptime(r["시각"], FMT)
    if r["이벤트"] == "start":
        p["상태"] = "진행 중"
        p["start"] = t if p["start"] is None else min(p["start"], t)
    elif r["이벤트"] == "stuck":
        p["상태"] = "막힘"
    elif r["이벤트"] == "drop":
        if p["상태"] != "해결":
            p["상태"] = "포기"
    elif r["이벤트"] == "solve":
        p["상태"] = "해결"
        p["end"] = t

print("문제                | 분야       | 상태   | 담당            | 경과")
print("--------------------|------------|--------|-----------------|-----")
solved_pts = 0
for name, p in problems.items():
    elapsed = "-"
    if p["start"] and p["end"]:
        elapsed = f"{int((p['end'] - p['start']).total_seconds() // 60)}분"
    owners = ",".join(sorted(p["담당"]))
    flag = "  ⚠ 중복 착수" if len(p["담당"]) > 1 else ""
    if p["상태"] == "해결" and p["배점"]:
        solved_pts += int(p["배점"])
    print(f"{name:<20}| {p['분야']:<10} | {p['상태']:<5} | {owners:<15} | {elapsed}{flag}")

n_solved = sum(1 for p in problems.values() if p["상태"] == "해결")
print()
print(f"팀 합계: {len(problems)}문제 착수 / {n_solved}문제 해결 / {solved_pts}점")

with open("team_log.csv", "w", newline="", encoding="utf-8") as f:
    fields = ["시각", "이벤트", "분야", "문제", "배점", "메모", "담당"]
    w = csv.DictWriter(f, fieldnames=fields)
    w.writeheader()
    w.writerows(sorted(rows, key=lambda r: r["시각"]))
print("team_log.csv 저장 완료 (팀 합산 로그)")

핵심 동작 두 가지: ① 모든 로그를 시간순으로 정렬한 뒤 상태를 쌓습니다 — 파일을 읽은 순서대로 쌓으면, 뒤에 읽은 파일의 옛날 기록이 앞 파일의 최신 상태를 덮어버립니다. ② 한 문제에 담당이 둘 이상이면 ⚠ 중복 착수 경고를 붙입니다 — 풀렸더라도 경고가 남습니다. 중복으로 태운 시간은 해결 여부와 무관하게 손실이기 때문입니다.

3-4. 병합 실행 — 상태표와 중복 착수 경고 (실측)

예시 팀(alice, bob, minho)의 실제 기록으로 실행해 보았습니다. 세 사람의 로그를 넣은 결과, 실측 출력입니다.

$ python merge_board.py alice.csv bob.csv minho.csv
문제                | 분야       | 상태   | 담당            | 경과
--------------------|------------|--------|-----------------|-----
jwt-forgery         | Web        | 해결    | alice           | 47분
bof-ret2win         | Pwn        | 해결    | bob             | 142분
substitution-101    | Crypto     | 해결    | minho           | 34분
file-upload-rce     | Web        | 해결    | alice,minho     | 670분  ⚠ 중복 착수
packed-binary       | Rev        | 막힘    | bob             | -
usb-pcap            | Forensics  | 해결    | minho           | 50분
disk-image-1        | Forensics  | 해결    | alice           | 45분
rsa-small-e         | Crypto     | 해결    | bob             | 47분

팀 합계: 8문제 착수 / 7문제 해결 / 1300점
team_log.csv 저장 완료 (팀 합산 로그)

읽는 법: ① 8문제 착수 7문제 해결 — 솔로 대회(6착수/3해결)와 비교해 커버리지의 위력이 보입니다. ② 그런데 file-upload-rce⚠ 중복 착수 경고가 붙었습니다 — alice가 22:10에 착수한 것을 minho가 모르고 22:30에 착수해 40분을 태운 뒤 진행판을 보고 넘긴 사건입니다. ③ packed-binary는 bob이 막힌 채 끝났습니다 — 스왑이 안 일어난 문제입니다. 이 두 줄이 오늘 팀 회고의 안건입니다.

이 스크립트도 실수를 거쳤습니다. 첫 판은 파일 처리 순서대로 상태를 쌓아서, 나중에 읽은 파일의 옛날 "막힘" 기록이 앞 파일의 "해결"을 덮어버렸습니다(file-upload-rce가 해결인데도 막힘으로 표시). 로그를 시간순으로 정렬한 뒤에야 올바른 상태표가 나왔습니다 — "상태는 시간순으로만 쌓인다"는 원칙을 코드로 확인한 셈입니다.

3-5. 종료 후 팀 회고 — 1시간의 안건

대회 종료 후 24시간 안에 음성으로 모입니다. 개인 회고(Step 279)와 다른, 팀 회고만의 안건은 세 가지입니다.

[팀 회고 안건 — 60분]
1. 잘된 분업 (15분): 어떤 배정이 점수를 만들었나
   - 예: Forensics 둘을 alice/minho가 나눠 먹어 350점 확보
2. 소통 미스 (20분): 상태표/채널이 막았어야 할 사고
   - 예: file-upload-rce 중복 착수 40분 — 착수 즉시 기록이 늦었음
   - 예: packed-binary — bob이 "스왑 구함"을 선언하지 않음
3. 다음 개선점 (25분): 규칙 문장으로 만들어 합의
   - "착수 기록은 문제를 연 그 자리에서" — 로그를 나중에 몰아 쓰지 않는다
   - "스왑 구함은 자존심이 아니라 의무" — 2시간 지나면 무조건 선언

회고의 산출물은 반드시 규칙 문장이어야 합니다. "소통을 잘하자"는 다짐은 다음 대회에서 사라지지만, "2시간 지나면 스왑 구함 선언이 의무"는 지킬 수 있는 문장입니다. 이 개선점들이 Step 283에서 목표와 함께 다시 등장합니다.


4. 미션과 연습문제

미션 — 팀 대회 완주와 협업 회고

  1. 팀(2~4명)으로 주말 대회에 등록하고, 2-2의 삼종 세트(채널·상태표·규칙)를 대회 전에 완성합니다.
  2. 킥오프 미팅 30분을 진행하고, 분야 배정과 스왑 규칙 합의를 announce 채널에 고정합니다.
  3. 대회 중 1시간 리듬으로 상태표를 갱신하고, 💡 채널에 단서를 최소 3개 던집니다.
  4. 종료 후 팀원 로그를 merge_board.py로 합쳐 팀 상태표와 합산 성적을 산출합니다.
  5. 3-5의 안건으로 팀 회고를 열고, 개선 규칙 문장 3개를 확정해 저장합니다.

연습문제

문제 1. 소통이 무너진 팀이 "솔로 3명의 합계보다 못한" 이유를 중복 착수의 개념으로 설명해 보세요.

문제 2. 솔로 대회의 2시간 규칙(drop)과 팀 대회의 2시간 스왑이 다른 결론을 내리는 이유를 설명해 보세요.

문제 3. "판단은 받는 사람이 한다"는 단서 공유 규칙이 없으면 어떤 일이 생기나요?

문제 4. 3-4의 실측 상태표에서 팀의 다음 대회 개선점 두 가지를 데이터 근거와 함께 도출해 보세요.


5. 모범 답안과 완료 기준

미션 모범 답안

검증하는 법: ① 킥오프 합의(분야 배정·스왑 규칙)가 채널에 문장으로 남아 있는가 — 구두 합의는 대회 중반에 증발합니다. ② 상태표에 갱신 흔적이 대회 전 시간대에 걸쳐 있는가(시작 때만 쓰고 방치된 표는 장식입니다). ③ 💡 채널에 단서가 실제로 던져졌고, 받은 사람이 "받음"으로 응답했는가. ④ 병합 결과에 ⚠ 중복 착수가 있다면 회고 안건으로 올라갔는가 — 경고를 보고도 회고에서 다루지 않으면 측정의 의미가 없습니다. ⑤ 개선 규칙이 "잘하자"형이 아니라 "언제 무엇을 한다"형 문장 3개인가.

연습문제 해답

문제 1 해답. 솔로 3명이 각자 풀면 같은 문제를 셋이 봐도 각자의 시간이고 결과도 독립적입니다. 그러나 팀으로 묶인 상태에서 중복 착수가 일어나면, 팀 전체 자원(3명 × 시간)의 일부가 같은 문제에 중복 투입되면서도 분업의 이득(커버리지)은 포기한 상태가 됩니다. 소통 비용만 내고 분업 이득을 못 챙기는 구조이므로, 합계 이하로 떨어집니다.

문제 2 해답. 솔로의 drop은 "내 배경 지식으로는 2시간 안에 못 푼다"는 판단입니다. 그런데 배경 지식은 사람마다 다릅니다 — 내게 2시간짜리 벽이 팀원에게는 익숙한 유형일 수 있습니다. 팀에는 다른 눈이 존재하므로, 막힘의 첫 처방은 포기가 아니라 관점의 교체입니다. 스왑마저 실패한 문제가 그때 비로소 팀의 drop이 됩니다.

문제 3 해답. 던지는 사람이 스스로 쓸모를 검열하게 됩니다. "이건 내 문제와 무관하니까"라는 판단은 자기 분야의 지식 안에서 내린 것이라, 다른 문제의 열쇠라는 가능성을 본질적으로 알 수 없습니다. 검열이 시작되면 💡 채널은 멈추고, 한 문제의 부산물이 다른 문제의 열쇠가 되는 사건들이 전부 사라집니다.

문제 4 해답.file-upload-rce의 ⚠ 중복 착수(40분 손실) → "착수 기록은 문제를 연 그 자리에" 규칙 필요. ② packed-binary가 막힘 상태로 종료(스왑 미발생) → "2시간 막히면 스왑 구함 선언 의무화" 규칙 필요. 둘 다 추상적 다짐이 아니라 상태표의 특정 행이 증거인 개선점입니다.

완료 기준 체크리스트

  • [ ] 킥오프 30분의 안건(배정·단서 규칙·스왑·수면 교대)을 설명할 수 있다
  • [ ] 채널을 용도별로 나누는 이유를 설명할 수 있다
  • [ ] 1시간 상태표 갱신 리듬을 대회 내내 유지했다
  • [ ] 단서 던지기를 실제로 3개 이상 실행했다
  • [ ] 팀원 로그를 병합해 합산 성적과 중복 착수 경고를 확인했다
  • [ ] 팀 회고에서 개선 규칙 문장 3개를 확정했다
  • [ ] 솔로와 팀의 2시간 규칙 차이(drop vs 스왑)를 설명할 수 있다

6. 흔한 실수와 해결

벽 1. 각자 따로 풀다가 대회가 끝났어요

증상: 분위기는 좋았는데, 끝나고 보니 누가 무엇을 했는지 모릅니다. 초보 팀의 가장 흔한 실패 패턴입니다.

원인: 상태표 갱신이 "규칙"이 아니라 "하면 좋은 것"으로 합의됐습니다.

해결: 1시간 리듬을 장치로 만드세요 — 음성 채널에 정시 알림 봇을 걸거나, 매시 정각에 캡틴이 "상태표 타임" 한 줄을 치는 것만으로 됩니다. 상태표가 살아 있으면 소통은 저절로 생깁니다. 거꾸로는 절대 안 됩니다.

벽 2. 같은 문제를 두 명이 잡고 있었어요

증상 (실측 — 3-4의 출력):

file-upload-rce     | Web        | 해결    | alice,minho     | 670분  ⚠ 중복 착수

원인: minho가 착수 전에 상태표를 보지 않았고, alice도 착수 기록을 늦게 했습니다. 둘의 40분이 한 문제에 겹쳐 태워졌습니다.

해결: 착수의 순서를 뒤집으세요 — 상태표에 이름을 먼저 쓰고, 그 다음에 문제를 엽니다. "문제부터 열고 나중에 기록"이 중복의 어머니입니다. 병합 스크립트의 경고는 사후 확인용이고, 예방은 이 순서 뒤집기 하나입니다.

벽 3. 스왑을 제안했는데 아무도 안 받아요

원인 1순위: 인수인계 없이 "이거 누가 좀 봐주세요"만 던졌습니다. 받는 사람은 2시간의 맥락을 처음부터 다시 쌓아야 하니 피하게 됩니다.

해결: 스왑은 세 줄 인수인계와 함께입니다 — ① 지금까지 확인한 사실, ② 버린 가설과 이유, ③ 내가 의심하는 다음 후보. 이 세 줄이 있으면 받는 사람의 진입 비용이 수십 분에서 수 분으로 줄어듭니다.

벽 4. 음성 채널이 잡담으로 흐르거나, 반대로 죽어 있어요

원인: 음성 채널의 역할 합의가 없었습니다.

해결: 음성은 "상시 접속 라운지"로 정하고, 기록이 필요한 것(착수/해결/단서/합의)은 전부 문자 채널과 상태표에 남기는 규칙을 쓰세요. 음성의 말은 휘발되므로, 중요한 합의는 그 자리에서 announce에 문장으로 옮기는 것까지가 합의입니다.

벽 5. 실력 차이가 커서 눈치가 보여요

증상: 신입 팀원이 단서 던지기를 주저하고, 고수 팀원이 문제를 독점합니다.

원인: 역할이 "분야"로만 나뉘고, 신입의 기여 경로가 설계되지 않았습니다.

해결: 신입의 명시적 역할을 정하세요 — 상태표 관리, 💡 채널의 첫 반응자, 쉬운 문제(워밍업) 전담. 특히 상태표 관리자는 팀 전체 진행을 보는 자리라, 실력과 무관하게 대회 전체 그림을 가장 빨리 배우는 자리입니다. 고수는 고수대로 "막힌 문제의 스왑 수신자"라는 역할이 있어야 독점이 풀립니다.


7. 정리

오늘의 개념

개념 한 줄 설명
분업의 효율 분야 합집합으로 커버리지가 넓어지는 팀의 이득
소통의 비용 조율·기록·중복 착수 — 작동하지 않으면 솔로 합계 이하
킥오프 30분 배정·단서 규칙·스왑·수면 교대를 대회 전에 문장으로 합의
1시간 리듬 상태표 갱신 주기 — 팀이 솔로들의 합으로 퇴화하지 않는 장치
2시간 스왑 막힌 문제의 담당 교체 — 다른 눈은 다른 배경 지식
💡 단서 던지기 한 문제의 부산물을 다른 문제의 열쇠로 연결하는 팀 고유 장치
중복 착수 같은 문제에 둘이 착수하는 손실 — 예방은 "기록 먼저, 문제는 나중"
팀 회고 잘된 분업/소통 미스를 구분하고 개선점을 규칙 문장으로 확정

오늘의 도구와 명령어

도구·명령 하는 일
디스코드 채널 (announce/flags/clues) 합의 고정, 인증 보고, 단서 유통
공유 상태표 문제별 담당·상태의 단일 진실 공급원
python merge_board.py alice.csv bob.csv ... 개별 로그 병합 → 상태표 + 중복 경고 + 팀 합산
team_log.csv 병합된 팀 로그 — Step 283 목표 산출의 입력
세 줄 인수인계 확인한 사실/버린 가설/다음 후보 — 스왑의 진입 비용 절감

핵심 감각

팀 대회에서 점수를 만드는 것은 실력만이 아닙니다 — 정보가 이동하는 속도입니다. 단서가 발견에서 채널까지 1분, 채널에서 맞는 사람의 눈까지 1분에 도달하는 팀은, 개인 실력이 같아도 한 등급 위의 성적을 냅니다. 오늘 만든 규칙들은 전부 그 이동 속도를 위한 배관입니다.

그리고 회고에서 확정한 개선 규칙들을 버리지 마세요. 다음 대회 — Step 283 — 에서는 그 규칙들을 지키는 것 자체가 측정 가능한 목표가 됩니다.


전부 체크되면 Step 281 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.