doc 5 of 10

Stage 03 — Draw the boundary: private means private

You will produce: a refactor with zero behavior change — real public APIs on Conductor and Note, and judgment flowing through an enum instead of strings. Warm-up — from memory:

  1. From stage 02: which defect couldn't have shipped, and why?
  2. From the farming FSM (if built): what does an enum make unrepresentable that strings allow?

Why this exists#

Every method on the Conductor starts with _ — and every one of them is called from other nodes. The underscore convention says "internal, don't touch"; this codebase says it and then touches everywhere. The official demo draws the line correctly: public play(), get_current_beat(); private _update_position(). Naming is architecture at script scale — it tells the next reader (you, in six months) what they may depend on.

The second boundary is the judgment channel: judged.emit("perfect") and _counts[kind] — a typo'd string compiles fine and fails at runtime, silently or with a Dictionary crash. Your farming stage 05 already taught the cure.

Do it — requirements, not recipes#

  1. Sort every underscore member of Conductor and Note into actually public (called cross-node) vs actually private. The Signals/warnings won't help you here — read the call sites. Rename the public ones; keep genuinely internal ones prefixed. The editor's rename-symbol helps but does not catch scene-file references — recall stage 02's typo lesson and verify accordingly.
  2. Introduce enum Judgment { PERFECT, GOOD, MISS } where it belongs (whose concept is it — the manager's or the controller's? Take a position, log it). Convert the signal, the emissions, the counter dictionary, and the display formatting. match with all three arms — and consider what the demo's assert in its match default buys, then decide whether you want it.
  3. The counts display currently dot-accesses a Dictionary (_counts.perfect). Does your enum version still want a Dictionary at all? There's a smaller structure hiding here — find it or argue for keeping the Dictionary; either is fine with a reason.
  4. One commit per step; play the game after each.

Checkpoint — definition of done#

  • grep -n "\._" *.gd scenes/*.gd music/*.gd scripts/globals/*.gd (or the editor's search) shows no cross-node underscore calls
  • No judgment string literal survives anywhere; the score line still reads correctly
  • The game plays identically to the stage-02 baseline
  • You can explain: what class of bug did the enum kill, and what did renaming kill?

Close the module by reading your boundary decisions side by side with the official demo's — where you differ, decide deliberately whose call is better and why. That comparison marks this project, at last, as understood code.

Stretch (no instructions)#

The demo's conductor smooths playback jitter with a 1€ filter and a second clock. Write — prose only, no implementation — the paragraph explaining why the prototype's single-clock approach visibly stutters at 144 Hz and what the demo's design trades to fix it. If that paragraph comes easily, this module did its job.