Replace max_rar=1 fallback (which allowed both Reserve and Replacement cards)
with min_rarity=rar, max_rarity=rar so each draw targets exactly the intended
rarity tier. Also removes the TODO comments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add SCOUTABLE_PACK_TYPES env var (default: Standard,Premium) to control
which pack types offer scout opportunities
- Unify embed construction into build_scout_embed() — removes 3 near-duplicate
embed builders across scout_view.py and scouting.py
- Replace manual total_scouts counter with derived property from claims dict
- Remove redundant db_get("current") API call per scout click — use PD_SEASON
- Remove duplicate expiry computation in create_scout_opportunity
- Move send_to_channel to top-level import, remove redundant local import
- Update tests to match simplified code
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Include pack_id in db_post("cards") payload (API requires it)
- Player names now link to card image URLs in scout embed
- Display format: "🟡 All-Star — [2023 Mike Trout](card_image_url)"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix int_timestamp() no-arg path returning seconds instead of
milliseconds, which would silently break the daily scout token cap
against the real API
- Acknowledge double-click interactions with ephemeral message instead
of silently returning (Discord requires all interactions to be acked)
- Reorder scout flow: create card copy before consuming token so a
failure doesn't cost the player a token for nothing
- Move build_scouted_card_list import to top of scout_view.py
- Remove unused asyncio import from helpers/scouting.py
- Fix footer text inconsistency ("One scout per player" everywhere)
- Update tests for new operation order and double-click behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Consolidate SCOUT_TOKENS_PER_DAY and get_scout_tokens_used() into
helpers/scouting.py (was duplicated across 3 files)
- Add midnight_timestamp() utility to helpers/utils.py
- Remove _build_scouted_ids() wrapper, use self.claims directly
- Fix build_scout_embed return type annotation
- Use Discord <t:UNIX:R> relative timestamps for scout window countdown
- Add 66-test suite covering helpers, ScoutView, and cog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a player opens a pack, a scout opportunity is posted to #pack-openings
with face-down card buttons. Other players can blind-pick one card using
daily scout tokens (2/day), receiving a copy. The opener keeps all cards.
New files:
- discord_ui/scout_view.py: ScoutView with dynamic buttons and claim logic
- helpers/scouting.py: create_scout_opportunity() and embed builder
- cogs/economy_new/scouting.py: /scout-tokens command and cleanup task
Modified:
- helpers/main.py: Hook into open_st_pr_packs() after display_cards()
- paperdynasty.py: Register scouting cog
Requires new API endpoints in paper-dynasty-database (scout_opportunities).
Tracks #44.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verified via `git ls-files storage/` that no storage files are tracked.
The existing `storage*` pattern already covers the directory, but adding
an explicit entry for `storage/paper-dynasty-service-creds.json` makes
the intent clear for this sensitive Google Sheets service credential file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DEFAULT_ACTIONS_URL=self requires local actions use short form
(cal/gitea-actions/...) so the runner passes its auth token, and
GitHub actions use full URLs (https://github.com/...) to bypass
local resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes tag step failing due to branch protection on main rejecting
the runner's git push. Creates tags via REST API which bypasses
branch protection. Also removes the unnecessary VERSION file commit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove manual semver validation from PR checks. Versions are now
auto-generated on merge to main by counting existing monthly tags.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Align production environment section with Major Domo's format:
container name, remote log command, co-hosted services, tea PR workflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove generated architecture docs, vague data flow sections, and boilerplate.
Keep commands, key patterns, and development notes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The gha cache backend silently fails on Gitea Actions due to Docker
networking issues between the Buildx builder container and the
act_runner cache server. Registry-based caching stores layers on
Docker Hub, which is more reliable for self-hosted runners.
range() objects were used directly in list membership checks instead of
being unpacked with *, causing all pitcher error ratings in range values
(roughly e27+) to silently fail during x-checks. Also fixed two range
boundary mismatches on dice 12 and dice 6.
Closes#12
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a half-inning ended with a CS (or pickoff), the batter who was at
the plate was incorrectly skipped in the next inning. The side-switch
code unconditionally advanced the batting order by 1 without checking
whether the last play was a plate appearance. Now checks opponent_play.pa
before incrementing, matching the existing non-side-switch logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Early returns in log_chaos, log_sac_bunt, and log_stealing left play
locks permanently stuck because the lock was acquired but never released.
The new locked_play async context manager wraps checks_log_interaction()
and guarantees lock release on exception, early return, or normal exit.
Migrated all 18 locking commands in gameplay.py and removed redundant
double-locking in end_game_command.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents PositionNotFoundException from crashing mlb-campaign when a
player is placed at a position they cannot play (e.g. an outfielder
listed at Catcher in the Google Sheet). Adds early validation in
get_lineups_from_sheets and proper error handling at all read_lineup
call sites so the user gets a clear message and the game is cleaned up.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HOTFIX: Production bot failed to start due to circular import.
Root cause: utilities/dropdown.py importing from command_logic/logic_gameplay.py
while logic_gameplay.py imports from utilities/dropdown.py.
Solution: Created play_lock.py as standalone module containing:
- release_play_lock()
- safe_play_lock()
Both modules now import from play_lock.py instead of each other.
Error message:
ImportError: cannot import name 'release_play_lock' from partially
initialized module 'command_logic.logic_gameplay' (most likely due
to a circular import)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
CRITICAL BUG FIX: Play locks were never released on exceptions, causing
permanent user lockouts. Found 13 stuck plays in production.
Changes:
1. Added lock_play parameter to checks_log_interaction() (default True)
2. Removed unnecessary locks from read-only commands:
- /settings-ingame (game settings, not play state)
- /show-card defense (read-only display)
- /substitute commands (just show UI, lock in callback)
3. Added safe_play_lock() context manager for automatic lock release
4. Added play locking to substitution callbacks:
- SelectBatterSub.callback()
- SelectReliefPitcher.callback()
5. Global error handler now releases stuck locks automatically
Architecture:
- Commands that display UI or read data: No lock
- Commands that modify play state: Lock at last possible moment
- 3-layer defense: manual release, context manager, global handler
Resolves race condition from concurrent play modifications.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>