Lesson 267: Discord Playtest Thread CSV Ingest Receipt on BUILD_RECEIPT (2026)

Direct answer: Every Tuesday before Wednesday smoke, promote playtest_csv_ingest_receipt_v1.json with D1–D6 gates—archive Discord CSV + sha256, column map, jq normalize to playtest_feedback_normalized.json, build_id crosswalk to BUILD_RECEIPT, P0/P1 blocker counts, and wednesday_smoke_allowed policy. Pair Discord CSV jq preflight (Guide #29), Tuesday CSV ingest ritual, and cousin Lesson 256 (UTF-8 Weblate bytes—not triage counts).

Lesson hero for Discord playtest thread CSV ingest receipt

Why this matters now (async playtest before Wednesday smoke)

October–November 2026 Discord threads produce 30–80 CSV rows per fest week while stand-ups stay fifteen minutes. Producers promote nextfest-nov-2026-rc5 with four P0 rows still referencing last Tuesday’s build_id. Wednesday smoke proves binary golden path—it does not read facilitator sheets. Lesson 266 wires VOD→GitHub honesty; 267 wires async CSV→BUILD_RECEIPT scope before promotion.

The Tuesday twelve-minute ritual is facilitator rhythm; 267 is the BUILD_RECEIPT milestone.

Beginner path (Tuesday twelve-minute ingest)

Step Action Success check
1 Export Discord thread CSV to playtest-feedback/raw/ D1 sha256 logged
2 Confirm column map matches headers D2 pass
3 Run jq normalize script D3 playtest_feedback_normalized.json
4 Crosswalk build_id to BUILD_RECEIPT D4 single match
5 Count P0/P1; apply promotion policy D5 wednesday_smoke_allowed
6 File receipt + BUILD_RECEIPT columns csv_ingest_ok: true

Time: ~52 minutes first Tuesday setup; ~12 minutes when jq script + column map are pinned.

Developer path (gates D1–D6)

Gate Check Fail when
D1 Discord CSV archived Path missing or sha256 not logged
D2 Column map matches README Required fields absent after alias map
D3 jq normalize runs clean Empty rows[] or jq exit non-zero
D4 build_id crosswalk BUILD_RECEIPT Multiple builds or Discord pin ≠ receipt
D5 P0/P1 counts + promotion policy P0 > 0 without waiver when promoting
D6 playtest_csv_ingest_receipt_v1.json csv_ingest_ok false at promote

Align with Guide #29 preflight—blog = rhythm; guide = ninety-second gate; 267 = BUILD_RECEIPT column.

D1 — Archive raw CSV + sha256

CSV="playtest-feedback/raw/discord_thread_2026-11-04.csv"
test -f "$CSV"
sha256sum "$CSV" | tee playtest-feedback/raw/discord_thread_2026-11-04.sha256

PowerShell:

Get-FileHash "playtest-feedback\raw\discord_thread_2026-11-04.csv" -Algorithm SHA256

Rule: Archive whatever facilitator exports—encoding fixes are Lesson 256 cousin lane, not D1.

D2 — playtest_csv_column_map_v1.json

{
  "schema": "playtest_csv_column_map_v1",
  "source_headers": ["Build", "P-level", "Category", "Issue", "Link"],
  "canonical_map": {
    "build_id": "Build",
    "severity": "P-level",
    "area": "Category",
    "summary": "Issue",
    "discord_message_url": "Link"
  },
  "severity_normalize": {
    "P0": ["P0", "p0", "Blocker", "Critical"],
    "P1": ["P1", "p1", "High"],
    "P2": ["P2", "p2", "Medium"]
  }
}

Fail-closed: facilitator adds columns mid-week without map update → D2 fail before jq.

D3 — jq normalize

jq -n --slurpfile rows playtest-feedback/raw/discord_thread_2026-11-04.csv \
  --argfile map playtest-feedback/playtest_csv_column_map_v1.json \
  -f scripts/normalize_playtest_csv.jq \
  > playtest-feedback/normalized/playtest_feedback_normalized.json

Normalized output shape:

{
  "schema": "playtest_feedback_normalized_v1",
  "build_label": "2026-11-04-playtest-rc4",
  "rows": [
    {
      "build_id": "2026-11-04-playtest-rc4",
      "severity": "P0",
      "area": "progression",
      "summary": "Soft-lock after boss door",
      "discord_message_url": "https://discord.com/channels/…"
    }
  ],
  "counts": { "P0": 1, "P1": 3, "P2": 8, "total": 12 }
}

D4 — build_id crosswalk

BUILD="$(jq -r '.build_id' release-evidence/BUILD_RECEIPT.json)"
UNIQUE="$(jq -r '.rows[].build_id' playtest-feedback/normalized/playtest_feedback_normalized.json | sort -u)"
test "$UNIQUE" = "$BUILD"

Fail: split CSV by build; re-run D3 per file. Visible build label helps players confirm the same string.

D5 — blocker promotion policy

Count wednesday_smoke_allowed Promotion rule
P0 > 0 false unless waiver Block fest promotion until triaged
P1 > 5 true with p1_overflow: true YELLOW—document in receipt notes
P2 only true GREEN for smoke scope (still file receipt)

P0 waiver (when you must promote anyway):

## P0 waiver roster
| build_id | P0 summary | approver | UTC | waiver_id |
|----------|------------|----------|-----|-----------|
| 2026-11-04-playtest-rc4 | progression soft-lock | producer@studio | 2026-11-05T08:00:00Z | WAIVER-2026-11-04-01 |

Receipt field: "p0_waiver_id": "WAIVER-2026-11-04-01"Thursday row review column waiver Y/N.

D6 — playtest_csv_ingest_receipt_v1.json

{
  "schema": "playtest_csv_ingest_receipt_v1",
  "build_label": "2026-11-04-playtest-rc4",
  "source_csv_path": "playtest-feedback/raw/discord_thread_2026-11-04.csv",
  "source_csv_sha256": "cc99a435…",
  "normalized_path": "playtest-feedback/normalized/playtest_feedback_normalized.json",
  "column_map_path": "playtest-feedback/playtest_csv_column_map_v1.json",
  "counts": { "P0": 1, "P1": 3, "P2": 8, "total": 12 },
  "p0_blockers": ["progression: Soft-lock after boss door"],
  "cousin_receipts": {
    "weblate_utf8_handoff": "release-evidence/localization/WEBLATE_CSV_UTF8_HANDOFF_RECEIPT.json",
    "whisper_issue_tag": "release-evidence/playtest/whisper-tags/WHISPER_ISSUE_TAG_RECEIPT_fragment_03.json"
  },
  "gates": {
    "D1_csv_archived": "pass",
    "D2_column_map": "pass",
    "D3_jq_normalize": "pass",
    "D4_build_id_crosswalk": "pass",
    "D5_blocker_counts": "pass",
    "D6_receipt": "pass"
  },
  "csv_ingest_ok": true,
  "wednesday_smoke_allowed": true
}

Path: release-evidence/playtest/PLAYTEST_CSV_INGEST_RECEIPT.json.

C1 — cousin receipt crosswalk

Field Cousin (256) Cousin (266) This lesson (267)
Schema weblate_csv_utf8_handoff_receipt_v1 whisper_issue_tag_receipt_v1 playtest_csv_ingest_receipt_v1
Scope UTF-8 Weblate bytes VOD→GitHub quotes Discord CSV P0/P1 scope
Day After Tuesday ingest Post-Whisper Tuesday before Wednesday

Order: 267 jq ingest256 UTF-8 handoff (if Weblate) → 266 Whisper tags (per clip) → Wednesday smoke.

D6 — BUILD_RECEIPT promotion jq

RECEIPT="release-evidence/playtest/PLAYTEST_CSV_INGEST_RECEIPT.json"
jq -e '.csv_ingest_ok == true' "$RECEIPT"
jq -e '.gates.D4_build_id_crosswalk == "pass"' "$RECEIPT"
jq -e '.wednesday_smoke_allowed == true' "$RECEIPT"

BUILD_RECEIPT columns:

Column Example
playtest_csv_p0 1
playtest_csv_p1 3
csv_ingest_ok true
csv_ingest_receipt_path release-evidence/playtest/PLAYTEST_CSV_INGEST_RECEIPT.json

Mental model — triage vs encoding vs smoke

Stage Owner Proves
267 (Tuesday) jq + P0/P1 counts Async scope on BUILD_RECEIPT
256 UTF-8 handoff Weblate-safe bytes
266 Whisper tags Quote-only GitHub issues
Wednesday smoke S4–S6 Installed demo boots

Key takeaways

  1. Tuesday CSV ingest is not optional—Wednesday smoke does not read Discord sheets.
  2. Column map before jq—header drift silently empties rows[] (D2).
  3. build_id only from BUILD_RECEIPT (D4)—not thread title memory.
  4. P0 blocks promotion unless documented waiver (D5).
  5. Guide #29 preflight — ninety-second gate before this lesson.
  6. Tuesday ritual blog owns facilitator timer; 267 owns BUILD_RECEIPT promotion.
  7. 18 free async playtest tools — facilitator README templates.
  8. Cousin: Lesson 268 forward — CI cancel poison lane.
  9. Lesson 256 — encoding handoff after counts are honest.
  10. Thursday row review — diff playtest_csv_p0 week over week.

Common mistakes

  • Promoting Wednesday without filing D6 receipt.
  • Excel double-click save → UTF-16—fix in 256, but D1 still archives raw export.
  • jq script pinned to old headers after facilitator renames column (D2).
  • Counting P0 from Slack reactions instead of normalized JSON (D5).
  • Merging playtest_csv_ingest_receipt into weblate_csv_utf8_handoff_receipt.
  • Setting wednesday_smoke_allowed: true with open P0 and no waiver.

Troubleshooting

Symptom Lane
jq returns empty rows[] D2 column map
Two build_id values in CSV Split files; D4
P0 in sheet, receipt shows 0 Severity alias missing in map
Weblate mojibake after ingest Lesson 256
VOD issues without CSV row Lesson 266 parallel lane

Mini exercise (45 minutes)

  1. Export sample Discord CSV—log D1 sha256.
  2. Intentionally rename a header—confirm D2 fail.
  3. Fix map; run jq—verify counts.
  4. Crosswalk build_id (D4).
  5. Add one P0 row—confirm wednesday_smoke_allowed: false without waiver.
  6. File receipt; update BUILD_RECEIPT columns for Thursday review.

Continuity — November 2026 fest close-out + playtest triage (266–276)

Lesson Receipt focus
266 Whisper issue tag
267 (this) Discord CSV ingest
268 Unity CI cancel poison (forward)

Previous: Lesson 266 — Whisper playtest issue tag receipt
Next: Lesson 269 — Godot export VDF LF line ending steamcmd receipt

FAQ

Same as the Tuesday ritual blog?
Blog owns twelve-minute facilitator rhythm; 267 is the course milestone with BUILD_RECEIPT promotion jq.

Same as Guide #29?
Guide = ninety-second preflight; 267 = receipt culture + cousin crosswalk to 256/266.

Same as Lesson 256?
256 = UTF-8 Weblate encoding handoff; 267 = P0/P1 triage counts from Discord CSV.

Can we skip Tuesday if Whisper tags filed?
No—VOD tags and async CSV answer different questions; wire both receipts when both lanes run.

Google Sheets instead of Discord export?
Fine for D1—column map must match exported headers.

Does this replace Wednesday smoke?
No—binary smoke is separate; 267 sets scope honesty before smoke runs.


November promotion lies when P0 rows live only in Discord—jq normalize Tuesday, crosswalk build_id, file blocker counts, then run Wednesday smoke.