Lesson 272: Ren'Py SteamPipe Upload Wall-Clock Receipt on BUILD_RECEIPT (2026)

Direct answer: Before November 2026 Ren'Py teams treat a long SteamCMD run as auth failure, promote steampipe_wall_clock_receipt_v1.json with L1–L6 gates—pin the Ren'Py distribution to build_label, capture UTC start/end + exit code, retain a redacted SteamCMD excerpt, wire upload_duration_min on BUILD_RECEIPT, and fail-close wall_clock_log_ok (not setlive_allowed). Pair Ren'Py wall-clock preflight (Guide #34), October wall-clock trend, and cousin Lesson 262 (partner identity—not duration).

Lesson hero for Ren'Py SteamPipe upload wall-clock receipt

Why this matters now (November Ren'Py close-out uploads)

November 2026 visual-novel teams pass Lesson 262 partner checks on a cousin engine, upload from Ren'Py Build Distributions, and watch Steamworks stay Queued for forty minutes. Discord declares the login dead at minute twelve—while steamcmd already exited 0. Upload duration ≠ auth failure ≠ validation wait ≠ branch promotion—four clocks, one receipt lane.

Lesson 271 wires diligence hero mirror; 272 wires Ren'Py SteamPipe wall-clock so Thursday row review diffs upload_duration_min beside smoke—not founder memory of how long Queued lasted.

Beginner path (label → timestamps → excerpt → receipt)

Step Action Success check
1 Match Ren'Py zip to build_label L1 distribution label
2 Record UTC before/after SteamCMD L2–L3 timestamps + exit code
3 Save redacted excerpt L4 auth vs success class
4 Append validation poll jsonl Processing → Valid separate
5 Attach duration to BUILD_RECEIPT L5 upload_duration_min
6 File receipt + BUILD_RECEIPT column wall_clock_log_ok: true

Time: ~50 minutes first wall-clock wiring; ~5 minutes when Guide #34 preflight template exists.

Developer path (gates L1–L6)

Gate Check Fail when
L1 Ren'Py package + build_label Zip name drift vs BUILD_RECEIPT
L2 UTC start before SteamCMD Missing RFC 3339 start
L3 End, duration, exit code Negative duration; exit omitted
L4 Redacted diagnostic excerpt Password in evidence
L5 BUILD_RECEIPT duration row Verbal “upload took forever”
L6 Receipt + BUILD_RECEIPT Promote before L4 classification

Align with Guide #34—guide = five-minute gate; 272 = BUILD_RECEIPT milestone with jq fail-closed promotion.

Cousin receipt crosswalk

Field Cousin (partner Lesson 262) Cousin (queue trend blog) This lesson (272)
Schema gm_steam_partner_receipt_v1 steampipe_queue_wall_clock_receipt_v1 steampipe_wall_clock_receipt_v1
Scope Partner identity Queue policy narrative Ren'Py upload log BUILD_RECEIPT
Path release-evidence/steam/ release-evidence/steam/ release-evidence/steam/renpy-upload/

Do not merge schemas—reference paths in cousin_receipts only.

L1 — Ren'Py distribution label

jq -e '.build_label == "nextfest-nov-2026-rc2"' release-evidence/BUILD_RECEIPT.json
test -f dist/my-visual-novel-nextfest-nov-2026-rc2.zip

Producer rule: Ren'Py Build Distributions output name must include the same build_label string as BUILD_RECEIPT and visible build label in-game.

L2–L3 — Wall-clock around SteamCMD

PowerShell release runner:

$evidence = "release-evidence/steam/renpy-upload"
New-Item -ItemType Directory -Force $evidence | Out-Null

$started = (Get-Date).ToUniversalTime()
& steamcmd +login $env:STEAM_PARTNER_USER $env:STEAM_PARTNER_PASS `
  +run_app_build "C:\build\steam\app_build_renpy.vdf" +quit `
  *> "$evidence\steamcmd-upload.log"
$exitCode = $LASTEXITCODE
$finished = (Get-Date).ToUniversalTime()
$durationSeconds = [math]::Round(($finished - $started).TotalSeconds)

Log started_at_utc, finished_at_utc, duration_seconds, exit_code—never passwords.

L4 — Redacted excerpt + classification

Signal Classification Next action
Login error + non-zero exit auth_failure Lesson 262 lane
VDF/depot error + non-zero configuration_failure Fix mapping; do not retry unchanged
Success marker + exit 0 upload_command_success Poll Steamworks separately
Exit 0, no marker ambiguous_success Inspect full restricted log

Queued in Steamworks after exit 0 is not auth failure—append validation poll rows.

L5 — BUILD_RECEIPT upload_duration_min

{
  "build_label": "nextfest-nov-2026-rc2",
  "steampipe_upload": {
    "duration_min": 18.4,
    "exit_code": 0,
    "classification": "upload_command_success",
    "receipt": "release-evidence/steam/renpy-upload/STEAMPIPE_WALL_CLOCK_RECEIPT.json"
  }
}

Pair October wall-clock trend Q1–Q6 queue policy—272 owns Ren'Py BUILD_RECEIPT column.

L6 — steampipe_wall_clock_receipt_v1.json

{
  "schema": "steampipe_wall_clock_receipt_v1",
  "engine": "renpy",
  "build_label": "nextfest-nov-2026-rc2",
  "distribution_path": "dist/my-visual-novel-nextfest-nov-2026-rc2.zip",
  "app_build_vdf": "build/steam/app_build_renpy.vdf",
  "started_at_utc": "2026-11-12T18:04:11.0000000Z",
  "finished_at_utc": "2026-11-12T18:22:36.0000000Z",
  "duration_seconds": 1105,
  "exit_code": 0,
  "classification": "upload_command_success",
  "log_excerpt": "release-evidence/steam/renpy-upload/steamcmd-upload-excerpt.log",
  "validation_poll_log": "release-evidence/steam/renpy-upload/validation-poll.jsonl",
  "cousin_receipts": {
    "partner_identity": "release-evidence/steam/GM_STEAM_PARTNER_RECEIPT.json",
    "queue_wall_clock": "release-evidence/steam/STEAMPIPE_QUEUE_WALL_CLOCK_RECEIPT.json",
    "build": "release-evidence/BUILD_RECEIPT.json"
  },
  "gates": {
    "L1_distribution_label": "pass",
    "L2_started_at_utc": "pass",
    "L3_end_duration_exit": "pass",
    "L4_redacted_excerpt": "pass",
    "L5_build_receipt_row": "pass",
    "L6_receipt": "pass"
  },
  "wall_clock_log_ok": true,
  "setlive_allowed": false
}

Pin at release-evidence/steam/renpy-upload/STEAMPIPE_WALL_CLOCK_RECEIPT.json.

Fail-closed jq (CI tail)

REC=release-evidence/steam/renpy-upload/STEAMPIPE_WALL_CLOCK_RECEIPT.json
jq -e '
  .schema == "steampipe_wall_clock_receipt_v1" and
  .engine == "renpy" and
  (.duration_seconds | type == "number" and . >= 0) and
  (.gates | to_entries | all(.value == "pass")) and
  .wall_clock_log_ok == true and
  .setlive_allowed == false
' "$REC"

Exit 0 proves log evidence complete—it does not authorize setlive.

Mini exercise (45 minutes)

  1. Run staging upload; capture L2–L4.
  2. Compute duration from UTC timestamps—not console approximations.
  3. Append one Processing and one Valid poll row.
  4. Wire L5 on BUILD_RECEIPT.
  5. Validate L6 jq; confirm setlive_allowed stays false.

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

Lesson Receipt focus
271 Blender diligence mirror
272 (this) Ren'Py SteamPipe wall-clock
273 Audacity loudness spot (forward)

Previous: Lesson 271 — Blender diligence hero GLB rclone mirror receipt
Next: Lesson 273 — Audacity playtest VOD loudness spot Whisper receipt

FAQ

Same as Guide #34?
Guide = five-minute L1–L6 preflight; 272 owns steampipe_wall_clock_receipt_v1 BUILD_RECEIPT column + jq promotion.

Same as October wall-clock blog?
Blog owns queue policy + steampipe_queue_wall_clock_receipt_v1 narrative; 272 owns Ren'Py engine row on BUILD_RECEIPT.

Same as Lesson 262?
262 = partner identity P1–P6; 272 = upload duration L1–L6 after partner GREEN.

Does exit 0 mean setlive?
No—receipt keeps setlive_allowed: false; branch promotion needs Valid build + Lesson 270 cousin checks for GameMaker SKUs.

Re-upload when Queued?
No—append validation poll; see 14 free SteamPipe observability tools.

Related GamineAI reads


Queued is not failed—capture UTC wall-clock, classify the excerpt, file steampipe_wall_clock_receipt_v1, then poll validation before any branch promotion.