Award AFL top-4 their guaranteed points when the bracket is set #143

Merged
chrisp merged 2 commits from claude/afl-top-4-guaranteed-points-lof6ze into main 2026-08-24 17:24:26 +00:00
Owner

An AFL top-4 seed has the double chance from the moment the bracket is
drawn: lose the Qualifying Final, lose the Semi-Final, and you still
finish in the 5th-6th tier. Nothing was awarding that. Seeds 1-4 sat on
0 fantasy points until their first game resolved, which understated
every roster holding them.

Add an entryFloor field to BracketRound for floors a seeding locks in
before anyone plays, plus applyBracketEntryFloors to bank them, wired
into both bracket generation and reprocess-bracket. For afl_10 that is 5
for the Qualifying Finals (seeds 1-4) and 7 for the Elimination Finals
(seeds 5-6). Every write is provisional, so a real result supersedes it,
and upsertParticipantResult's never-un-finalize guard leaves finalized
rows alone.

Two related floors were also wrong, both from the generic
"winning into a scoring round means top-8" default in
nonScoringWinnerFloorFor:

  • Qualifying Finals winners banked 5 when the bye to a Preliminary
    Final guarantees the 3rd-4th tier. progressive-floor-scoring.test.ts
    already asserted 3 here, but via an isScoring=true call the runtime
    never makes.
  • Wildcard winners banked 5 when winning only buys an Elimination
    Final, whose losers are the 7th-8th tier — an over-award of a full
    tier until that game was played.

Both are now explicit nonScoringWinnerFloor values on the template.

reprocess-bracket now applies entry floors after wiping results and
before replaying matches, and no longer refuses a bracket with no
completed matches, so setting a bracket and reprocessing awards the
guaranteed points. It stays silent on Discord as before.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01JkhdcbUCvramxJdVdoBsKd

An AFL top-4 seed has the double chance from the moment the bracket is drawn: lose the Qualifying Final, lose the Semi-Final, and you still finish in the 5th-6th tier. Nothing was awarding that. Seeds 1-4 sat on 0 fantasy points until their first game resolved, which understated every roster holding them. Add an `entryFloor` field to BracketRound for floors a seeding locks in before anyone plays, plus `applyBracketEntryFloors` to bank them, wired into both bracket generation and reprocess-bracket. For afl_10 that is 5 for the Qualifying Finals (seeds 1-4) and 7 for the Elimination Finals (seeds 5-6). Every write is provisional, so a real result supersedes it, and upsertParticipantResult's never-un-finalize guard leaves finalized rows alone. Two related floors were also wrong, both from the generic "winning into a scoring round means top-8" default in nonScoringWinnerFloorFor: - Qualifying Finals winners banked 5 when the bye to a Preliminary Final guarantees the 3rd-4th tier. progressive-floor-scoring.test.ts already asserted 3 here, but via an isScoring=true call the runtime never makes. - Wildcard winners banked 5 when winning only buys an Elimination Final, whose losers are the 7th-8th tier — an over-award of a full tier until that game was played. Both are now explicit nonScoringWinnerFloor values on the template. reprocess-bracket now applies entry floors after wiping results and before replaying matches, and no longer refuses a bracket with no completed matches, so setting a bracket and reprocessing awards the guaranteed points. It stays silent on Discord as before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JkhdcbUCvramxJdVdoBsKd
chrisp added 1 commit 2026-08-24 17:14:55 +00:00
Award AFL top-4 their guaranteed points when the bracket is set
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 3m14s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m18s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
ea58db9595
An AFL top-4 seed has the double chance from the moment the bracket is
drawn: lose the Qualifying Final, lose the Semi-Final, and you still
finish in the 5th-6th tier. Nothing was awarding that. Seeds 1-4 sat on
0 fantasy points until their first game resolved, which understated
every roster holding them.

Add an `entryFloor` field to BracketRound for floors a seeding locks in
before anyone plays, plus `applyBracketEntryFloors` to bank them, wired
into both bracket generation and reprocess-bracket. For afl_10 that is 5
for the Qualifying Finals (seeds 1-4) and 7 for the Elimination Finals
(seeds 5-6). Every write is provisional, so a real result supersedes it,
and upsertParticipantResult's never-un-finalize guard leaves finalized
rows alone.

Two related floors were also wrong, both from the generic
"winning into a scoring round means top-8" default in
nonScoringWinnerFloorFor:

  - Qualifying Finals winners banked 5 when the bye to a Preliminary
    Final guarantees the 3rd-4th tier. progressive-floor-scoring.test.ts
    already asserted 3 here, but via an isScoring=true call the runtime
    never makes.
  - Wildcard winners banked 5 when winning only buys an Elimination
    Final, whose losers are the 7th-8th tier — an over-award of a full
    tier until that game was played.

Both are now explicit nonScoringWinnerFloor values on the template.

reprocess-bracket now applies entry floors after wiping results and
before replaying matches, and no longer refuses a bracket with no
completed matches, so setting a bracket and reprocessing awards the
guaranteed points. It stays silent on Discord as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkhdcbUCvramxJdVdoBsKd
chrisp added 1 commit 2026-08-24 17:15:05 +00:00
Fix four issues found reviewing the entry-floor change
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 3m16s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m17s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
21422a7e92
- Provisional rows were being treated as finished by
  updateProbabilitiesAfterResult, whose finishedMap filtered on
  finalPosition alone. Entry floors made that fire for the whole seeded
  field: on the first match result, AFL seeds 1-6 would each be pinned
  to 100% at their floor position and dropped from the ICM recalc,
  zeroing the championship odds of six teams that had not played. Filter
  partial rows out of finishedMap so they stay in the unfinished set.
  Finalized 0-position eliminations still finalize as before.

- generate-bracket recalculated standings only inside
  markEliminatedAndAnnounce, which no-ops when nothing was eliminated.
  A season whose participants exactly equal the bracket field would
  never surface the floors in teamStandings.totalPoints. Recalculate
  explicitly in that case (skipDiscord: seeding is not a result).

- applyBracketEntryFloors upserted unconditionally, so regenerating a
  bracket mid-tournament could downgrade a team already sitting on a
  better placement. Read existing placements first and only write when
  the floor improves on what a participant already has; position 0 is
  eliminated, not a placement, so it never blocks a floor.

- Relaxing the reprocess guard to matches.length made the season-wide
  deleteParticipantResultsBySportsSeasonId reachable with zero completed
  matches, wiping other events' placements with no replay able to
  rebuild them. Skip the wipe when there is nothing to replay; entry
  floors and elimination marking are additive and need no wipe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkhdcbUCvramxJdVdoBsKd
chrisp merged commit 3d89db3d0a into main 2026-08-24 17:24:26 +00:00
Sign in to join this conversation.
No description provided.