# Waitlist Override Redemption — Analysis and Implementation Plan

**Filed:** August 30, 2026 (Andrew + Claude)
**Verified against:** `ab5d702` (clean tree)
**Triggering incident:** Season 48 (Coed 2026 – Early Fall). Five spots reserved and offered to
waitlisted players; none of them can complete registration.
**Status:** ⬜ Analysis complete, not built. Needs an Andrew + Caleb decision on §7 before coding.

---

## 1. Headline

**An override cannot be redeemed once a season is hidden or past its registration close date — which
is exactly and only when overrides are ever issued.**

The waitlist feature exists to backfill a roster when an active player drops out mid-season. That
event is *always* after `registration_close` and *always* after an admin has flipped
`registration_visible` off. So the redemption path has never worked in the scenario the feature was
built for. It would only work during the open registration window, when nobody needs it.

The waitlist machinery itself is correct. Offers are created correctly, `reserved_spots` is
incremented and decremented correctly, the notification sends correctly. **Only redemption is
broken.** Nothing in the season registration path ever asks *"does this player hold a valid
override?"* before rejecting them.

---

## 2. Live state that produced this (season 48, from `/admin/ccsoccer/season/48`)

| Field | Value |
|---|---|
| Registration period | May 23 – **Aug 7, 2026** (closed 23 days ago) |
| `registration_visible` | **FALSE** |
| `active` | TRUE |
| `max_players` | 144 |
| Registered (`paid` + `active`) | 140 |
| `reserved_spots` | **5** |
| Spots remaining (displayed) | **−1** = `144 − 140 − 5` |
| On waitlist | 16 |

The `−1` is a red herring — see §4.5. Capacity is not what is blocking anyone.

---

## 3. What already works (do not rebuild this)

Establishing this so the fix stays surgical:

- **`CancelRegistrationForm:429-436`** — when a player cancels and a waitlist exists for the season,
  `reserved_spots` is incremented. The mid-season drop-out correctly protects a seat.
- **`WaitlistController::offerSpot()` → `WaitlistManagerService::offerSpot()`
  (`WaitlistManagerService:110-134`)** — marks the waitlist entry `offered`, creates an Override via
  `OverrideManagerService::createOverride()` with `override_type = 'waitlist'` and a 7-day
  expiration, invalidates the player's cache tag, sends the notification. All correct.
- **`OverrideManagerService::getValidSeasonOverride()`** — resolves and self-expires correctly.
- **`OrderCompleteSubscriber:405-424`** — *does* recognise the override, mark it used, and decrement
  `reserved_spots`. The logic is right; its **position** in the method is wrong (§5).

So: the key is minted correctly. The lock doesn't have a keyhole.

---

## 4. The blockers, in the order a player hits them

### 4.1 — W1 · The emailed link is rejected (this is what your players are seeing)

`NotificationService::sendWaitlistSpotOffered()` (`:1587-1610`) builds the offer email around a
single link:

```php
$register_url = \Drupal\Core\Url::fromRoute('ccsoccer.register.add_season',
  ['season' => $season->id()], ['absolute' => TRUE])->toString();
```

That route (`ccsoccer.routing.yml:1051`, `/register/season/{season}`, `_user_is_logged_in: TRUE`)
lands on `RegistrationController::addSeasonToCart()`. Its first guard, **`RegistrationController:1129-1135`**:

```php
// Guard: season must still be open for registration.
if (!$season->get('registration_visible')->value) {
  $this->messenger()->addError($this->t('Registration for @season is no longer available.', [
    '@season' => $season->label(),
  ]));
  return $this->redirect('ccsoccer.register');
}
```

Season 48 has `registration_visible = FALSE`. **Every offered player gets "Registration for Coed 2026
– Early Fall is no longer available."** and is bounced to `/register`.

**Provenance:** introduced in `a019bb2` (June 4, 2026, *"feat: enforce registration_visible and
active flags at checkout"*). That commit added three correct guards — cart-add for seasons, cart-add
for tournaments, and order-completion — and none of them carry an override exemption. It is a
straightforward oversight, not a design disagreement: the override concept predates it by months.

### 4.2 — W2 · The fallback page doesn't list the season either

`RegistrationController::register()` at **`:214-220`**:

```php
// Load only seasons with registration_visible = TRUE
$season_storage = $this->entityTypeManager->getStorage('season');
$season_query = $season_storage->getQuery()
  ->condition('registration_visible', TRUE)
  ->accessCheck(FALSE);
```

The season is excluded at the query level, before any per-user state is computed. So the player
bounced by W1 arrives at a page that does not mention season 48 at all — no error, no card, no
explanation. From the player's side this reads as "the league lost my spot."

### 4.3 — W3 · Even with visibility on, the closed date kills the card

`getSeasonState()` (`:587-655`) computes `'registration_open' => $season->isRegistrationOpen()`
(`:596`). `Season::isRegistrationOpen()` (`Season.php:366-388`) returns FALSE because today is past
the Aug 7 `registration_close`. Then at **`:637-654`** the state is stamped `status = 'closed'`.

`buildSeasonCard()` (`:664+`) branches in this order:

| Line | Branch | Renders |
|---|---|---|
| 665 | `$state['is_registered']` | "You Are Registered" |
| **714** | **`$state['status'] === 'closed'`** | **"Registration Deadline Has Passed" — no button** |
| 724 | `$state['status'] === 'not_yet_open'` | "Registration Opens …" |
| **735** | **`$state['registration_open']`** | *(everything below is nested here)* |
| 737 | └ `$state['has_override'] or !$state['is_full']` | "Spot Reserved For You" + Register button |
| 788 | └ `waitlist_status === 'pending'` | "You Are On The Waitlist" |
| **794** | └ **`waitlist_status === 'offered'`** | **"Spot Offered From Waitlist" + Register button** |
| 826 | └ `else` | "Season Full" + Join Waitlist button |

**The two branches written specifically for override holders — 737 and 794 — are nested inside
`elseif ($state['registration_open'])` at 735, which is unreachable once `status` is `closed` at
714.** The "Spot Reserved For You" and "Spot Offered From Waitlist" messaging is dead code from the
moment `registration_close` passes.

This is a second, independent blocker. Fixing W1 alone leaves the card mute; fixing W3 alone leaves
the button 404-equivalent. **Both must land together.**

### 4.4 — W4 · Order completion burns the override, then rejects the registration

See §5 — this is the one with money attached.

### 4.5 — Not a blocker: capacity

Worth stating explicitly so nobody "fixes" it:

- `Season::isFull()` / `getSpotsRemaining()` (`Season.php:339-361`) return `144 − 140 − 5 = −1`,
  i.e. full. **Neither `addSeasonToCart()` nor the cart subscriber consults them.**
- `CartEventSubscriber::onCartEntityAdd()` (`:91-124`) counts `status = 'paid'` only against
  `max_players`: `140 < 144` → passes.
- `OrderCompleteSubscriber:447-455` does the same `paid`-only count: passes.

So there are four seats of real headroom under `max_players`, and the displayed `−1` never gates
anything on this path. (The three-checkpoints-disagree problem is **P6 / Decision 2** in
`OUTSTANDING_ISSUES.md` and is deliberately out of scope here — see §11.)

---

## 5. W4 in detail — the data-integrity bug

`OrderCompleteSubscriber::createSeasonRegistration()` currently runs in this order:

```php
// :405-424  — CONSUME
$override = $override_manager->getValidSeasonOverride($user->id(), $season->id());
if ($override) {
  $override_manager->markOverrideUsed($override);          // status → 'used', saved
  $reserved_spots = (int) $season->get('reserved_spots')->value;
  if ($reserved_spots > 0) {
    $season->set('reserved_spots', $reserved_spots - 1);   // saved
    $season->save();
  }
}

// :426-428  — reload
$season = $this->entityTypeManager->getStorage('season')->load($season->id());

// :430-446  — REJECT
if (!$season->get('active')->value || !$season->get('registration_visible')->value) {
  // logs "Payment was collected — admin follow-up required"
  $order->setData('ccsoccer_season_registration_failed', ['reason' => 'season_inactive', ...]);
  $order->save();
  return;                                                  // no registration created
}

// :447-478 — capacity check, same shape, same `return`
```

**The override is consumed before the guards that can reject the registration.** If either guard
fires, the outcome is:

- payment captured ✅
- `Override.status = 'used'` — unrecoverable without manual edit ❌
- `Season.reserved_spots` decremented — the seat that was being held for this player is gone ❌
- no `ccsoccer_registration` row ❌
- waitlist entry still `offered` (nothing resets it)
- order tagged `ccsoccer_season_registration_failed`, surfacing only in dblog (**S3** — the admin
  report for these flags is not built)

The order flag is a good instinct and should stay, but it records the *money*, not the *override* or
the *seat*. An admin resolving one of these by hand today would have to notice, unprompted, that
`reserved_spots` needs incrementing back and the Override needs flipping from `used` to `active`.

**Practical exposure right now:** low but non-zero — a player who added season 48 to their cart
before it was hidden and pays afterward. **After the §7 fix it becomes zero for the override path**
(the guard will no longer fire for override holders), but the ordering should be corrected anyway,
because the `active = FALSE` and capacity guards can still fire.

This is the same family as **D3** in `OUTSTANDING_ISSUES.md` §P3 (tournament completion has no guard
at all) — worth fixing in the same pass if cheap, but not a blocker for this work.

---

## 6. Immediate ops workaround — no deploy needed

**Set `Registration Visible = TRUE` on season 48** (Edit Season). This unblocks the emailed offer
links today.

Why it does *not* re-open public registration:

| Path | With `registration_visible = TRUE` and close date passed |
|---|---|
| `/register` card | `status = 'closed'` → branch 714 → "Registration Deadline Has Passed", **no button, no Join Waitlist button** |
| Emailed link `/register/season/48` | `addSeasonToCart()` checks `registration_visible` ✅, `active` ✅, age, photo — **never checks `isRegistrationOpen()`** → proceeds to cart |
| Cart add | `140 paid < 144` → passes |
| Order complete | `registration_visible` now TRUE ✅, `140 < 144` ✅ → **registration created, override marked used, `reserved_spots` decremented** |

So the season is invisible-but-reachable, which is very nearly the behaviour we actually want.

**Two caveats before doing this:**

1. **`/register/season/48` becomes open to anyone who has or guesses the URL.** It is not restricted
   to override holders — `addSeasonToCart()` does not check for one. Exposure is bounded by the four
   seats of headroom under `max_players` (144 − 140), and by the URL not being published anywhere,
   but it is real. This is the single strongest argument for doing §7 properly rather than living on
   the workaround.
2. **Check the Overrides page first.** `offerSpot()` grants **7 days**. Offers made before ~Aug 23
   are already expired, and `getValidSeasonOverride()` silently flips them to `expired` on read.
   Anything stale needs re-issuing (or extending via `/admin/ccsoccer/override/{id}/extend`) or the
   player will hit "not available" for a different reason. Related: **S4** — a waitlist entry stays
   `offered` forever with no cron pass, so some of the 16 waitlist / 5 reserved may be stale in both
   directions.

**Revert plan:** flip `Registration Visible` back to FALSE once the five seats are filled or the
offers lapse.

---

## 7. The fix

### 7.1 The design decision to make first

> **An override is a key that opens a closed door for one named player.**

Concretely, a valid override for `(player, season)` should exempt that player from:

- ✅ `registration_visible = FALSE` — **yes.** This is the whole point.
- ✅ `registration_close` in the past — **yes.** Same reason.
- ✅ `Season::isFull()` — **yes**, already the intent; `reserved_spots` is what makes the seat real.
- ❌ `active = FALSE` — **no.** An inactive season is archived/deleted-ish; nobody should register.
- ❌ `max_players` hard cap at order completion — **no.** Keep as the last-resort backstop.
- ❌ Age/gender eligibility — **already handled separately** by `override_type = 'age'` via
  `getValidAgeOverride()` (`RegistrationController:1554-1560`). Don't merge the two concepts.

**This needs Andrew + Caleb sign-off before coding**, because it is a genuine policy statement, not
a bug fix. The alternative framing — "an override only exempts capacity, and admins must re-open the
season to use one" — is defensible and much cheaper (§7.3), but it makes every mid-season backfill a
two-step manual dance with a public-exposure window, which is what we have today.

### 7.2 Patch set — five touch points

All in `web/modules/custom/ccsoccer/`. Suggested branch: `fix/waitlist_override_redemption`.

---

**Patch 1 — `src/Controller/RegistrationController.php:1129-1135` (addSeasonToCart)**
*The load-bearing fix. Without this nothing else matters.*

```php
// BEFORE
if (!$season->get('registration_visible')->value) {
  $this->messenger()->addError($this->t('Registration for @season is no longer available.', [
    '@season' => $season->label(),
  ]));
  return $this->redirect('ccsoccer.register');
}

// AFTER
$override_manager = \Drupal::service('ccsoccer.override_manager');
$has_override = !$this->currentUser()->isAnonymous()
  && (bool) $override_manager->getValidSeasonOverride($this->currentUser()->id(), $season->id());

// A valid override is a key for a closed door — it exempts the holder from the
// visibility gate and the registration_close date, but never from `active`.
// See WAITLIST_OVERRIDE_REDEMPTION_PLAN.md §7.1.
if (!$season->get('registration_visible')->value && !$has_override) {
  $this->messenger()->addError($this->t('Registration for @season is no longer available.', [
    '@season' => $season->label(),
  ]));
  return $this->redirect('ccsoccer.register');
}
```

The `active` guard immediately below (`:1136-1141`) is **left exactly as is** — no exemption.

> **Note:** this method is also reached from `available()` (`:127`, invite-token arrivals) and from
> `getSeasonState`-driven card buttons. Injecting `ccsoccer.override_manager` properly via the
> constructor is cleaner than `\Drupal::service()`, but the class already uses `\Drupal::service()`
> for this exact service at `:181` and `:1556`, so matching local style is acceptable if the
> implementer prefers a minimal diff. **Pick one and be consistent within the file.**

---

**Patch 2 — `src/Controller/RegistrationController.php:214-220` (register listing)**

The `registration_visible` filter cannot stay a pure query condition, because seasons the current
user holds an override for must also appear. Two options:

**2a (recommended) — union the override'd season IDs into the load:**

```php
// Load seasons with registration_visible = TRUE, PLUS any season this user
// holds a valid override for (which is how a hidden, closed season reaches
// exactly the players who were offered a spot).
$season_storage = $this->entityTypeManager->getStorage('season');
$season_ids = $season_storage->getQuery()
  ->condition('registration_visible', TRUE)
  ->accessCheck(FALSE)
  ->execute();

if (!$current_user->isAnonymous()) {
  foreach ($override_manager->getUserOverrides($current_user->id()) as $override) {
    // getUserOverrides() already filters to valid (active + unexpired).
    if (!$override->get('season')->isEmpty()) {
      $sid = $override->get('season')->target_id;
      $season_ids[$sid] = $sid;
    }
  }
}
$seasons = $season_storage->loadMultiple($season_ids);
```

`OverrideManagerService::getUserOverrides()` already exists and already filters to valid overrides,
so this adds one service call and no new query patterns. Note it returns **both** season and
tournament overrides — the `->get('season')->isEmpty()` check is required.

**2b — drop the query filter, gate per-season inside the loop.** Simpler to read, but loads every
season on every `/register` hit. With ~50 seasons in the table today that is survivable but wasteful.
**Recommend 2a.**

⚠️ **Cache:** `/register` output must vary per user for this to be correct. `offerSpot()` already
invalidates `user:{uid}:registrations` (`WaitlistManagerService:130`), so confirm that tag is
actually attached to the `/register` render array — **if it is not, the fix will appear to work for
the implementer and silently fail for real players behind page cache.** Worth an explicit check;
this is the most likely way this patch set ships broken.

---

**Patch 3 — `src/Controller/RegistrationController.php:596` + `:637` (getSeasonState)**

`has_override` is already computed at `:618-621`, but the closed-date logic at `:637` overwrites
`status` without consulting it. Make the override survive:

```php
// In the "Determine status" block at :637, wrap the whole thing:
if (!$state['registration_open'] && !$state['has_override']) {
  // ... existing not_yet_open / closed logic, unchanged ...
}
```

Effect: an override holder keeps `status = 'open'`, and `registration_open` stays FALSE in the state
array (which is still true and still useful for messaging). Patch 4 then needs to branch on the
override rather than on `registration_open`.

**Also add** `'has_valid_override_seat' => ...` or similar if you want the card to distinguish
"reserved for you" from "offered from waitlist" — but `waitlist_status` at `:628-632` already carries
that, so probably unnecessary.

---

**Patch 4 — `src/Controller/RegistrationController.php:714` and `:735` (buildSeasonCard)**

Hoist the override branch above the closed/not-yet-open branches:

```php
if ($state['is_registered']) {
  // ... unchanged (line 665)
}
elseif ($state['has_override']) {
  // NEW — highest-priority non-registered branch. An override holder sees a
  // live Register button regardless of visibility or the close date.
  // Message text depends on origin: waitlist offer vs. admin-granted.
  $message = $state['waitlist_status'] === 'offered'
    ? 'Spot Offered From Waitlist'
    : 'Spot Reserved For You';
  // ... status-box + Register button, lifted verbatim from the existing
  //     :737-825 body (product lookup, anonymous vs. logged-in link) ...
}
elseif ($state['status'] === 'closed') {
  // ... unchanged (line 714)
}
// ... rest unchanged
```

Then **delete** the now-dead `$state['has_override']` test from the condition at `:737` (it becomes
`if (!$state['is_full'])`) and **delete** the `waitlist_status === 'offered'` branch at `:794`,
whose body has moved up. Leaving duplicates in place is how this drifts.

⚠️ An expiry hint in the card would help a lot here — the player has 7 days and currently only the
email says so. `$override->get('expiration_date')` is available; consider threading it into
`$state`.

---

**Patch 5 — `src/EventSubscriber/OrderCompleteSubscriber.php:405-455` (ordering + exemption)**

Two changes in one edit:

1. **Move the override consumption block (`:405-424`) to *after* both hard blocks**, so an override
   is never burned by a registration that then fails to be created.
2. **Exempt override holders from the `registration_visible` half of the guard at `:433`** — the
   `active` half stays absolute.

```php
// 1. LOOK UP the override (do not consume yet).
$override_manager = \Drupal::service('ccsoccer.override_manager');
$override = $override_manager->getValidSeasonOverride($user->id(), $season->id());

$season = $this->entityTypeManager->getStorage('season')->load($season->id());

// 2. HARD BLOCK — `active` is absolute; `registration_visible` yields to a valid override.
if (!$season->get('active')->value
    || (!$season->get('registration_visible')->value && !$override)) {
  // ... existing logger + setData('ccsoccer_season_registration_failed') + return, unchanged ...
}

// 3. HARD BLOCK — capacity backstop, unchanged (:447-478).

// 4. CONSUME the override, now that the registration is definitely being created.
if ($override) {
  $override_manager->markOverrideUsed($override);
  $reserved_spots = (int) $season->get('reserved_spots')->value;
  if ($reserved_spots > 0) {
    $season->set('reserved_spots', $reserved_spots - 1);
    $season->save();
  }
  $this->logger->notice('Override @oid used for user @uid season @sid', [...]);
}
```

⚠️ **Watch the reload.** The current code reloads `$season` at `:428` *because* the consume block
saved it. After reordering, the reload must still happen before the guards (season may have been
edited mid-checkout), and the consume block at the end must operate on a `$season` that is fresh —
re-read `reserved_spots` off the reloaded object, which the snippet above does.

⚠️ **Also mark the waitlist entry.** Nothing currently moves `ccsoccer_waitlist.status` from
`offered` to a terminal state on successful registration. Add it in the consume block:
`$waitlist_manager->getUserWaitlistEntry($user->id(), $season->id())` → mark registered/cancelled.
Without this the entry sits `offered` forever (**S4**) and the admin waitlist page keeps showing a
player who is already on a roster. **Check whether `Waitlist` has a suitable status value before
assuming — it may need one added.**

### 7.3 Cheaper alternative, if §7.1 is rejected

If the board would rather not give overrides door-opening power: keep all guards absolute, and
instead add an admin action **"Open for waitlist redemption"** that sets `registration_visible = TRUE`
while a new `waitlist_only = TRUE` flag suppresses the public card and the Join Waitlist button.
Roughly the §6 workaround, made explicit and safe. Cost is lower on logic but adds a field, a form
element, an admin action and a migration. **Recommend §7.2 instead** — it is less machinery and it
matches what "override" already means everywhere else in the module.

---

## 8. Decisions needed from Andrew + Caleb

1. **Adopt §7.1?** Does a valid override exempt a player from `registration_visible` and
   `registration_close`? (Recommend: yes.)
2. **Should `addSeasonToCart()` refuse non-override holders when the season is hidden?** Under
   Patch 1 it still does — but note anyone with a *valid* override can share their URL and the next
   person is blocked, which is correct. Confirm that is the desired behaviour.
3. **Patch 2a vs 2b** — union query vs. load-all. (Recommend 2a.)
4. **Waitlist terminal status** — what should `ccsoccer_waitlist.status` become when the player
   successfully registers? Does the enum already have a value for it?
5. **Do we apply the §6 workaround to season 48 right now**, ahead of the fix, accepting the URL
   exposure? (Recommend: yes, plus re-issue expired overrides — those five players have been
   waiting.)
6. **Port to tournaments?** The same shape exists at `addTournamentToCart()` (`:1230`) and D3 says
   tournament order-completion has no guard at all. Tournament overrides exist
   (`getValidTournamentOverride()`) but there is no tournament waitlist, so the urgency is lower.

---

## 9. Verification plan

**There is no automated test suite in this module** (`find . -name "*Test.php"` → nothing, no
`tests/` directory). All verification is manual. Do it on DEV against a cloned season, then repeat
step 6 on PROD.

Set up a fixture season with: `registration_visible = FALSE`, `registration_close` in the past,
`active = TRUE`, `reserved_spots = 1`, one waitlisted test player with a fresh offer.

| # | Step | Expected after fix |
|---|---|---|
| 1 | Offered player opens the emailed link | Reaches checkout — no "no longer available" error |
| 2 | Offered player loads `/register` | Season card appears, "Spot Offered From Waitlist" + Register button |
| 3 | **Non**-offered logged-in player loads `/register` | Season absent — *this is the regression that matters most* |
| 4 | Non-offered player pastes `/register/season/{id}` | "Registration for … is no longer available." |
| 5 | Offered player completes payment | Registration created; `Override.status = 'used'`; `reserved_spots` 1 → 0; waitlist entry no longer `offered` |
| 6 | Set `active = FALSE`, retry step 1 | Blocked — override does **not** override `active` |
| 7 | Fill season to `max_players`, retry step 1 | Blocked at the capacity backstop; **confirm the override was NOT consumed** (Patch 5) |
| 8 | Expire the override (`expiration_date` in the past), retry step 1 | Blocked; override auto-flips to `expired` |
| 9 | Anonymous hit on `/register` | No fatal from the `getUserOverrides()` call — anonymous guard works |
| 10 | Log in as offered player in one browser, non-offered in another, same season | Cards differ — **proves the page cache varies per user** (see Patch 2 cache warning) |

Steps 3, 7 and 10 are the ones that catch a bad implementation. Do not skip them.

---

## 10. Related tracker entries

Cross-reference when filing; none of these are superseded by this document.

- **S4** (`OUTSTANDING_ISSUES.md`, Parked) — waitlist offers expire in the email but never in the
  system: no cron pass, entry stays `offered` forever, next in line is never offered, reserved spot
  held indefinitely. **Patch 5's waitlist-status change is a partial fix; the cron expiry is not.**
- **S3** (Parked) — the five money-collected-but-action-failed flag states have no admin report.
  §5's failure mode writes one of them.
- **P6 / Decision 2** — three season-capacity checkpoints disagree on both status set and
  `reserved_spots`. **Deliberately out of scope** (§11), but Patch 5 touches the same method, so
  whoever does P6 should read this first.
- **D3** (§P3) — tournament order completion never re-checks `active`/`registration_visible`. Same
  family as Patch 5; see decision 6 in §8.
- **P11** — "Cancelling a waitlist entry does not revoke the override" and "two override records may
  exist in parallel." Both are `[server]` items about override *bookkeeping*, adjacent to this work.
  Worth resolving the parallel-records question before Patch 2, since `getUserOverrides()` reads the
  **Override entity** and P11 suspects some code reads `ccsoccer_registration.override_expires`
  instead.

---

## 11. Explicitly out of scope

- **Unifying the capacity checkpoints** (P6 / `CapacityManagerService`). Patch 5 edits
  `createSeasonRegistration()` but must not redefine `Season::isFull()` or `getSpotsRemaining()` —
  see the standing warning in P6.
- **Automated waitlist progression** — stays manual by requirement.
- **Cron expiry for waitlist offers** (S4) — separate, and only worth it if season waitlists get
  real use. Five live offers on season 48 suggests they now do.
- **The `−1 spots remaining` display.** Arithmetically correct; the confusion is that the number
  gates nothing on this path. Consider a tooltip, not a logic change.
