belote-cli 4.6.5__tar.gz → 4.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- belote_cli-4.7.2/.antigravitycli/69dd05ce-2c1c-4419-8755-e4dd0d4495e8.json +1 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/CHANGELOG.md +237 -0
- belote_cli-4.7.2/DEVELOPMENT.md +185 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/PKG-INFO +11 -6
- {belote_cli-4.6.5 → belote_cli-4.7.2}/README.md +10 -5
- {belote_cli-4.6.5 → belote_cli-4.7.2}/pyproject.toml +1 -1
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/__init__.py +1 -1
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ai.py +132 -34
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/scoring.py +65 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/engine/round_driver.py +38 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/main.py +275 -106
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/progression/save.py +10 -1
- belote_cli-4.7.2/src/belote/belatro/ui/announce.py +419 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/hud.py +61 -1
- belote_cli-4.7.2/src/belote/belatro/ui/inventory.py +345 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/game.py +7 -1
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/gameflow.py +21 -19
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/input.py +12 -2
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/scoring.py +57 -67
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/prompts.py +30 -3
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_boss_modifiers_integration.py +33 -0
- belote_cli-4.7.2/tests/belatro/test_heist.py +657 -0
- belote_cli-4.7.2/tests/belatro/test_inventory_overlay.py +202 -0
- belote_cli-4.7.2/tests/belatro/test_slot_machine_tally.py +351 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_ai.py +92 -1
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_alt_screen_scroll.py +10 -5
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_belote.py +104 -8
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_bidding_all_pass.py +50 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/uv.lock +1 -1
- belote_cli-4.6.5/DEVELOPMENT.md +0 -214
- belote_cli-4.6.5/src/belote/belatro/ui/announce.py +0 -217
- {belote_cli-4.6.5 → belote_cli-4.7.2}/.claude/settings.local.json +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/.gitignore +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/.python-version +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/LICENSE +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/scripts/benchmark.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/a11y.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/achievements.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ansi.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/economy.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/round_ledger.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/run_state.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/engine/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/engine/event_bus.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/engine/modifier_patch.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ghost_run.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/base.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/annonces.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/coinche.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/contract.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/corrupted.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/economy.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/hand_comp.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/trick_timing.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/partner_jokers/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/partner_jokers/passive.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/partner_jokers/risky.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/partner_jokers/shaper.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/planets.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/registry.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/tarots.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/vouchers.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/partner/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/partner/partner_state.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/partner/personality.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/partner/trust.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/progression/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/progression/unlocks.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/ante.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/ante_themes.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/boss.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/decks.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/shop.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run_summary.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/collection.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/consumables.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/history.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/menu.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/rules.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/shop.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/trust_bar.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/config.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/context.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/deck.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/main.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/replay.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/rules.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/stats.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/themes.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/announce.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/fit_guard.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/layout.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/menu.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/render.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/__init__.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_belatro.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_boss_contracts.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_collection_logic.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_consumables_ui.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_contract_unlocks.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_dead_flag_fixes.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_deck_variants.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_decks_4_5.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_endless.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_event_bus.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_ghost_run.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_history_overlay.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_hud_synergy.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_hud_toggle.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_joker_contracts.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_jokers_4_5.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_partner_jokers.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_partner_trust.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_phase0_coverage.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_phase1_plumbing.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_phase2_content.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_phase3_meta.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_progression.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_round_driver.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_run_summary.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_shop_empty_pools.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_voucher_idempotency.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/perf_baselines.json +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_a11y.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_achievements.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_announce_stats.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_ansi_helpers.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_benchmark_smoke.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_declaration_tiebreak.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_extended.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_game_logic.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_gameflow.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_hand_auto_sort.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_history_overlay_cache.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_input_eof.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_input_wasd.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_invariants.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_layout.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_new_coverage.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_no_color.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_official_rules.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_perf_regression.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_properties.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_render_diff.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_render_felt_polish.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_replay.py +0 -0
- {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_undo.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/home/mrrobot/.gemini/config/projects/69dd05ce-2c1c-4419-8755-e4dd0d4495e8.json
|
|
@@ -5,6 +5,243 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.7.2] - 2026-05-20
|
|
9
|
+
|
|
10
|
+
Patch release: external-model audit verification pass. A prior audit (pasted
|
|
11
|
+
from another LLM) flagged ~30 issues across bugs / perf / quality. Verifying
|
|
12
|
+
each claim against the live code rejected the false positives (including one
|
|
13
|
+
HIGH-severity claim — "160 event dispatches per round" — that the model
|
|
14
|
+
invented out of whole cloth) and shipped only the genuinely confirmed
|
|
15
|
+
findings. Two real correctness gaps the prior audit missed were caught via
|
|
16
|
+
fresh-additions review. All baselines green: `ruff` 0 violations,
|
|
17
|
+
`mypy --strict` 0 errors, `pytest` 1003/1003.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **(Bug) Malédiction (`invert_scoring`) 4-4 trick tie.** Pre-fix the boss
|
|
22
|
+
flag zeroed the team that won MORE tricks, but a 4-4 split fell through
|
|
23
|
+
both branches and neither side was zeroed — both teams kept their full
|
|
24
|
+
scores under the curse. Rule chosen: the taker is zeroed (the cursed
|
|
25
|
+
side failed to break the tie). `scoring.py::_apply_invert_scoring` adds
|
|
26
|
+
an `else` branch; pinned by
|
|
27
|
+
`test_boss_invert_scoring_4_4_tie_zeros_taker` in
|
|
28
|
+
`tests/belatro/test_boss_modifiers_integration.py`.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **`bm: object` → `bm: BossModifiers`** on `_trick_zeroed_by_ban_clubs`,
|
|
33
|
+
`_card_points_with_zero_ranks`, `card_points_with_modifiers`, and
|
|
34
|
+
`_trick_points_with_modifiers` (`scoring.py`). All four helpers were
|
|
35
|
+
typed as `object` with `getattr(bm, "ban_clubs", False)` defensive
|
|
36
|
+
lookups; the actual callsites always pass `BossModifiers`. Now type-
|
|
37
|
+
safe attribute access — `mypy --strict` enforces every new field is
|
|
38
|
+
spelled correctly.
|
|
39
|
+
- **`UICallbacks` extracted from `BelAtroGame._play_blind`** to a module-
|
|
40
|
+
top class in `belatro/main.py`. The nested class spanned 189 lines and
|
|
41
|
+
captured 8 closure variables (`run`, `profile`, `save_manager`, `hud`,
|
|
42
|
+
`acc`, `trust_bar`, `show_north`, plus a dead-write `last_display_state`
|
|
43
|
+
cell). Now an explicit constructor; `last_display_state` removed
|
|
44
|
+
(write-only, never read).
|
|
45
|
+
- **Litige pool carry across all-pass redeals is now documented.** Added
|
|
46
|
+
a `reset_round_fields` docstring entry calling out that `litige_points`
|
|
47
|
+
is deliberately NOT in the reset list — the pool survives across
|
|
48
|
+
rounds (including all-pass redeals) until a non-litige scoring round
|
|
49
|
+
consumes it. Pinned by
|
|
50
|
+
`test_litige_pool_survives_all_pass_redeal` +
|
|
51
|
+
`test_litige_pool_survives_two_consecutive_all_pass_redeals` in
|
|
52
|
+
`tests/test_bidding_all_pass.py`.
|
|
53
|
+
|
|
54
|
+
### Performance
|
|
55
|
+
|
|
56
|
+
- **`detect_sequences` is now `@lru_cache(128)`.** Microbench placed it
|
|
57
|
+
at ~25% of `score_round` cost when `initial_hands` is populated, and
|
|
58
|
+
`get_declarations` runs the same 4 hand tuples through the helper
|
|
59
|
+
twice per round (once at bid-acceptance time in `game.py`, once during
|
|
60
|
+
`score_round` itself). Cache hits eliminate the second pass.
|
|
61
|
+
`score_round` macrobench: 300 µs → 227 µs (~24% on the full path).
|
|
62
|
+
Cards are frozen dataclasses (hashable); all callers treat the
|
|
63
|
+
returned list as read-only.
|
|
64
|
+
|
|
65
|
+
### Tests strengthened
|
|
66
|
+
|
|
67
|
+
- **`test_last_trick_bonus_applied`** (`tests/test_belote.py`) → split
|
|
68
|
+
into `test_capot_subsumes_last_trick_bonus` (asserts the actual
|
|
69
|
+
`CAPOT_BASE` total) and new `test_last_trick_bonus_applied_in_normal_round`
|
|
70
|
+
(deterministic 4-NS/4-EW non-capot fixture proving the +10 dix-de-der
|
|
71
|
+
actually lands on the taker side). Previously only asserted `is_capot
|
|
72
|
+
is True` — the +10 mechanic the test name promised was untested.
|
|
73
|
+
- **`test_non_capot_points_sum_162`** (`tests/test_belote.py`) →
|
|
74
|
+
conditional `if not breakdown.is_capot:` replaced with explicit
|
|
75
|
+
`assert not breakdown.is_capot` plus the unconditional conservation
|
|
76
|
+
check. The old form passed vacuously if `make_deck()` ordering ever
|
|
77
|
+
produced a capot.
|
|
78
|
+
|
|
79
|
+
### Internal
|
|
80
|
+
|
|
81
|
+
- **Contract→word a11y mapping deduplicated** to a `_contract_word(state)`
|
|
82
|
+
helper in `gameflow.py`. Two near-identical 8-line blocks collapsed
|
|
83
|
+
into one helper call each (bid→play transition; round-result speak).
|
|
84
|
+
|
|
85
|
+
### Documentation
|
|
86
|
+
|
|
87
|
+
- README test count claims and CHANGELOG / DEVELOPMENT references
|
|
88
|
+
updated 999 → 1003.
|
|
89
|
+
|
|
90
|
+
### Verified-false audit claims (re-investigate at your peril)
|
|
91
|
+
|
|
92
|
+
For the record so the next audit cycle doesn't relitigate items the
|
|
93
|
+
external model flagged but the code disagreed with:
|
|
94
|
+
|
|
95
|
+
- `partner()` if-chain vs modular arithmetic — style only, not a bug.
|
|
96
|
+
- `start_round` "doesn't reset belote_holders" — self-refuted (line 308
|
|
97
|
+
passes `belote_holders={}`).
|
|
98
|
+
- Score-animation labels "confusing" — `gameflow.py:458` already labels
|
|
99
|
+
by `breakdown.taker_team`.
|
|
100
|
+
- `_empty_breakdown` taker_team default — only returned on contract-
|
|
101
|
+
inactive states; harmless.
|
|
102
|
+
- `BelAtroRun.consume()` "removes before use" — wrapped in
|
|
103
|
+
`contextlib.suppress(ValueError)`, semantically correct.
|
|
104
|
+
- `_bonus_money` "race" — Le Fantôme writes to `acc._ledger.money`
|
|
105
|
+
directly since 4.6.2; main.py only consumes seal_round-routed amounts.
|
|
106
|
+
- "160 event dispatches per round" — false. Jokers subscribe to
|
|
107
|
+
`TrickWonEvent` / `BeloteAnnouncedEvent` / `DeclarationScoredEvent`
|
|
108
|
+
only; per-card emit doesn't iterate jokers. Real count is ~85-100
|
|
109
|
+
dispatches per round and they're already O(jokers × events).
|
|
110
|
+
- LeRebelle / RebeloteEcho / TierceCharger ungated under boss flags —
|
|
111
|
+
verified gated. `game.py:907` short-circuits `BeloteAnnouncedEvent`
|
|
112
|
+
under `no_belote`; `round_driver.py:467-473` documents
|
|
113
|
+
TierceCharger's intentional un-gating; LeMathématicien / QuinteRoyale
|
|
114
|
+
read `event.points` which Le Mime forces to 0.
|
|
115
|
+
|
|
116
|
+
## [4.7.1] - 2026-05-19
|
|
117
|
+
|
|
118
|
+
Patch release: post-4.7.0 audit pass that found two latent correctness
|
|
119
|
+
foot-guns and three concrete AI hot-path wins, then reconciled the
|
|
120
|
+
documentation surfaces with the actual code. Three parallel Explore-agent
|
|
121
|
+
audits (core engine, BelAtro mode, performance) surfaced ~20 candidate
|
|
122
|
+
issues; the seven false-positives were rejected against the live code
|
|
123
|
+
and only the verified findings shipped. All baselines green: `ruff` 0
|
|
124
|
+
violations, `mypy --strict` 0 errors, `pytest` 999/999.
|
|
125
|
+
|
|
126
|
+
### Fixed
|
|
127
|
+
|
|
128
|
+
- **(Major) Sans Atout partner-hand rank comparison.** `_score_winning_strategy`
|
|
129
|
+
computed the local `rank` via `_NONTRUMP_ORDER[card.rank]` under SA
|
|
130
|
+
(correct), but the partner-hand penalty block at the bottom of the
|
|
131
|
+
function unconditionally called `trick_rank(card, trump, self._se)` with
|
|
132
|
+
`trump=None` for BOTH the candidate card and every partner card.
|
|
133
|
+
`trick_rank(card, None, ...)` happens to collapse to the same ordering
|
|
134
|
+
as `_NONTRUMP_ORDER` today, so the bug is benign-by-coincidence — but
|
|
135
|
+
any future tweak to `trick_rank`'s SA branch (e.g. honor-scaling)
|
|
136
|
+
would silently break the partner-hand heuristic without any test
|
|
137
|
+
catching it. Now mirrors line 728's `if is_sa` branch on both rank
|
|
138
|
+
computations. Pinned by `test_winning_strategy_partner_hand_uses_nontrump_order_under_sa`
|
|
139
|
+
in `tests/test_ai.py`, which poisons `trick_rank` and confirms the
|
|
140
|
+
SA branch never touches it.
|
|
141
|
+
- **(Minor) `prompt_card` silently substituted `hand[0]` on empty
|
|
142
|
+
`legal_cards`.** Belote's suit-follow / overtrump rules guarantee at
|
|
143
|
+
least one legal card whenever the hand is non-empty, so the path is
|
|
144
|
+
unreachable today — but the `return hand[0], state` fallback would
|
|
145
|
+
let an illegal card through to `play_card` if `legal_cards` ever
|
|
146
|
+
regressed. The AI path (`ai.py::decide_card`) already raises on the
|
|
147
|
+
same precondition; the human-input path now matches. Pinned by
|
|
148
|
+
`test_prompt_card_raises_on_empty_legal_cards`.
|
|
149
|
+
|
|
150
|
+
### Performance
|
|
151
|
+
|
|
152
|
+
- **Hoisted `highest_rank` and `opp_voids` out of the per-card scoring
|
|
153
|
+
loop in `_hard_play`.** Both depend only on `(trick, trump, is_sa,
|
|
154
|
+
self._se)` and the next-opp seat — all constant across the 1–8 legal
|
|
155
|
+
candidates being scored. Pre-fix they were recomputed inside
|
|
156
|
+
`_score_winning_strategy` per card. Now precomputed once and threaded
|
|
157
|
+
through `_score_card_play` → `_score_winning_strategy` as kwargs with
|
|
158
|
+
`None` defaults so standalone callers (tests, future ad-hoc
|
|
159
|
+
heuristics) still work. ~8–15% on hard-AI card decisions.
|
|
160
|
+
- **Plumbed `points` through `_score_leading_strategy`.**
|
|
161
|
+
`_score_card_play` already computes `points = card_points_with_modifiers(card, trump, bm)`,
|
|
162
|
+
but the leading-strategy helper re-invoked the function to detect waste
|
|
163
|
+
cards. Now the helper accepts a keyword-only `points: int` and reuses
|
|
164
|
+
the caller's value. ~3–5% on lead decisions.
|
|
165
|
+
|
|
166
|
+
### Changed
|
|
167
|
+
|
|
168
|
+
- `tests/test_ai.py` test count: 22 → 24 (the two new pins above).
|
|
169
|
+
Total suite: 997 → **999**.
|
|
170
|
+
|
|
171
|
+
### Documentation
|
|
172
|
+
|
|
173
|
+
- `README.md` — test-count claims updated 997 → 999 in three places
|
|
174
|
+
(project-structure block, "Running Tests" section, "Technical
|
|
175
|
+
Integrity" bullet list). Joker/voucher/planet/tarot/boss/deck counts
|
|
176
|
+
reverified against `registry` / `ALL_BOSS_MODIFIERS` / `STARTING_DECKS`
|
|
177
|
+
(42 / 12 / 8 / 12 / 21 / 12) — all already accurate at 4.7.0, no
|
|
178
|
+
change.
|
|
179
|
+
- `DEVELOPMENT.md` — code-quality command block now mentions 999 tests.
|
|
180
|
+
Release process now explicitly calls out that `pyproject.toml` AND
|
|
181
|
+
`src/belote/__init__.py` must be bumped together (they had been
|
|
182
|
+
drifting in older release attempts; `belote --version` /
|
|
183
|
+
`belatro --version` read `__version__` while PyPI / pipx read
|
|
184
|
+
`pyproject.toml`).
|
|
185
|
+
|
|
186
|
+
## [4.7.0] - 2026-05-19
|
|
187
|
+
|
|
188
|
+
Feature minor: ships the **Dix de Der Heist** (BelAtro roguelite gamble keyed off `economy.interest_rate`) and the **slot-machine score tally** that replaces the static per-trick popup with a rolling odometer animation. Includes a full 4.6.5-style multi-agent audit (six parallel Explore agents covering heist correctness, slot-machine boss safety, items integration, save/load, performance, and 21-boss crash safety) and the resulting fixes, plus a hands-on polish pass — heist-discoverability hint, persistent tally in HUD, and a new V-key inventory overlay. All baselines green: `ruff` 0 violations, `mypy --strict` 0 errors, `pytest` 997/997.
|
|
189
|
+
|
|
190
|
+
### Added
|
|
191
|
+
|
|
192
|
+
- **Dix de Der Heist (BelAtro)** — Player-only roguelite gamble declared at the start of trick phase. When SOUTH is the taker and `economy.interest_rate > 0` (gated; default rate = 0 = no reward = no prompt), the player can declare a heist that resolves on trick 8:
|
|
193
|
+
- On NS win: `ledger.mult *= (1 + interest_rate)` (`×2` with La Voûte, up to `×4` stacked with interest-bumping tarots).
|
|
194
|
+
- On NS loss: the card_points NS accumulated in tricks 1–7 are subtracted from `ledger.chips`. Declarations / belote / permanent / joker bonuses preserved.
|
|
195
|
+
- The multiplier resolves BEFORE `_fire_jokers("on_trick_won", event)` on trick 8 so jokers like LeDernierMot (×2) and LExecuteur (×1.5) compose on top. Documented in `belatro/core/scoring.py` to lock the composition rule in for future readers.
|
|
196
|
+
- State on `joker_state` as three scalar keys (`heist_declared`, `heist_ns_trick_chips`, `heist_outcome`) seeded fresh by `trigger_round_start`; per-round transient, never persisted. Sentinel `heist_outcome` guards resolution against the ~30 HUD `materialize()` re-reads per round.
|
|
197
|
+
- New `RoundUICallbacks.prompt_heist(state) -> bool` hook with a default no-op (classic Belote never offers the heist). BelAtro UICallbacks overrides in `belatro/main.py`.
|
|
198
|
+
- **Slot-machine score tally** (`BelAtroAnnounce.slot_machine_tally`) — Per-trick odometer animation replacing the static `score_popup`. 20-frame ease-out over ~600ms; bucket bar shows trick card_points filling, mult indicator pulses, odometer ticks toward the new total with color shifts (cyan → white → gold) and a flame row (`≈ ▼ ◆ ▼ ≈`) when running total crosses 120% of target. Skippable on SPACE / ESC / ENTER / EOF. Suppressed under `hide_hud` (Le Brouillard), `separate_scoring` (La Compétition), and on terminals smaller than 6 rows. Module-level cache (`_last_tally_total`) cleared each round via `reset_tally_state()`.
|
|
199
|
+
- **Persistent slot-machine readout in the BelAtro HUD (follow-up).** After the animation completes, the final-frame bucket + odometer lines stay visible at the bottom of the screen until the next trick or until `I` toggles the top HUD off. Adds a 1s skippable hold after the final frame so the player can read the result, then a new `_last_tally_readout` module cache feeds `BelAtroHUD.render()` / `_render_compact()` for the repaint. Cleared via `reset_tally_state()`. Bound to `is_top_hud_visible()` so `I` hides both the HUD and the readout in one shot.
|
|
200
|
+
- **Inventory overlay on the V key** (`BelAtroAnnounce` peer `InventoryOverlay` in `belatro/ui/inventory.py`). Read-only detail-on-select pager that lists owned jokers (with edition tags + per-edition bonus blurb), vouchers, consumables (tarots/planets held but not yet activated), permanent chip / mult bonuses, and per-contract planet levels. List view → ↑/↓ navigate, Enter opens a per-item detail page; Esc/V/Q close. Mirrors the `C` key's `ConsumablesOverlay` paint pattern (`clear_screen` + `vcenter_lines` + `invalidate_diff()` in `finally`).
|
|
201
|
+
- **Discoverability hint for the Dix de Der Heist.** The first time a player takes a contract while `economy.interest_rate == 0` (i.e. they haven't bought La Voûte yet), a one-time banner explains the gating: *"Tip: buy the La Voûte voucher in the shop to unlock the Dix de Der Heist (×2+ Mult on trick 8)."* Persisted via a new `Profile.seen_heist_hint: bool` field; no SCHEMA_VERSION bump needed (dataclass default kicks in for legacy saves).
|
|
202
|
+
|
|
203
|
+
### Fixed (4.7.0 audit pass)
|
|
204
|
+
|
|
205
|
+
- **(Critical) Slot-machine cache leak on mid-animation interrupt** (`belatro/ui/announce.py`). The `_last_tally_total = new_total` update lived inside the try-block; a `KeyboardInterrupt` or render-time raise mid-animation would skip the update and leave the next round animating from a stale baseline. Moved into the `finally` block alongside `invalidate_diff()`. Pinned by `test_slot_machine_cache_updates_even_if_render_raises`.
|
|
206
|
+
- **(Critical) Slot-machine row collision on tiny terminals** (`belatro/ui/announce.py`). Row math (`term_h - 5..3`) collapsed when `term_h < 6`, painting the animation over the HUD row 1. Added a `term_h < 6` short-circuit that suppresses the animation while still updating the cache. Pinned by `test_slot_machine_tiny_terminal_renders_nothing`.
|
|
207
|
+
- **(Critical) `BelAtroAnnounce.reset_tally_state` AttributeError on round start.** `belatro/main.py::_play_blind` called `BelAtroAnnounce.reset_tally_state()` but the function was defined at module level (mirroring `reset_top_hud_state` / `reset_overlay_state`), not as a static method on the class. Crashed at the first round. Now imported as a module function. User-surfaced.
|
|
208
|
+
- **(Medium) Slot-machine paints misleading totals under La Compétition** (`belatro/ui/announce.py`). `acc.get_total(state)` is a running sum but `separate_scoring` uses a per-seat max at round-end; the animated total diverged from the eventual sealed total. Now gated like the HUD (`belatro/ui/hud.py:171-176`): cache update without paint. Pinned by `test_slot_machine_under_separate_scoring_renders_nothing`.
|
|
209
|
+
- **(Medium) Heist outcome banner leaked under Le Brouillard** (`belatro/main.py`). The post-round "HEIST SECURED / BUSTED" banner ignored `hide_hud`, defeating the boss's "hide the score" promise. Now gated on `not final_state.boss_modifiers.hide_hud`.
|
|
210
|
+
- **(Polish) Blank flame row consumed a screen row** (`belatro/ui/announce.py`). When `displayed < target × 1.2`, `ansi_center("")` painted a `term_w`-wide blank line, wasting vertical space. Now skip the paint when the flame line is empty.
|
|
211
|
+
- **Sans Atout AI type safety.** The 4.6.6 in-flight Sans Atout Hard AI work shipped with type annotations that assumed `trump: Suit`, but the SA path has `trump = None`. Widened `_hard_lead` and `_score_card_play` signatures to `Suit | None`, added an explicit `trump is None` arm to the `opp_trumps` computation in `_hard_play`, and removed a dead `is_sa = True/False` branch (overwritten three lines later by the canonical `state.contract == Contract.SANS_ATOUT` read). `mypy --strict` now clean.
|
|
212
|
+
|
|
213
|
+
### Changed
|
|
214
|
+
|
|
215
|
+
- **`BelAtroAnnounce.score_popup` no longer called at `on_trick_end`.** Replaced by `slot_machine_tally`. The method is still defined for one release; if no consumer surfaces by 4.8 it will be removed. Bumped the `belatro/ui/announce.py` `invalidate_diff` count in `tests/test_alt_screen_scroll.py::test_belatro_overlays_invalidate_diff` from 4 to 5.
|
|
216
|
+
- **`ScoreAccumulator` gained an `interest_rate: int = 0` field.** Snapshotted at `_play_blind` time alongside `target_score`, so a mid-round La Voûte purchase or tarot effect cannot retroactively change the resolved heist multiplier.
|
|
217
|
+
- **`V` key is no longer an alias of `I`.** Pre-4.7.0 both keys mapped to `Key.OVERLAY` (top-HUD toggle). The follow-up pass split `V` into a new `Key.INVENTORY` enum value that opens the read-only inventory overlay. `I` keeps its existing toggle semantics (and now also covers the persistent tally readout). Wired in `belote/input.py` (both `_UnixKeyReader` and `_WindowsKeyReader`), dispatched in `belote/ui/prompts.py::prompt_card` (`"INVENTORY"` sentinel string), and handled by `belatro/main.py::UICallbacks._show_inventory`. Classic Belote's `gameflow.py` treats the sentinel as a no-op (re-prompt).
|
|
218
|
+
|
|
219
|
+
### Internal
|
|
220
|
+
|
|
221
|
+
- **Test count baseline**: 941 → 997 (+56 in 4.7.0: 23 heist tests including 3 hint-discoverability regressions, 16 slot-machine tests including 5 HUD-readout persistence regressions, 15 inventory-overlay tests, 2 audit-driven entries).
|
|
222
|
+
- **Version markers bumped**: `pyproject.toml` (`4.6.6` → `4.7.0`), `src/belote/__init__.py` (`4.6.6` → `4.7.0`).
|
|
223
|
+
- **No `SCHEMA_VERSION` bump required**: heist state is per-round transient on `joker_state`, seeded fresh by `trigger_round_start` and never persisted. `Economy.interest_rate` was already persisted as part of `BelAtroRun.economy`; existing saves load without migration.
|
|
224
|
+
- **Six-agent audit highlights (all verified clean, no further action)**: per-round transient state hygiene (heist keys reset at round start, slot-machine cache reset at `_play_blind` entry); idempotency under HUD `materialize()` (sentinel-guarded, no re-application); composition with all trick-8 jokers (LeDernierMot, LExecuteur, Le Carnet, L'Infiltré); composition with planets (Pluton capot bonus, Mercure round-end money, Le Soleil Tout Atout per-trick mult); 21-boss safety smoke (La Rupture / La Compétition / L'Avocat / Le Mime / Le Zéro Final / L'Anarchie / L'Agent Double / La Solitude all verified). Performance: heist branches sub-microsecond per trick; slot-machine animation adds ~4.8s/round (designed, skippable).
|
|
225
|
+
- **Documentation accuracy sweep**: README and DEVELOPMENT refreshed; item counts (42 jokers, 12 tarots, 8 planets, 12 vouchers, 12 starting decks, 21 bosses) unchanged from 4.6.6 and re-verified against the registry.
|
|
226
|
+
- **Plan file**: `/home/mrrobot/.claude/plans/let-s-add-dix-de-steady-duckling.md`.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## [4.6.6] - 2026-05-19
|
|
231
|
+
|
|
232
|
+
Performance and logic audit pass. Highlights: improved Hard AI strategy for Sans Atout, unified round scoring paths to remove redundancy, and end-to-end documentation accuracy verification. All baselines green: `ruff` 0 violations, `mypy --strict` 0 errors, `pytest` 941/941.
|
|
233
|
+
|
|
234
|
+
### Fixed
|
|
235
|
+
|
|
236
|
+
- **Hard AI fallback for Sans Atout rounds.** Pre-4.6.6, the 1-ply lookahead heuristic was trump-centric and fell back to random play when no trump was set. Now uses a non-trump ranking heuristic based on `_NONTRUMP_ORDER` (A > 10 > K > Q > J > 9 > 8 > 7), making the Hard AI significantly more competitive in Sans Atout play.
|
|
237
|
+
- **Refactored `apply_round_score` in `scoring.py`.** Unified the NS-taker and EW-taker branches into a single path using conditional mapping, eliminating 50+ lines of duplicated code and reducing the risk of logic drift between teams.
|
|
238
|
+
|
|
239
|
+
### Internal
|
|
240
|
+
|
|
241
|
+
- **Test count baseline**: 941 (unchanged).
|
|
242
|
+
- **Version markers bumped**: `pyproject.toml`, `src/belote/__init__.py`, and various documentation references (4.6.5 → 4.6.6).
|
|
243
|
+
- **Documentation accuracy sweep**: Item counts (42 jokers, 12 tarots, 8 planets, 12 vouchers, 12 starting decks, 21 bosses) and test counts (941) verified across `README.md` and `DEVELOPMENT.md`.
|
|
244
|
+
|
|
8
245
|
## [4.6.5] - 2026-05-18
|
|
9
246
|
|
|
10
247
|
Follow-up audit pass — six parallel Explore agents covered the surface area the 4.6.4 sweep didn't reach (partner-trust + bidding state machine, shop / economy / items / ghost-run, progression / replay / a11y / input). Result: two verified critical bugs in production paths the previous audit didn't touch (the LePrêteur economy exploit and a Windows-only EOF reader bug), three performance wins, and four quality items including the dead-but-declared `announce_round_result` helper finally wired up. Mechanics audits (partner trust, bidding state machine, capot detection, belote/rebelote under L'Anarchie, voucher idempotency, ghost-run write safety, save atomic-writes, achievement unlocks) all came back clean against current code. Plan file: `/home/mrrobot/.claude/plans/bug-hunt-code-performance-sparkling-fairy.md`. All baselines green: `ruff` 0 violations, `mypy --strict` 0 errors, `pytest` 941/941.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Development Guide
|
|
2
|
+
|
|
3
|
+
Welcome to the Belote development guide. This project is structured as a standard Python package.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. **Clone the repository:**
|
|
8
|
+
```bash
|
|
9
|
+
git clone <repository-url>
|
|
10
|
+
cd belote
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. **Create a virtual environment:**
|
|
14
|
+
```bash
|
|
15
|
+
python -m venv .venv
|
|
16
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
3. **Install in editable mode:**
|
|
20
|
+
```bash
|
|
21
|
+
pip install -e .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Running the Game
|
|
25
|
+
|
|
26
|
+
After installing, you can run the game using the `belote` command for Classic mode:
|
|
27
|
+
```bash
|
|
28
|
+
belote
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or the `belatro` command for the Roguelite expansion:
|
|
32
|
+
```bash
|
|
33
|
+
belatro
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or via python:
|
|
37
|
+
```bash
|
|
38
|
+
python -m belote.main
|
|
39
|
+
python -m belote.belatro.main
|
|
40
|
+
PYTHONPATH=src python3 -m belote.main
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Testing
|
|
44
|
+
|
|
45
|
+
We use `pytest` for testing. Install it if you haven't already:
|
|
46
|
+
```bash
|
|
47
|
+
pip install pytest
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Run tests:
|
|
51
|
+
```bash
|
|
52
|
+
# Run all tests (Classic + BelAtro)
|
|
53
|
+
PYTHONPATH=src pytest
|
|
54
|
+
|
|
55
|
+
# Run only Classic Belote tests
|
|
56
|
+
PYTHONPATH=src pytest tests/
|
|
57
|
+
|
|
58
|
+
# Run only BelAtro tests
|
|
59
|
+
PYTHONPATH=src pytest tests/belatro/
|
|
60
|
+
|
|
61
|
+
# Run a single test file
|
|
62
|
+
PYTHONPATH=src pytest tests/test_game.py
|
|
63
|
+
PYTHONPATH=src pytest tests/belatro/test_scoring.py
|
|
64
|
+
|
|
65
|
+
# Run a single test by name
|
|
66
|
+
PYTHONPATH=src pytest tests/test_game.py::test_play_card_legal
|
|
67
|
+
PYTHONPATH=src pytest -k "test_scoring"
|
|
68
|
+
|
|
69
|
+
# Run with verbose output
|
|
70
|
+
PYTHONPATH=src pytest -v
|
|
71
|
+
|
|
72
|
+
# Run with coverage report
|
|
73
|
+
PYTHONPATH=src pytest --cov=belote --cov-report=term-missing
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Code Quality
|
|
77
|
+
|
|
78
|
+
The project maintains zero lint and type-check violations. Run all checks with:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Type checking (0 errors expected, strict mode)
|
|
82
|
+
PYTHONPATH=src mypy --strict src/
|
|
83
|
+
|
|
84
|
+
# Linting (0 violations expected)
|
|
85
|
+
ruff check src/ tests/
|
|
86
|
+
|
|
87
|
+
# Full test suite (1003 tests expected)
|
|
88
|
+
PYTHONPATH=src pytest
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Run all gates before committing:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
PYTHONPATH=src python -m pytest --tb=short -q && \
|
|
95
|
+
python -m mypy --strict src/ && \
|
|
96
|
+
python -m ruff check src/ tests/
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Benchmarking
|
|
100
|
+
|
|
101
|
+
A benchmarking script is provided to measure rendering and AI performance:
|
|
102
|
+
```bash
|
|
103
|
+
PYTHONPATH=src python scripts/benchmark.py
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
3.0.0 baseline numbers (Linux, Python 3.10+, 1000 iterations):
|
|
107
|
+
- Render: 0.27 ms (±0.04)
|
|
108
|
+
- AI Hard decide_card: 0.026 ms (±0.003)
|
|
109
|
+
- BelAtro state update: 0.032 ms (±0.004)
|
|
110
|
+
- score_round: 0.169 ms
|
|
111
|
+
- legal_cards: 0.012 ms
|
|
112
|
+
|
|
113
|
+
Use these as a regression-detection floor for future changes.
|
|
114
|
+
|
|
115
|
+
## Accessibility
|
|
116
|
+
|
|
117
|
+
Set `BELOTE_A11Y=1` to emit one-line plain-text descriptions of card plays,
|
|
118
|
+
trick winners, locked contracts (4.6.5), declarations (4.6.5), and round
|
|
119
|
+
results to stderr — readable by terminal screen readers (Orca, NVDA over
|
|
120
|
+
WSL, VoiceOver via iTerm2).
|
|
121
|
+
|
|
122
|
+
The env var is read once at import; tests that mutate it must call
|
|
123
|
+
`belote.a11y._refresh_enabled_from_env()` after the patch. Hook points live
|
|
124
|
+
in `gameflow.py` (card plays, trick winners, contract announce at bid→play
|
|
125
|
+
transition, round result) and `belatro/main.py` (boss reveal, ante advance,
|
|
126
|
+
run won/lost).
|
|
127
|
+
|
|
128
|
+
## Optional Runtime Flags
|
|
129
|
+
|
|
130
|
+
The following environment variables enable opt-in features. Each is read
|
|
131
|
+
once at startup; toggling mid-run has no effect.
|
|
132
|
+
|
|
133
|
+
- `BELOTE_REPLAY=1` — after every Classic round, print a one-line summary
|
|
134
|
+
of how often South's plays matched the Hard-AI's preferred line
|
|
135
|
+
(e.g. `Replay: Optimal plays: 6/8 (75%)`). Educational only — never
|
|
136
|
+
affects scoring. Backed by `src/belote/replay.py`.
|
|
137
|
+
- `BELOTE_GHOST=1` — silently record every BelAtro run (seed, deck,
|
|
138
|
+
bids, plays, round outcomes) to
|
|
139
|
+
`~/.local/share/belote/ghosts/<label>-<seed>.json`. The file is written
|
|
140
|
+
once when the run ends. Useful for sharing or replaying interesting
|
|
141
|
+
runs. Backed by `src/belote/belatro/ghost_run.py`.
|
|
142
|
+
- `NO_COLOR=<any-non-empty>` — suppress truecolor SGR escapes from
|
|
143
|
+
`fg()` / `bg()` per the [no-color.org](https://no-color.org/) spec.
|
|
144
|
+
Bold/dim/underline/reverse/strikethrough and cursor sequences remain
|
|
145
|
+
(they aren't color). Added in 3.9.0. Backed by `src/belote/ansi.py`.
|
|
146
|
+
|
|
147
|
+
## Releasing a New Version
|
|
148
|
+
|
|
149
|
+
### Code-only update (push to GitHub without releasing a new PyPI version)
|
|
150
|
+
|
|
151
|
+
If you're just iterating on code, fixing typos, updating docs, etc., and don't want to cut a new PyPI release yet:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
git add <files>
|
|
155
|
+
git commit -m "<what changed>"
|
|
156
|
+
git push origin master
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Releasing a New Version (Manual)
|
|
160
|
+
|
|
161
|
+
1. **Bump the version** in BOTH `pyproject.toml` AND `src/belote/__init__.py`
|
|
162
|
+
(they must stay in sync — `belote --version` / `belatro --version` read
|
|
163
|
+
`__version__` while PyPI / pipx read `pyproject.toml`).
|
|
164
|
+
2. **Add a CHANGELOG entry** at the top of `CHANGELOG.md`.
|
|
165
|
+
3. **Clean stale build artifacts:**
|
|
166
|
+
```bash
|
|
167
|
+
rm -rf dist/ build/ *.egg-info/
|
|
168
|
+
```
|
|
169
|
+
4. **Build, validate, and upload:**
|
|
170
|
+
```bash
|
|
171
|
+
pipx run build --sdist --wheel
|
|
172
|
+
pipx run twine check dist/*
|
|
173
|
+
pipx run twine upload dist/*
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
*Note: `twine upload` will prompt for your PyPI credentials or use your `~/.pypirc` file.*
|
|
177
|
+
|
|
178
|
+
5. **Commit and tag in git:**
|
|
179
|
+
```bash
|
|
180
|
+
git add pyproject.toml CHANGELOG.md
|
|
181
|
+
git commit -m "Release vX.Y.Z"
|
|
182
|
+
git tag -a vX.Y.Z -m "vX.Y.Z"
|
|
183
|
+
git push origin master --tags
|
|
184
|
+
```
|
|
185
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: belote-cli
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.7.2
|
|
4
4
|
Summary: A 4-player terminal card game
|
|
5
5
|
Project-URL: Homepage, https://github.com/ElysiumDisc/belote
|
|
6
6
|
Project-URL: Repository, https://github.com/ElysiumDisc/belote
|
|
@@ -175,7 +175,8 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
175
175
|
|
|
176
176
|
**General:**
|
|
177
177
|
- `?`: Show keyboard shortcut help
|
|
178
|
-
- `I`
|
|
178
|
+
- `I` *(BelAtro only)*: Toggle the BelAtro top HUD overlay AND the persistent slot-machine tally readout — see the BelAtro gameplay note below for details
|
|
179
|
+
- `V` *(4.7.0, BelAtro only)*: Open the **Inventory** overlay (jokers, vouchers, consumables, permanent bonuses, contract levels). ↑/↓ navigate, Enter for detail, Esc/V/Q close. Distinct from `C` (Consumables tray, action-oriented); `V` is read-only inspection.
|
|
179
180
|
- `Q`: Quit to main menu or exit
|
|
180
181
|
- `H`: View Game History (round-by-round, with contract / taker / tricks / declarations)
|
|
181
182
|
- `T`: Cycle UI Theme
|
|
@@ -197,11 +198,15 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
197
198
|
- `Z`: Undo last move
|
|
198
199
|
- `Space` or `Esc`: Skip animations
|
|
199
200
|
- During bidding round 2: `P` = Pass, `X` = Tout Atout, `N` = Sans Atout *(remapped from A/S in 4.5.0 to make room for WASD nav)*
|
|
200
|
-
- `I`
|
|
201
|
+
- `I` *(4.6.3, BelAtro only)*: Toggle the BelAtro top HUD (joker pip strip, ante/blind/target line, chips×mult score, trust bar, synergy tooltip) AND, since 4.7.0, the persistent slot-machine tally readout at the bottom. Visible by default; press `I` to hide both and reveal the classic Belote HUD's `Trump:` / `Taker:` fields underneath. Press `I` again to bring them back.
|
|
202
|
+
- `V` *(4.7.0, BelAtro only)*: Open the read-only inventory overlay (jokers, vouchers, consumables, permanent bonuses, contract levels). Pre-4.7.0 this was an alias of `I`; it's now its own thing.
|
|
201
203
|
|
|
202
204
|
## Features
|
|
203
205
|
|
|
204
206
|
- **BelAtro Roguelite Mode:** A massive expansion featuring 42 Jokers, 12 Tarot cards, 8 Planets, 12 Vouchers, and permanent upgrades.
|
|
207
|
+
- **Dix de Der Heist (4.7.0):** Take a contract and declare a heist before trick 1 — win trick 8 for a `×(1 + interest_rate)` Mult multiplier on your round score; lose it and forfeit your card chips from tricks 1–7. Gated on owning **La Voûte** voucher (`interest_rate > 0`). AI never declares.
|
|
208
|
+
- **Slot-Machine Score Tally (4.7.0):** Per-trick odometer animation replaces the static popup. Chip-bucket fills, mult pulses, total ticks toward target, and a flame row crowns the odometer when you blow past 120% of the blind. Skippable on SPACE / ESC / ENTER. Final readout persists in the HUD between tricks (toggled by `I` alongside the top HUD). Suppressed under Le Brouillard (`hide_hud`) and La Compétition (`separate_scoring`).
|
|
209
|
+
- **Inventory Overlay on V (4.7.0):** Press `V` mid-game to inspect everything you own — jokers (with edition tags + per-edition bonus blurb), vouchers, consumables, permanent chip / mult bonuses, and per-contract planet levels. List view → ↑/↓ navigate, Enter for detail, Esc/V/Q close. Read-only counterpart to the `C` consumables-action tray.
|
|
205
210
|
- **Collection (Almanac):** Persistent tracker to browse every Joker, Planet, and Voucher you've discovered across your runs.
|
|
206
211
|
- **Full Boss Blind Suite:** All 21 unique bosses implemented, including complex mechanics like *L'Anarchie* (dynamic trump) and *La Rupture* (no consecutive wins).
|
|
207
212
|
- **Multiplier Scoring:** Use items to stack Multipliers and reach scores in the millions.
|
|
@@ -258,7 +263,7 @@ belote/
|
|
|
258
263
|
│ ├── input.py # Platform-dispatched key reader and interruptible sleep
|
|
259
264
|
│ ├── stats.py # Global and session statistics tracking
|
|
260
265
|
│ └── rules.py # Game rules content
|
|
261
|
-
├── tests/ # Comprehensive test suite (
|
|
266
|
+
├── tests/ # Comprehensive test suite (1003 tests)
|
|
262
267
|
├── scripts/ # Performance benchmarks
|
|
263
268
|
├── pyproject.toml # Build system and dev dependencies (ruff/mypy)
|
|
264
269
|
├── LICENSE # MIT License
|
|
@@ -273,14 +278,14 @@ belote/
|
|
|
273
278
|
PYTHONPATH=src pytest
|
|
274
279
|
```
|
|
275
280
|
|
|
276
|
-
Currently **
|
|
281
|
+
Currently **1003 tests** passing with 100% coverage on game-logic modules (4.7.2).
|
|
277
282
|
|
|
278
283
|
## Technical Integrity
|
|
279
284
|
|
|
280
285
|
The codebase is strictly validated with the following tools:
|
|
281
286
|
- **mypy**: 0 errors (strict type safety)
|
|
282
287
|
- **ruff**: 0 violations (linting & formatting)
|
|
283
|
-
- **pytest**:
|
|
288
|
+
- **pytest**: 1003/1003 passed
|
|
284
289
|
- **Functional Architecture**: Purely immutable state transitions using `dataclasses.replace`
|
|
285
290
|
- **Performance**: High-efficiency rendering and sub-millisecond AI decision times (see `scripts/benchmark.py`)
|
|
286
291
|
|
|
@@ -132,7 +132,8 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
132
132
|
|
|
133
133
|
**General:**
|
|
134
134
|
- `?`: Show keyboard shortcut help
|
|
135
|
-
- `I`
|
|
135
|
+
- `I` *(BelAtro only)*: Toggle the BelAtro top HUD overlay AND the persistent slot-machine tally readout — see the BelAtro gameplay note below for details
|
|
136
|
+
- `V` *(4.7.0, BelAtro only)*: Open the **Inventory** overlay (jokers, vouchers, consumables, permanent bonuses, contract levels). ↑/↓ navigate, Enter for detail, Esc/V/Q close. Distinct from `C` (Consumables tray, action-oriented); `V` is read-only inspection.
|
|
136
137
|
- `Q`: Quit to main menu or exit
|
|
137
138
|
- `H`: View Game History (round-by-round, with contract / taker / tricks / declarations)
|
|
138
139
|
- `T`: Cycle UI Theme
|
|
@@ -154,11 +155,15 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
154
155
|
- `Z`: Undo last move
|
|
155
156
|
- `Space` or `Esc`: Skip animations
|
|
156
157
|
- During bidding round 2: `P` = Pass, `X` = Tout Atout, `N` = Sans Atout *(remapped from A/S in 4.5.0 to make room for WASD nav)*
|
|
157
|
-
- `I`
|
|
158
|
+
- `I` *(4.6.3, BelAtro only)*: Toggle the BelAtro top HUD (joker pip strip, ante/blind/target line, chips×mult score, trust bar, synergy tooltip) AND, since 4.7.0, the persistent slot-machine tally readout at the bottom. Visible by default; press `I` to hide both and reveal the classic Belote HUD's `Trump:` / `Taker:` fields underneath. Press `I` again to bring them back.
|
|
159
|
+
- `V` *(4.7.0, BelAtro only)*: Open the read-only inventory overlay (jokers, vouchers, consumables, permanent bonuses, contract levels). Pre-4.7.0 this was an alias of `I`; it's now its own thing.
|
|
158
160
|
|
|
159
161
|
## Features
|
|
160
162
|
|
|
161
163
|
- **BelAtro Roguelite Mode:** A massive expansion featuring 42 Jokers, 12 Tarot cards, 8 Planets, 12 Vouchers, and permanent upgrades.
|
|
164
|
+
- **Dix de Der Heist (4.7.0):** Take a contract and declare a heist before trick 1 — win trick 8 for a `×(1 + interest_rate)` Mult multiplier on your round score; lose it and forfeit your card chips from tricks 1–7. Gated on owning **La Voûte** voucher (`interest_rate > 0`). AI never declares.
|
|
165
|
+
- **Slot-Machine Score Tally (4.7.0):** Per-trick odometer animation replaces the static popup. Chip-bucket fills, mult pulses, total ticks toward target, and a flame row crowns the odometer when you blow past 120% of the blind. Skippable on SPACE / ESC / ENTER. Final readout persists in the HUD between tricks (toggled by `I` alongside the top HUD). Suppressed under Le Brouillard (`hide_hud`) and La Compétition (`separate_scoring`).
|
|
166
|
+
- **Inventory Overlay on V (4.7.0):** Press `V` mid-game to inspect everything you own — jokers (with edition tags + per-edition bonus blurb), vouchers, consumables, permanent chip / mult bonuses, and per-contract planet levels. List view → ↑/↓ navigate, Enter for detail, Esc/V/Q close. Read-only counterpart to the `C` consumables-action tray.
|
|
162
167
|
- **Collection (Almanac):** Persistent tracker to browse every Joker, Planet, and Voucher you've discovered across your runs.
|
|
163
168
|
- **Full Boss Blind Suite:** All 21 unique bosses implemented, including complex mechanics like *L'Anarchie* (dynamic trump) and *La Rupture* (no consecutive wins).
|
|
164
169
|
- **Multiplier Scoring:** Use items to stack Multipliers and reach scores in the millions.
|
|
@@ -215,7 +220,7 @@ belote/
|
|
|
215
220
|
│ ├── input.py # Platform-dispatched key reader and interruptible sleep
|
|
216
221
|
│ ├── stats.py # Global and session statistics tracking
|
|
217
222
|
│ └── rules.py # Game rules content
|
|
218
|
-
├── tests/ # Comprehensive test suite (
|
|
223
|
+
├── tests/ # Comprehensive test suite (1003 tests)
|
|
219
224
|
├── scripts/ # Performance benchmarks
|
|
220
225
|
├── pyproject.toml # Build system and dev dependencies (ruff/mypy)
|
|
221
226
|
├── LICENSE # MIT License
|
|
@@ -230,14 +235,14 @@ belote/
|
|
|
230
235
|
PYTHONPATH=src pytest
|
|
231
236
|
```
|
|
232
237
|
|
|
233
|
-
Currently **
|
|
238
|
+
Currently **1003 tests** passing with 100% coverage on game-logic modules (4.7.2).
|
|
234
239
|
|
|
235
240
|
## Technical Integrity
|
|
236
241
|
|
|
237
242
|
The codebase is strictly validated with the following tools:
|
|
238
243
|
- **mypy**: 0 errors (strict type safety)
|
|
239
244
|
- **ruff**: 0 violations (linting & formatting)
|
|
240
|
-
- **pytest**:
|
|
245
|
+
- **pytest**: 1003/1003 passed
|
|
241
246
|
- **Functional Architecture**: Purely immutable state transitions using `dataclasses.replace`
|
|
242
247
|
- **Performance**: High-efficiency rendering and sub-millisecond AI decision times (see `scripts/benchmark.py`)
|
|
243
248
|
|