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.
Files changed (156) hide show
  1. belote_cli-4.7.2/.antigravitycli/69dd05ce-2c1c-4419-8755-e4dd0d4495e8.json +1 -0
  2. {belote_cli-4.6.5 → belote_cli-4.7.2}/CHANGELOG.md +237 -0
  3. belote_cli-4.7.2/DEVELOPMENT.md +185 -0
  4. {belote_cli-4.6.5 → belote_cli-4.7.2}/PKG-INFO +11 -6
  5. {belote_cli-4.6.5 → belote_cli-4.7.2}/README.md +10 -5
  6. {belote_cli-4.6.5 → belote_cli-4.7.2}/pyproject.toml +1 -1
  7. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/__init__.py +1 -1
  8. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ai.py +132 -34
  9. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/scoring.py +65 -0
  10. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/engine/round_driver.py +38 -0
  11. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/main.py +275 -106
  12. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/progression/save.py +10 -1
  13. belote_cli-4.7.2/src/belote/belatro/ui/announce.py +419 -0
  14. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/hud.py +61 -1
  15. belote_cli-4.7.2/src/belote/belatro/ui/inventory.py +345 -0
  16. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/game.py +7 -1
  17. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/gameflow.py +21 -19
  18. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/input.py +12 -2
  19. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/scoring.py +57 -67
  20. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/prompts.py +30 -3
  21. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_boss_modifiers_integration.py +33 -0
  22. belote_cli-4.7.2/tests/belatro/test_heist.py +657 -0
  23. belote_cli-4.7.2/tests/belatro/test_inventory_overlay.py +202 -0
  24. belote_cli-4.7.2/tests/belatro/test_slot_machine_tally.py +351 -0
  25. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_ai.py +92 -1
  26. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_alt_screen_scroll.py +10 -5
  27. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_belote.py +104 -8
  28. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_bidding_all_pass.py +50 -0
  29. {belote_cli-4.6.5 → belote_cli-4.7.2}/uv.lock +1 -1
  30. belote_cli-4.6.5/DEVELOPMENT.md +0 -214
  31. belote_cli-4.6.5/src/belote/belatro/ui/announce.py +0 -217
  32. {belote_cli-4.6.5 → belote_cli-4.7.2}/.claude/settings.local.json +0 -0
  33. {belote_cli-4.6.5 → belote_cli-4.7.2}/.gitignore +0 -0
  34. {belote_cli-4.6.5 → belote_cli-4.7.2}/.python-version +0 -0
  35. {belote_cli-4.6.5 → belote_cli-4.7.2}/LICENSE +0 -0
  36. {belote_cli-4.6.5 → belote_cli-4.7.2}/scripts/benchmark.py +0 -0
  37. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/__init__.py +0 -0
  38. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/a11y.py +0 -0
  39. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/achievements.py +0 -0
  40. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ansi.py +0 -0
  41. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/__init__.py +0 -0
  42. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/__init__.py +0 -0
  43. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/economy.py +0 -0
  44. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/round_ledger.py +0 -0
  45. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/core/run_state.py +0 -0
  46. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/engine/__init__.py +0 -0
  47. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/engine/event_bus.py +0 -0
  48. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/engine/modifier_patch.py +0 -0
  49. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ghost_run.py +0 -0
  50. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/__init__.py +0 -0
  51. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/base.py +0 -0
  52. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/__init__.py +0 -0
  53. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/annonces.py +0 -0
  54. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/coinche.py +0 -0
  55. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/contract.py +0 -0
  56. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/corrupted.py +0 -0
  57. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/economy.py +0 -0
  58. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/hand_comp.py +0 -0
  59. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/jokers/trick_timing.py +0 -0
  60. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/partner_jokers/__init__.py +0 -0
  61. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/partner_jokers/passive.py +0 -0
  62. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/partner_jokers/risky.py +0 -0
  63. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/partner_jokers/shaper.py +0 -0
  64. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/planets.py +0 -0
  65. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/registry.py +0 -0
  66. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/tarots.py +0 -0
  67. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/items/vouchers.py +0 -0
  68. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/partner/__init__.py +0 -0
  69. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/partner/partner_state.py +0 -0
  70. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/partner/personality.py +0 -0
  71. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/partner/trust.py +0 -0
  72. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/progression/__init__.py +0 -0
  73. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/progression/unlocks.py +0 -0
  74. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/__init__.py +0 -0
  75. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/ante.py +0 -0
  76. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/ante_themes.py +0 -0
  77. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/boss.py +0 -0
  78. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/decks.py +0 -0
  79. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run/shop.py +0 -0
  80. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/run_summary.py +0 -0
  81. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/__init__.py +0 -0
  82. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/collection.py +0 -0
  83. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/consumables.py +0 -0
  84. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/history.py +0 -0
  85. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/menu.py +0 -0
  86. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/rules.py +0 -0
  87. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/shop.py +0 -0
  88. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/belatro/ui/trust_bar.py +0 -0
  89. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/config.py +0 -0
  90. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/context.py +0 -0
  91. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/deck.py +0 -0
  92. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/main.py +0 -0
  93. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/replay.py +0 -0
  94. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/rules.py +0 -0
  95. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/stats.py +0 -0
  96. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/themes.py +0 -0
  97. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/__init__.py +0 -0
  98. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/announce.py +0 -0
  99. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/fit_guard.py +0 -0
  100. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/layout.py +0 -0
  101. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/menu.py +0 -0
  102. {belote_cli-4.6.5 → belote_cli-4.7.2}/src/belote/ui/render.py +0 -0
  103. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/__init__.py +0 -0
  104. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/__init__.py +0 -0
  105. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_belatro.py +0 -0
  106. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_boss_contracts.py +0 -0
  107. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_collection_logic.py +0 -0
  108. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_consumables_ui.py +0 -0
  109. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_contract_unlocks.py +0 -0
  110. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_dead_flag_fixes.py +0 -0
  111. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_deck_variants.py +0 -0
  112. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_decks_4_5.py +0 -0
  113. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_endless.py +0 -0
  114. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_event_bus.py +0 -0
  115. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_ghost_run.py +0 -0
  116. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_history_overlay.py +0 -0
  117. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_hud_synergy.py +0 -0
  118. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_hud_toggle.py +0 -0
  119. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_joker_contracts.py +0 -0
  120. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_jokers_4_5.py +0 -0
  121. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_partner_jokers.py +0 -0
  122. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_partner_trust.py +0 -0
  123. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_phase0_coverage.py +0 -0
  124. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_phase1_plumbing.py +0 -0
  125. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_phase2_content.py +0 -0
  126. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_phase3_meta.py +0 -0
  127. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_progression.py +0 -0
  128. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_round_driver.py +0 -0
  129. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_run_summary.py +0 -0
  130. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_shop_empty_pools.py +0 -0
  131. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/belatro/test_voucher_idempotency.py +0 -0
  132. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/perf_baselines.json +0 -0
  133. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_a11y.py +0 -0
  134. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_achievements.py +0 -0
  135. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_announce_stats.py +0 -0
  136. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_ansi_helpers.py +0 -0
  137. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_benchmark_smoke.py +0 -0
  138. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_declaration_tiebreak.py +0 -0
  139. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_extended.py +0 -0
  140. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_game_logic.py +0 -0
  141. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_gameflow.py +0 -0
  142. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_hand_auto_sort.py +0 -0
  143. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_history_overlay_cache.py +0 -0
  144. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_input_eof.py +0 -0
  145. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_input_wasd.py +0 -0
  146. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_invariants.py +0 -0
  147. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_layout.py +0 -0
  148. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_new_coverage.py +0 -0
  149. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_no_color.py +0 -0
  150. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_official_rules.py +0 -0
  151. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_perf_regression.py +0 -0
  152. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_properties.py +0 -0
  153. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_render_diff.py +0 -0
  154. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_render_felt_polish.py +0 -0
  155. {belote_cli-4.6.5 → belote_cli-4.7.2}/tests/test_replay.py +0 -0
  156. {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.6.5
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` or `V` *(BelAtro only)*: Toggle the BelAtro top HUD overlay — see the BelAtro gameplay note below for details
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` or `V` *(4.6.3, BelAtro only)*: Toggle the BelAtro top HUD (joker pip strip, ante/blind/target line, chips×mult score, trust bar, synergy tooltip). Visible by default; press `I` to hide it and reveal the classic Belote HUD's `Trump:` / `Taker:` fields underneath. Press `I` again to bring the overlay back.
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 (941 tests)
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 **941 tests** passing with 100% coverage on game-logic modules (4.6.5).
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**: 941/941 passed
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` or `V` *(BelAtro only)*: Toggle the BelAtro top HUD overlay — see the BelAtro gameplay note below for details
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` or `V` *(4.6.3, BelAtro only)*: Toggle the BelAtro top HUD (joker pip strip, ante/blind/target line, chips×mult score, trust bar, synergy tooltip). Visible by default; press `I` to hide it and reveal the classic Belote HUD's `Trump:` / `Taker:` fields underneath. Press `I` again to bring the overlay back.
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 (941 tests)
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 **941 tests** passing with 100% coverage on game-logic modules (4.6.5).
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**: 941/941 passed
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "belote-cli"
7
- version = "4.6.5"
7
+ version = "4.7.2"
8
8
  description = "A 4-player terminal card game"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
- __version__ = "4.6.5"
1
+ __version__ = "4.7.2"
2
2
 
3
3
  __all__ = ["__version__"]