CTF · Wargames
Step 297. ★ CTF #10: 10th Competition Special — The Full Retrospective — Growth Is Not Linear, It Is a Cycle of Plateaus and Leaps
Level 3 — Real-World CTF & Advanced Offensive Skills | Difficulty ★★★★☆ | Estimated time: 2 days (24–48 hour competition + 1 day integrated retrospective)
Prerequisites: all of competitions #1–#9 from Steps 279–295, with each competition’s log, debrief tables, and gap-analysis records.
- What you need: one weekend competition (your tenth appearance), every competition record so far (logs, debrief tables, gap analyses, ranking memos), Python + matplotlib, and a retrospective slot where the whole team can gather. The competition scenes in this chapter are screen examples; the integrated analysis script is a measured tool you run yourself on your own records.
- ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. When including a teammate’s personal information or records in retrospective material, get their consent and anonymize first.
- This chapter is competition + consolidation — you enter the tenth competition with your usual routine, then retrospect the trajectory of ten competitions into a single document.
The tenth competition is a milestone. Open the log of your first competition — the team that trembled through the 30-minute full sweep, the team that cheered after solving two easy problems. Now put today’s you side by side. Problems solved, ranking percentile, fields covered, the speed at which you read a problem — every number should be different.
But graph that change and it is not a straight line. Growth comes as a cycle of plateaus and leaps — several competitions that feel like standing still, then one competition where you suddenly climb a step. Today’s retrospective is about confirming that trajectory as data, connecting what you did just before each leap, and designing the next ten competitions (Season 2). The duty of an anniversary is not celebration — it is completing the record.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Enter the 10th competition with your usual routine intact, securing comparable data
- Consolidate 10 competitions into one data table of "round | ranking | score | solves | per-field score | notes"
- Graph the ranking-percentile trend and connect each leap to the training that preceded it
- Analyze the causes of plateaus and the triggers of escape, extracting reproducible growth conditions
- Design Season 2 (competitions #11–#20): target percentile band, training direction, and operating direction
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Python 3 + matplotlib (trend graph), spreadsheet or Markdown table |
| Today’s command | python season_review.py — integrated data table + percentile trend graph |
| Concepts needed | Percentile trends, the structure of plateaus and leaps, the composition of a long-term retrospective post |
| Today’s deliverable | Competition #10 log + 10-competition integrated retrospective document + Season 2 plan |
2-1. Why Percentiles — The Trap of Absolute Scores
Line up the scores of 10 competitions and they are not comparable — total points, participant level, and difficulty differ per competition. 450 points is bottom-tier in one competition and mid-tier in another. The comparable metric is the ranking percentile (my rank ÷ total teams) — a relative position within the participant pool, so it reads in the same unit even across competitions of different sizes.
That said, percentiles carry noise too — in a competition crowded with strong teams, the same skill produces a worse percentile. So a retrospective reads not one round’s rise or fall but the trend line. A single drop is data; three consecutive drops are a signal.
2-2. Plateaus and Leaps — The Real Shape of the Growth Curve
The skill-acquisition curve is staircase-shaped. Training does not raise scores immediately — there is a lag before what you learned hardens into a field-ready pattern. The standing-still during that lag is the plateau; the round where the hardened material converts into points all at once is the leap.
If you don’t know this structure, you declare "I’m not improving" during a plateau, change the routine, and throw away what was just hardening. Know the structure, and the plateau question changes — not "why am I stuck?" but "what is hardening right now?" The retrospective’s core task is finding each leap and connecting it to the training of the 2–3 weeks before it — that connection is the raw material of Season 2 design.
2-3. The Integrated Data Table — A Round Without Records Is "No Record"
Gather 10 competitions into one table. There is one rule — never fill from memory. A round with neither a log nor a ranking memo is marked "no record," not left blank.
The rule may look forced, but there are two reasons. ① Memory-filled numbers contaminate the analysis — the trend line ends up reflecting memory’s embellishment. ② The distribution of "no record" cells is itself data — it means the record routine broke down during that period, and that becomes the first item of the Season 2 operations plan.
2-4. Composing the Long-Term Retrospective — A Milestone Document for the Blog
This retrospective is also a candidate centerpiece for your blog portfolio (Step 294). The recommended composition for a 10-competition retrospective is five paragraphs.
1. Ten competitions in numbers — the integrated data table and trend graph (numbers only, no adjectives)
2. The map of leaps — each leap connected to the training done just before it
3. The record of plateaus — stalled stretches, their causes, their escape triggers
4. The team's evolution — how operating rules and tactics changed (the charter's version history)
5. The Season 2 declaration — goals and measurement methods for the next ten
The point is the first paragraph’s "no adjectives." A long-term retrospective’s credibility comes from numbers appearing first — interpretation is the job of paragraph two onward.
3. Follow Along
3-1. Competition #10 — With Your Usual Routine
Don’t deploy special tactics just because it’s the tenth. Comparability for the retrospective comes from "same conditions" — the fixed team tactics, the 4-hour checkpoints, the swap rules, the kickoff prediction table, all exactly as usual. The specialness of an anniversary competition is expressed not in tactics but in fidelity of records — this competition’s log becomes the last row of the 10-round integrated table, so record times and per-field scores with particular completeness.
Screen example (memo right after competition #10 ended):
Competition #10: 120th of 428 teams (top 28.0%) — first entry into the 30% line
Score 2900, 8 solves (web 2, pwn 2, crypto 2, rev 1, forensics 1)
Note: first solve of a babyheap-type heap problem — confirmed the single goal from competition #9's gap analysis
Look at the last line — competition #9’s single improvement goal ("heap boot camp") was recovered as points in this competition. A goal with a judgment criterion settles accounts at the next competition like this. This one line is the most valuable sentence in the retrospective.
3-2. Building the Integrated Data Table
Gather the records of 10 competitions. Fill by cross-checking competition logs against CTFtime records; anything missing is "no record."
Screen example (integrated data table — this chapter’s example data):
| Round | Teams | Rank | Percentile | Score | Solves | Notes |
|---|---|---|---|---|---|---|
| #1 | 380 | 291 | top 77% | 450 | 2 | First competition, goal was finishing |
| #2 | 405 | 262 | top 65% | 700 | 3 | First team appearance |
| #3 | 356 | 178 | top 50% | 1100 | 4 | Behavior goals introduced |
| #4 | 390 | 187 | top 48% | 1050 | 4 | Weakness-attack experiment |
| #5 | 402 | 165 | top 41% | 1400 | 5 | First Hard attempt |
| #6 | 433 | 201 | top 46% | 1250 | 5 | Mid-evaluation — a drop |
| #7 | 368 | 140 | top 38% | 1900 | 6 | Library validation |
| #8 | 395 | 158 | top 40% | 1700 | 5 | Rotation experiment |
| #9 | 412 | 156 | top 38% | 2350 | 7 | 30% challenge missed, gap analysis |
| #10 | 428 | 120 | top 28% | 2900 | 8 | 30% entry, heap boot camp pays off |
How to read it: two stretches should be visible — the drop from #5→#6 (41%→46%) and the gentle entry from #8–#10. That the drop is not a failure is explained by the notes column — #6 was a competition crowded with strong teams, and the absolute solve count (5 problems) held. Reading the numbers’ rises and falls together with the notes’ context is the correct use of the integrated table.
3-3. The Trend Graph — Visualizing the Growth Curve
Turn the table into a graph. This script plots the percentile trend as a line graph with 50% and 30% reference lines in matplotlib. Save it as season_review.py.
# season_review.py — integrated retrospective analyzer for 10 CTF competitions
# Replace the ROUNDS list with your team's records
import sys
from pathlib import Path
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = ["Malgun Gothic", "Noto Sans SC"] # CJK font
plt.rcParams["axes.unicode_minus"] = False
ROUNDS = [ # round, total teams, our rank, score, solves
(1, 380, 291, 450, 2), (2, 405, 262, 700, 3), (3, 356, 178, 1100, 4),
(4, 390, 187, 1050, 4), (5, 402, 165, 1400, 5), (6, 433, 201, 1250, 5),
(7, 368, 140, 1900, 6), (8, 395, 158, 1700, 5), (9, 412, 156, 2350, 7),
(10, 428, 120, 2900, 8),
]
CATS = ["web", "pwn", "rev", "crypto", "forensics"]
R1 = [1, 0, 0, 1, 0] # per-field solves at competition #1
R10 = [2, 2, 1, 2, 1] # per-field solves at competition #10
print("[Integrated data for 10 competitions]")
print(f"{'rd':>3} {'teams':>5} {'rank':>5} {'pct':>10} {'score':>6} {'solves':>6}")
pcts = []
for r, total, rank, score, solves in ROUNDS:
pct = rank / total * 100
pcts.append(pct)
print(f"{r:>3} {total:>5} {rank:>5} {'top %.1f%%' % pct:>10} {score:>6} {solves:>6}")
print("n[Per-field growth — #1 vs #10]")
for c, a, b in zip(CATS, R1, R10):
print(f" {c:<10} {a} -> {b} (+{b - a})")
fig, ax = plt.subplots(figsize=(8, 4.2))
ax.plot([r[0] for r in ROUNDS], pcts, marker="o", color="#2b6cb0")
ax.axhline(50, ls="--", lw=1, color="gray"); ax.axhline(30, ls="--", lw=1, color="crimson")
ax.text(1, 51, "top 50%", color="gray"); ax.text(1, 31, "top 30%", color="crimson")
ax.invert_yaxis() # lower percentile is better, so flip so up is good
ax.set_xlabel("Competition round"); ax.set_ylabel("Ranking percentile (%, lower is better)")
ax.set_title("CTF #1-#10 ranking percentile trend")
ax.set_xticks([r[0] for r in ROUNDS])
fig.savefig("season_trend.png", bbox_inches="tight")
print("nGraph saved: season_trend.png")
Run result — measured output with the example data loaded:
[Integrated data for 10 competitions]
rd teams rank pct score solves
1 380 291 top 76.6% 450 2
2 405 262 top 64.7% 700 3
3 356 178 top 50.0% 1100 4
4 390 187 top 47.9% 1050 4
5 402 165 top 41.0% 1400 5
6 433 201 top 46.4% 1250 5
7 368 140 top 38.0% 1900 6
8 395 158 top 40.0% 1700 5
9 412 156 top 37.9% 2350 7
10 428 120 top 28.0% 2900 8
[Per-field growth — #1 vs #10]
web 1 -> 2 (+1)
pwn 0 -> 2 (+2)
rev 0 -> 1 (+1)
crypto 1 -> 2 (+1)
forensics 0 -> 1 (+1)
Graph saved: season_trend.png
Open the saved season_trend.png — the line that started at 77% crosses the 50% line at #3, wobbles at #5–#6, and finally drops below the 30% line at #10. The per-field growth table tells another story — at #1, three fields (pwn, rev, forensics) were at zero; at #10, points came from every field. The growth of coverage, more than the growth of ranking, is the body of moving up a weight class.
3-4. Connecting Leaps and Plateaus
For every rise and fall on the graph, find "what did we do just before" in the records.
Screen example (leap analysis):
Leap 1: #2 (65%) -> #3 (50%) — done just before: team role division introduced, behavior goals set
Leap 2: #6 (46%) -> #7 (38%) — done just before: exploit library built (block C)
Leap 3: #9 (38%) -> #10 (28%) — done just before: heap boot camp (competition #9's single improvement goal)
Plateau: #3~#5 (50 -> 48 -> 41%) — gentle, but actually a preparation stretch
Cause of the #6 drop: strong-team crowd + time allocated to a Hard attempt (solve count held)
How to read it: the three leaps share a common feature — all are the recovery of a specific prior investment (role division, library, boot camp). No competition went well by accident. This is the basis of the Season 2 plan — if you want the next leap, you must book the investment first; the points arrive with a lag.
3-5. Designing Season 2 and Publishing the Retrospective
Finally, design the next ten competitions. Write goals as percentiles, investments as concrete routines.
Screen example (Season 2 plan):
== Season 2 (competitions #11-#20) plan ==
Target band: settle inside top 30% by #15 -> challenge top 20% by #20
Training direction: ① past-exam training for domestic competition qualifiers (CODEGATE, CCE)
② raise second players for rev/forensics — deepen coverage
③ Medium-level speed — cut average time per problem by 20%
Operating direction: keep the KPT refresh rhythm of team charter v1, strengthen the record routine
(prevent recurrence of Season 1's "no record" cells — fix the competition log template)
Measurement: gap analysis every competition + interim retrospective every 5 (#15, #20)
Publish this document to the blog in 2-4’s five-paragraph composition — the integrated table, the trend graph (season_trend.png), the map of leaps, the plateau record, the Season 2 declaration. A ten-competition retrospective is a centerpiece by itself — a post that shows analytical power on top of proof of consistency.
4. Missions & Exercises
Mission — Complete Competition #10 + the 10-Round Integrated Retrospective Document
- Enter competition #10 with your usual routine and keep the log with extra fidelity — it becomes the integrated table’s last row.
- Build the integrated data table (round | teams | rank | percentile | score | solves | notes) for 10 competitions — missing records are "no record."
- Replace
ROUNDS,R1, andR10inseason_review.pywith your team’s records, run it, and save the trend graph. - Write the "map of leaps" connecting each leap to the training just before it, and analyze the cause and escape trigger of at least one plateau.
- Settle whether competition #9’s single improvement goal was recovered in this competition — if recovered, state it in the retrospective.
- Write the Season 2 plan (target band, training direction, operating direction, measurement cadence) and publish the retrospective to the blog in 2-4’s five-paragraph composition.
Exercises
Exercise 1. Why do we use ranking percentile instead of absolute score when comparing results across 10 competitions? What noise remains even in percentiles?
Exercise 2. Explain, using the concept of "the lag in converting training into points," why the growth curve is staircase-shaped (plateaus + leaps) rather than linear. Why is changing the routine during a plateau dangerous?
Exercise 3. Give two reasons for leaving a round without records as "no record" rather than filling it from memory.
Exercise 4. Using this chapter’s example data (#1 vs #10), explain the grounds for the interpretation that "growth in field coverage, more than growth in ranking, is the body of moving up a weight class."
Answers & completion criteria · expand/collapse
5. Model Answers & Completion Criteria
Mission Model Answer
Check against these verification criteria.
- Routine consistency: was competition #10 run with the usual routine, no anniversary special tactics — comparability is the retrospective’s lifeblood.
- Table completeness: are all 10 rows present, gaps explicitly marked "no record," and does the notes column explain the context of rises and falls?
- Graph accuracy: does the trend graph match the table’s numbers (graph points = table percentiles), with 50% and 30% reference lines present?
- Evidence of leap connections: is each leap connected to "concrete training from the 2–3 weeks before" — "worked hard" is not a connection.
- Goal settlement: was competition #9’s single improvement goal settled by its judgment criterion?
- Season 2 measurability: is the goal a percentile with a stated measurement cadence — "do better" is not a plan.
- Retrospective composition: does it follow the five-paragraph structure, with the first paragraph being adjective-free numbers?
Exercise Answers
Answer 1. Because total points, difficulty, and participant level differ per competition, absolute scores share no common unit — 450 points is bottom-tier in one competition and mid-tier in another. A percentile is "relative position within the participant pool," so competitions of different sizes can be compared. The remaining noise is the composition of the participant pool — in a competition crowded with strong teams, the same skill yields a worse percentile (#6 in the example). So percentiles are read as a trend line, not a single round, and rises and falls are interpreted together with the notes column’s context.
Answer 2. For a learned technique to become points in the field, the pattern recognition of "seeing a problem and recalling that technique" must harden — and that maturation takes time. Right after training, the knowledge exists but fires too slowly to raise scores (plateau); in the round after maturation completes, multiple techniques convert at once (leap). Changing the routine during a plateau discards what was just hardening — you fail to recover the investment right before conversion. The correct plateau question is not "why isn’t it rising" but "what is hardening right now."
Answer 3. First, memory-filled numbers tend toward embellishment and contaminate the trend line — if the analysis’s input is fiction, the output is fiction. Second, the distribution of "no record" cells is itself valid data — the period where records are missing is the period where the record routine broke down, and that information leads directly into Season 2’s operations plan (like fixing the record template). Writing absence as absence is the retrospective’s honesty, and a dishonest retrospective returns as a wrongly designed next season.
Answer 4. In the example, the percentile improved 77%→28%, but that is a relative position that shakes with the competition pool. Field coverage, by contrast, changed from #1’s "points in only 2 of 5 fields" to #10’s "points in every field" — an expansion of absolute capability independent of the pool. In particular, pwn moving 0→2 is the recovery of a specific investment (heap boot camp), so it is reproducible. Rankings are made by the environment; coverage is made by the team — which is why the latter is the more accurate ruler of weight class.
Completion Criteria Checklist
- [ ] I entered competition #10 with my usual routine and kept the log faithfully
- [ ] I completed the 10-competition integrated data table (missing records marked "no record")
- [ ] I ran
season_review.pyon my records and saved the trend graph - [ ] I wrote the map of leaps (leap points connected to prior training)
- [ ] I analyzed the cause and escape trigger of at least one plateau
- [ ] I settled whether competition #9’s single improvement goal was recovered
- [ ] I documented the Season 2 plan (target percentile, training, operations, measurement cadence)
- [ ] I published the five-paragraph retrospective to the blog
6. Common Pitfalls & Fixes
Wall 1. All the Korean text in my graph renders as boxes (□□)
Symptom: this warning appears when saving.
UserWarning: Glyph 45824 (N{HANGUL SYLLABLE DAE}) missing from font(s) Noto Sans SC.
fig.savefig(Path(__file__).parent / "season_trend.png", bbox_inches="tight")
Cause: matplotlib’s default CJK font (Noto Sans SC) is a Simplified Chinese font with no Hangul glyphs (measured).
Fix: as in the script’s font setting, on Windows put Malgun Gothic first — plt.rcParams["font.family"] = ["Malgun Gothic", "Noto Sans SC"]. The font setting must come right after the import, before any figure is created, to take effect. On macOS use AppleGothic; on Linux use Noto Sans CJK KR.
Wall 2. Half my old competition records are missing — should I give up the retrospective?
Symptom: the rankings and scores of the first 2–3 competitions exist only in memory.
Cause: early in Season 1 the record routine wasn’t established yet — true for most teams.
Fix: don’t abandon the retrospective, but separate memory from records. If the team was registered on CTFtime, rankings can often be recovered from past competition result pages — that is a record. Cells that can’t be recovered stay "no record," and you write "absence of a record routine" itself into the retrospective’s plateau analysis as one factor of the early stall. And it becomes Season 2’s first item — fixing the competition log template. Even a deficit becomes an asset once analyzed.
Wall 3. My trend graph declines somewhere and I’m embarrassed to publish it
Symptom: there’s a stretch where the percentile worsened, and you hesitate to publish.
Cause: you’re treating the retrospective as a report card — what the retrospective’s reader looks at is not your results but your analytical power.
Fix: a retrospective with a declining stretch is actually stronger. Even the #6 drop in the example, paired with the context "strong-team crowd + solve count held," is evidence of diligent analysis. A retrospective that only goes up and to the right reads one of two ways — lucky, or dishonest. A post that explains rises and falls with numbers and context — that is centerpiece material.
Wall 4. I set my Season 2 goal ambitiously at "top 10%"
Symptom: the anniversary atmosphere inflated the goal.
Cause: the elation right after a leap is a fine thing, but it is poison for goal setting.
Fix: return to the goal-raising rule — "the next tick above the last line you achieved" (Step 295). If you just entered 30%, the next tick is "settle into 30% → challenge 20%," not 10%. A leap climbs one stair; it doesn’t skip floors — trust Season 2’s ten competitions and bet on the gradual. An oversized goal makes every gap analysis output "huge failure," and the team burns out.
Wall 5. My retrospective grew into an autobiography
Symptom: writing every competition’s episode pushed it past 10,000 characters.
Cause: all ten rounds of memories are precious — but the retrospective is not a memory vault; it is an analysis document.
Fix: pin 2-4’s five-paragraph composition as the skeleton and set length limits per paragraph (paragraph 1: table + graph; paragraphs 2–4: five sentences each). Individual competition episodes already live in each competition’s Write-up — in the retrospective, recover them as links. Ten "the full story of this competition is here" links lighten the retrospective and simultaneously produce the series-bundling effect (Step 294) of flowing readers into your other posts.
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| Ranking percentile | The metric that compares differently sized competitions in one unit — read as a trend line |
| Staircase growth | The lag structure of train → mature → convert — a plateau is a preparation stretch |
| Map of leaps | Each leap connected to the investment just before it — how to book the next leap |
| The "no record" principle | Never fill from memory — the distribution of gaps is also data |
| Coverage growth | Expanding points across all fields — an absolute capability metric independent of the pool |
| Season design | Goals as percentiles, investments as routines, measurement as cadence |
Today’s Commands & Tools
| Command/tool | What it does |
|---|---|
python season_review.py |
Print the integrated data table + save the percentile trend graph |
plt.rcParams["font.family"] |
Set the graph’s Korean font — Malgun Gothic on Windows |
| Integrated data table | The single source of truth for 10 rounds — the notes column owns context |
| Map of leaps | The investment→leap connection table — Season 2’s basis document |
| Retrospective five paragraphs | Numbers → leaps → plateaus → team evolution → Season 2 declaration |
The Core Instinct
The most important fact in ten competitions of data is not the ranking — it is that "this team became a team that recovers what it invests." Introduce role division and the next competition’s score rises; build a library and the one after rises; run a boot camp and that problem type gets solved. A team whose causality is confirmed in data no longer reads competition results as luck.
Season 2’s first investment is already decided — domestic competition qualifiers. CODEGATE and CCE are stages of a different grain from the weekend competitions so far, and the way to grasp that grain is past exams. That is the subject of the next step.
Once every box is checked, Step 297 is complete.
ONE STEP FURTHER
Finished this lesson?
Check the completion criteria, then mark your progress.