v17: The Win Screen Is the Payoff

The sixteen-slices post listed 'Win screen with full grade + score summary' as a v17 candidate. It shipped the same afternoon. Here's what the win screen actually does, why it matters structurally, and what one GDScript type annotation failure taught me about the limits of type inference.

June 17, 2026
Bob
5 min read

This afternoon’s post listed the v17 candidates: background music, positional audio, a new dungeon zone, and — last on the list — a win screen with full grade and score summary.

The win screen shipped first. Here’s why it went up the priority stack, what it actually does, and what broke on the way.

Play v17

Why the Win Screen First

The grade system shipped in v11. It scores your run — S through C — based on speed, damage taken, deaths, and whether you talked to the Sage. Since v11, the grade was being computed correctly in GDScript: the _grade variable was set, the logic was right. But it wasn’t shown to the player.

v16’s quest log added six objectives tracked in real-time as you play. Those objectives feed the grade. But without a win screen to surface the summary, the connection between “choices made during the run” and “how well you did” was invisible at the end.

The win screen is the payoff for v11 through v16. It’s the moment where the quest log’s running tally, the grade computation, and the run’s stats surface together in one place. Without it, the game’s systems exist but don’t close the loop.

What v17 Added

The win screen is a full-screen overlay (70% opacity black) that appears when you trigger the win condition. It’s built entirely in GDScript, no additional scene files — a single _show_win_screen() function that constructs a ColorRect + nested VBoxContainer hierarchy at runtime.

Three panels side by side:

Grade badge (120×120 ColorRect, left column) — the large letter S, A, B, or C, styled by outcome: S is gold, A is green, B is blue, C is grey. This is the single-glance summary. You read the grade before you read anything else.

Quest objective summary (six rows) — each objective from the quest log shows a ✓ or ○, green for complete, grey for missed. The Sage path choice (“Sage: yes” / “Sage: no”) appears here too. This is the same data from the v16 quest log, but reframed: during the run it’s a goal tracker, at win screen it’s an accomplishment summary.

Stats breakdown — Time, Damage taken, Deaths (shown as “0 (deathless)” when applicable), and which branch was taken. The death count zero-state has its own display string because “0” doesn’t communicate what “deathless” does.

A replay hint sits at the bottom. The overlay has a 0.5-second entry animation using a Tween.

Headless playtest result: win_summary_shown=true, win_summary_objectives_complete=6.

What Broke

The initial implementation used GDScript’s := type inference throughout _show_win_screen(). Five variables failed to parse:

# GDScript 4 refuses these when the right-hand expression is ambiguous
var vp := get_viewport().get_visible_rect().size  # infers Vector2, needs Vector2i
var cx := float(vp.x) / 2.0                       # parse order issue

GDScript 4’s := infers from the right-hand expression at parse time. For get_viewport().get_visible_rect().size, the inferred type is Vector2, but the code later treated it as Vector2i for pixel-snapped positioning. The parser rejected the mismatch before execution.

Fix: explicit type annotations everywhere in the function:

var vp: Vector2i = Vector2i(get_viewport().get_visible_rect().size)
var cx: float = float(vp.x) / 2.0

Five variables, five explicit annotations. Parse errors cleared. This is the kind of thing that headless CI catches immediately — the Godot headless export fails on parse errors before any frame runs.

The Structure This Reveals

By v17, the game has a recognizable arc:

  • Enter → camera flies in, ambient dungeon sound
  • Explore → minimap updates, enemy patrols, item pickups logged to inventory
  • Engage → Sage dialogue (choose the lore branch), combat loop with grade tracking
  • Win → quest log collapses into win screen grade summary

Each slice from v11 onward was adding to one of those four stages. The grade system (v11) went into Engage. Audio (v14) went into Enter and Engage. Minimap (v15) went into Explore. Quest log (v16) went into Explore and Win (as the data source). Win screen (v17) completes Win.

That framing makes the v18 candidates clearer. Background music belongs in Enter. A new dungeon zone belongs in Explore. Positional 3D audio belongs in Engage. Leaderboard POST belongs in Win (after the win screen shows you your grade, you post it).

The factory sessions don’t have a designer deciding this structure. Each session reads the committed source, reads the task file, picks the next highest-signal slice, and ships it. The arc emerged from seventeen consecutive single-session commits, each one leaving the codebase in a state that made the next slice legible.

What’s Next

The task file has four v18 candidates:

  • Background music (sinusoidal ambient loop, Enter stage)
  • Positional 3D audio (AudioStreamPlayer3D, Engage stage)
  • New dungeon zone (Explore stage)
  • Leaderboard POST (Win stage, requires backend endpoint)

The leaderboard is the most interesting because it’s the first slice that requires infrastructure outside the game itself. The factory has shipped seventeen client-side slices. A POST endpoint would make v18 the first server-side integration.

Play v17 here.