Opinion & Hot Takes Jul 12, 2026

Stop Unfreezing Holiday Builds Without a Numbered SHIP Exception List - 2026 Opinion

2026 Opinion—stop unfreezing holiday builds without a numbered SHIP exception list; ship_exception_list_receipt_v1.json, gates O1–O6, BUILD_RECEIPT ship_exception_list_ok, and smoke discipline during freeze.

By GamineAI Team

Stop Unfreezing Holiday Builds Without a Numbered SHIP Exception List - 2026 Opinion

Pixel-art hero for stop unfreezing holiday builds without numbered SHIP exception list opinion 2026

December 28: #general asks for a menu hitch fix before New Year. You reply "we're frozen." January 2: someone merges anyway because "it's just one line." January 4: January re-entry smoke fails on a binary nobody numbered, nobody smoked, and nobody tied to December defer FREEZE.

January 2027 is when verbal freeze dies. Contractors return, Discord volume spikes, and "just one hotfix" feels compassionate. Without a numbered SHIP exception list, compassionate merges become unfreeze theater—scope creep with holiday branding.

December defer playbook embeds ship_exceptions[] inside december_defer_receipt_v1.json. That is necessary but not sufficient for January discipline: teams need a standalone numbered list auditors can diff, jq can gate, and producers can refuse in public channels without reopening the entire defer table.

This Opinion & Hot Takes piece makes a narrow claim: during holiday freeze and through January re-entry, no binary promotion happens unless the change appears as the next integer in ship_exception_list_receipt_v1.json with smoke proof and label lineage—not Slack approval, not "we'll document later."

Non-repetition note: December defer owns Ship/Freeze/Defer scope table + H1–H6. Visible build label opinion owns player-facing label policy. Developer console opinion owns debug surfaces. January marketing cap #9 owns cash stop rules. This URL owns numbered SHIP exception list discipline + O1–O6 gates when freeze is still culturally "over" but receipts say otherwise.

Pair BUILD_RECEIPT beginner pipeline, Friday capstone row review, jq January reentry pointer, and 14 free January unfreeze audit tools.

Why this matters now (January 2027 / post-freeze hotfix culture)

  1. Discord unfreeze pressure — Communities treat "frozen" as mood, not receipt; January threads reward fast merges.
  2. Label drift without numbersVisible build label discipline fails when hotfixes skip exception rows—players report mystery builds.
  3. January smoke depends on freeze truth — Re-entry smoke compares to frozen baseline; unnumbered merges invalidate R1–R6 gates.
  4. Capstone pointer decayjq January pointer assumes November truth + honest label transition; shadow hotfixes break J4 without a paper trail.
  5. February RC week proximity7-day RC challenge starts from a baseline; unnumbered January patches poison day-one gates.

Direct answer: Maintain ship-exception-list-2027.md with monotonic integers SHIP-001…SHIP-N, file ship_exception_list_receipt_v1.json, set BUILD_RECEIPT ship_exception_list_ok: true, run smoke same day any exception touches player binaries—refuse merges that skip the next number.

The opinion in one paragraph

Holiday freeze is a contract with your future self. If you merge without incrementing a numbered SHIP exception, you did not make a compassionate fix—you made an unlogged promotion that forces January-you to debug folklore. Micro-studios cannot afford folklore during February fest prep. Numbered lists are not bureaucracy; they are the minimum correlation layer between Discord sentiment, BUILD_RECEIPT build_label, and the smoke ritual you claim to respect. Verbal "we're still frozen except this" is how teams accidentally ship three hotfixes and one feature while telling partners zero scope moved.

Who this opinion is for

Audience Why read
Solo founder moderating Discord Copy-paste "next SHIP number or no merge"
Producer Block January promote until list matches smoke
Engineer Wire jq gate before holiday CI
Community manager Stop being the informal exception approver

Beginner path — numbered list in 20 minutes

Copy into release-evidence/ops/ship-exception-list-2027.md:

# Numbered SHIP exception list (holiday freeze → January re-entry)
freeze_start: 2026-12-20
list_owner: producer
next_seq: 3

| Seq | Date | Summary | Touches binary? | build_label_before | build_label_after | Smoke same day? | Owner |
|-----|------|---------|-----------------|--------------------|--------------------|-----------------|-------|
| SHIP-001 | 2026-12-22 | OTLP proxy pin diligence | yes | fest-demo-2026-12-rc3 | fest-demo-2026-12-rc3-hotfix1 | yes | engineering |
| SHIP-002 | 2026-12-29 | Menu null ref P0 | yes | fest-demo-2026-12-rc3-hotfix1 | fest-demo-2026-12-rc3-hotfix2 | yes | engineering |
| SHIP-003 | (reserved) | — | — | — | — | — | — |

Beginner rules:

  1. Next merge must be next_seq—no SHIP-004 before SHIP-003 exists.
  2. Binary touch ⇒ smoke same day—no "smoke Monday."
  3. build_label_after must update when players receive new bits—pair visible label opinion.
  4. DEFER rows never appear here—only SHIP exceptions during freeze.

Success check: A non-engineer can answer "how many holiday hotfixes shipped?" by reading the highest Seq integer.

Developer path — gates O1–O6

Gate Check Fail action
O1 exceptions[] seq integers are 1..N contiguous Fix gaps before promote
O2 next_seq == N+1 in markdown matches JSON Sync list + receipt
O3 touches_binary: true rows have smoke_receipt_path same day Block CI promote
O4 build_label_after on last exception matches BUILD_RECEIPT Fix label lineage
O5 ship_exception_list_ok: true on BUILD_RECEIPT Fail jq bundle gate
O6 freeze_active true ⇒ no merge without new exception row same commit Reject PR

ship_exception_list_receipt_v1.json (template)

{
  "schema": "ship_exception_list_receipt_v1",
  "generated_at": "2027-01-02T21:00:00Z",
  "freeze_start": "2026-12-20",
  "freeze_end": "2027-01-02",
  "next_seq": 3,
  "exceptions": [
    {
      "seq": 1,
      "id": "SHIP-001",
      "date": "2026-12-22",
      "summary": "OTLP proxy pin diligence",
      "touches_binary": true,
      "build_label_before": "fest-demo-2026-12-rc3",
      "build_label_after": "fest-demo-2026-12-rc3-hotfix1",
      "smoke_required": true,
      "smoke_receipt_path": "release-evidence/smoke/SHIP_001_SMOKE.json",
      "owner": "engineering",
      "december_defer_pointer": "release-evidence/ops/DECEMBER_DEFER_RECEIPT.json"
    },
    {
      "seq": 2,
      "id": "SHIP-002",
      "date": "2026-12-29",
      "summary": "Menu null ref P0",
      "touches_binary": true,
      "build_label_before": "fest-demo-2026-12-rc3-hotfix1",
      "build_label_after": "fest-demo-2026-12-rc3-hotfix2",
      "smoke_required": true,
      "smoke_receipt_path": "release-evidence/smoke/SHIP_002_SMOKE.json",
      "owner": "engineering"
    }
  ],
  "pointers": {
    "december_defer": "release-evidence/ops/DECEMBER_DEFER_RECEIPT.json",
    "january_reentry_smoke": "release-evidence/smoke/JANUARY_REENTRY_SMOKE_RECEIPT.json"
  },
  "gates": {
    "O1": "pass",
    "O2": "pass",
    "O3": "pass",
    "O4": "pass",
    "O5": "pass",
    "O6": "pass"
  },
  "ship_exception_list_ok": true
}

Store canonical copy at release-evidence/ops/SHIP_EXCEPTION_LIST_RECEIPT.json.

BUILD_RECEIPT row

{
  "ship_exception_list_ok": true,
  "ship_exception_count": 2,
  "ship_exception_list_receipt": "release-evidence/ops/SHIP_EXCEPTION_LIST_RECEIPT.json",
  "build_label": "fest-demo-2026-12-rc3-hotfix2",
  "holiday_defer_ok": true
}

Fail-closed: If ship_exception_countexceptions[].length or last build_label_afterbuild_label, jq gate fails—prevents "we'll fix the list later."

verify_ship_exception_list.sh

#!/usr/bin/env bash
set -euo pipefail
LIST="${1:-release-evidence/ops/SHIP_EXCEPTION_LIST_RECEIPT.json}"
BR="${2:-BUILD_RECEIPT.json}"

jq -e '.schema == "ship_exception_list_receipt_v1"' "$LIST" || exit 1

# O1 contiguous seq
COUNT=$(jq '.exceptions | length' "$LIST")
for i in $(seq 1 "$COUNT"); do
  ACT=$(jq -r --argjson n "$i" '.exceptions[] | select(.seq == $n) | .seq' "$LIST")
  [[ "$ACT" == "$i" ]] || { echo "O1 fail: missing seq $i"; exit 2; }
done

# O3 smoke paths when binary touch
jq -e '
  [.exceptions[] | select(.touches_binary == true)
    | .smoke_receipt_path] | all(. != null and . != "")
' "$LIST" || { echo "O3 fail: binary touch without smoke path"; exit 3; }

LAST=$(jq -r '.exceptions[-1].build_label_after' "$LIST")
BL=$(jq -r '.build_label' "$BR")
[[ "$LAST" == "$BL" ]] || { echo "O4 fail: label mismatch list=$LAST receipt=$BL"; exit 4; }

jq -e '.ship_exception_list_ok == true' "$LIST" || exit 5
echo "ship_exception_list verify: OK"

Exit codes: 1 schema, 2 O1 gaps, 3 O3 smoke, 4 O4 label, 5 O5 latch.

Unfreeze theater vs honest SHIP exception

Behavior Theater Honest exception
"Just one line" merge No seq row SHIP-00N filed same day
"We'll smoke after break" smoke_required ignored smoke_receipt_path exists
Label unchanged after hotfix Players on mystery build build_label_after bumps + visible label
Slack ✅ from founder Informal approval Receipt owner + seq in thread
Batch three fixes One vague exception Three seq rows or one row with explicit scope hash
Reopen combat design DEFER violation Move to defer table—not SHIP list

Producer script for Discord:

Freeze still active per DECEMBER_DEFER_RECEIPT.json. Binary changes need the next SHIP-00N row in ship-exception-list-2027.md, same-day smoke, and build_label bump. Feature ideas go to DEFER—not SHIP.

Relationship to December defer receipt

December defer ship_exceptions[] and this opinion exceptions[] are cousins—not duplicates:

Field location Role
december_defer_receipt_v1.ship_exceptions[] Scope authority during Dec 20–Jan 2 freeze window
ship_exception_list_receipt_v1.exceptions[] Numbered audit trail for any binary promotion in freeze + January re-entry
holiday_defer_ok Boolean latch that freeze table was filed
ship_exception_list_ok Boolean latch that every binary change is numbered

Working dev pattern: On each SHIP merge, bump both receipts in one PR—defer receipt cross-reference pointer + list receipt new seq row.

# Cross-check counts
DEFER_EX=$(jq '.ship_exceptions | length' release-evidence/ops/DECEMBER_DEFER_RECEIPT.json)
LIST_EX=$(jq '.exceptions | length' release-evidence/ops/SHIP_EXCEPTION_LIST_RECEIPT.json)
[[ "$LIST_EX" -ge "$DEFER_EX" ]] || echo "WARN: list shorter than defer exceptions"

January re-entry integration

January re-entry smoke gate R2 frozen-label parity assumes the frozen label is the last SHIP exception's build_label_after or the pre-freeze label if zero exceptions:

Exception count frozen_baseline_label source
0 december_defer build_label_frozen
≥1 Last exceptions[].build_label_after

Document in JANUARY_REENTRY_SMOKE_RECEIPT.json notes field—prevents smoke comparing wrong baseline.

Pair January unfreeze audit tools gate U4 re-entry lineage—numbered list is upstream evidence.

jq gate sketch (CI graph)

jq -e '
  .ship_exception_list_ok == true
  and (.exceptions | length) == (.exceptions | map(.seq) | max)
' release-evidence/ops/SHIP_EXCEPTION_LIST_RECEIPT.json

Run after verify_january_reentry.sh in CI—marketing and pointer gates assume binary truth is numbered.

Proof table

Row Evidence GREEN when
P1 DECEMBER_DEFER_RECEIPT.json holiday_defer_ok: true
P2 SHIP_EXCEPTION_LIST_RECEIPT.json O1–O6 pass
P3 Per-exception smoke JSON Same calendar date as exception
P4 BUILD_RECEIPT build_label Matches last build_label_after
P5 Player-visible label screenshot Matches BUILD_RECEIPT
P6 JANUARY_REENTRY_SMOKE_RECEIPT.json Baseline notes cite SHIP list

Scenarios A–G

A — Zero exceptions (honest freeze)
exceptions: [], next_seq: 1. January smoke uses December frozen label. Any January merge without SHIP-001 is O6 fail.

B — One P0 hotfix Dec 22
SHIP-001 only. Label hotfix1. Visible label updated. Discord thread links seq row—no further merges until January re-entry plan.

C — "Just config" change that still moves depot
Counts as touches_binary: true—players download new bits. Seq row required; config-only excuse is theater.

D — Art swap in Steam backend only
If no player binary changes, not a SHIP exception—track in marketing defer. If build depot changes, SHIP row required.

E — January 3 merge without list update
Fail O6; block February RC label evening until list reconciled.

F — Partner diligence asks holiday changes
Export numbered list PDF from markdown—faster than reconstructing Git history.

G — Community manager approves hotfix in DMs
Policy: only producer/engineering can assign next seq—CM approval is not O6-compliant.

Engine-specific exception notes (working dev)

Holiday hotfixes look different per stack—the seq row format stays identical:

Engine Binary touch signal Label source to pipe into build_label_after
Unity Player build artifact hash changes Same BUILD_LABEL env as Addressables CI challenge cousins
Godot Export preset output changes config/version + export template string
Unreal Cooked staging folder changes PROJECT_VERSION + Insights OTLP cousins if telemetry touched
GameMaker YYZ / zip changes GM_BUILD_LABEL per setlive evening
Ren'Py game/ bytecode bundle changes config.version per VN freeze challenge

Rule: If CI publishes a new depot ID, you need a seq row—even when the diff is "one script."

Git tag pairing (optional but valuable)

Tag ship-001-20261222 at the exception merge commit. jq can verify tag exists:

git tag -l 'ship-*' | wc -l
# should be >= exception count in receipt

Tags are not a substitute for JSON receipts—they are human-readable anchors for partners browsing Git without opening release-evidence/.

January week-by-week enforcement calendar

Week Policy emphasis
Dec 20–Jan 2 O6 strict—freeze window; any binary ⇒ new seq same day
Jan 3–5 Last SHIP label becomes re-entry smoke baseline; no unnumbered "cleanup" merges
Jan 6–12 Contractors return—DEFER feature work; SHIP only for P0
Jan 13–19 February RC label week—list must be GREEN before label evening
Jan 20+ Archive list to release-evidence/ops/archive/ after Monday capstone reconciliation forward #11

Counterarguments (and rebuttals)

"Numbered lists slow P0 fixes."
SHIP-001 takes five minutes—debugging unnumbered January smoke takes five hours.

"We're two people—we trust each other."
Trust does not help players report which build crashed—visible label needs lineage.

"December defer already has ship_exceptions."
Embed is scope; standalone numbered list is audit diff friendly for January partners and jq.

"Freeze ended Jan 2—why list still?"
January re-entry week still compares against freeze baseline; list closes the narrative through first Thursday row review.

"Exceptions are embarrassing."
Embarrassing is silent drift—numbered lists show discipline.

Engineering floor + marketing cap coupling

December engineering floor caps hours for SHIP work—each exception should include estimated_engineering_h in notes so blackout caps are not silently blown.

January marketing cap SR2 can reference ship_exception_list_ok—pause paid social if list jq fails while store still promises stable demo.

Printable producer card

## SHIP exception quick gate (pin in #engineering)

1. Is freeze/active re-entry policy still on? → defer table link
2. Next seq: SHIP-___ from ship-exception-list-2027.md
3. Touches binary? → smoke today + label bump
4. Update SHIP_EXCEPTION_LIST_RECEIPT.json + BUILD_RECEIPT same PR
5. Run verify_ship_exception_list.sh → paste exit 0
6. Discord reply with seq id—not "merged!"

Common mistakes

Using SHIP list for DEFER items — Combat rebalance belongs in defer table, not exception integers.

Skipping numbers "because revert" — Reverts still get seq rows; never reuse integers.

One exception row for five files — Scope hash or split rows—auditors read seq as atomic promotions.

Smoke next week — O3 is same calendar day; overnight is fine, next sprint is not.

Label bump without visible UI — Policy violation per visible label opinion—exception incomplete.

Merging on Jan 3 before re-entry smoke — Invalidates baseline story; run smoke on last SHIP label first.

Outbound references

Related GamineAI reads

Key takeaways

  1. Verbal freeze is theater—numbered SHIP-001…N rows are the minimum honest unfreeze log.
  2. ship_exception_list_receipt_v1.json gates O1–O6; standalone from but paired with December defer.
  3. Binary touch ⇒ same-day smoke—no January "we'll verify later."
  4. build_label_after on the last exception must match BUILD_RECEIPT + visible label.
  5. ship_exception_list_ok is a separate latch from holiday_defer_ok.
  6. Discord "just one hotfix" is the enemy—reply with next seq or DEFER table.
  7. January re-entry smoke baseline comes from last SHIP label or frozen label if zero exceptions.
  8. verify_ship_exception_list.sh gives CI one exit code for holiday promotion discipline.
  9. Producers pin the quick gate card—engineers should not be informal exception approvers.
  10. Monday capstone reconciliation expects numbered list GREEN before February upload.
  11. January marketing cap can pause ads when list jq fails.
  12. Live fest week: HOTFIX exception opinion continues numbered discipline with HOTFIX-001…N—cousin to SHIP, distinct schema.
  13. Zero exceptions is valid—any unlogged merge is not.

FAQ

Is this replacing December defer ship_exceptions?
No—pair both in the same PR; defer owns scope window, list owns numbered audit trail.

Do store-only changes need SHIP numbers?
Only when player binaries change; metadata-only may be marketing defer—document honestly.

What if we need SHIP-003 and SHIP-004 same day?
Allowed—ints must stay contiguous; two smokes if both touch binaries.

Can contractors assign seq numbers?
Only if named owner in receipt policy—default engineering lead + producer sign-off.

Does freeze ending Jan 2 retire the list?
Keep through January re-entry smoke and first Thursday row review—then archive to release-evidence/ops/archive/.

How does this relate to visible build label opinion?
Every build_label_after bump requires player-visible update—exceptions without label visibility are incomplete.

What about non-Steam demos?
Same policy—itch/HTML5 SKUs need channel suffix labels per GX resource cousins.