belote-cli 4.9.7__tar.gz → 4.10.0__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 (204) hide show
  1. {belote_cli-4.9.7 → belote_cli-4.10.0}/CHANGELOG.md +97 -0
  2. {belote_cli-4.9.7 → belote_cli-4.10.0}/DEVELOPMENT.md +24 -2
  3. {belote_cli-4.9.7 → belote_cli-4.10.0}/PKG-INFO +27 -11
  4. {belote_cli-4.9.7 → belote_cli-4.10.0}/README.md +26 -10
  5. {belote_cli-4.9.7 → belote_cli-4.10.0}/pyproject.toml +1 -1
  6. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/__init__.py +1 -1
  7. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/a11y.py +11 -0
  8. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ai.py +14 -18
  9. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ansi.py +5 -12
  10. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/core/round_ledger.py +5 -5
  11. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/core/run_state.py +3 -0
  12. belote_cli-4.10.0/src/belote/belatro/core/scoring.py +636 -0
  13. belote_cli-4.10.0/src/belote/belatro/core/state.py +46 -0
  14. belote_cli-4.10.0/src/belote/belatro/daily.py +224 -0
  15. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/engine/modifier_patch.py +11 -10
  16. belote_cli-4.10.0/src/belote/belatro/engine/round_driver.py +644 -0
  17. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ghost_run.py +3 -7
  18. belote_cli-4.10.0/src/belote/belatro/integration.py +75 -0
  19. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/trick_timing.py +4 -1
  20. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/main.py +403 -335
  21. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/partner/personality.py +2 -1
  22. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/partner/trust.py +1 -9
  23. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/progression/save.py +3 -6
  24. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/boss.py +8 -5
  25. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/shop.py +9 -3
  26. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run_summary.py +4 -6
  27. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/announce.py +5 -56
  28. belote_cli-4.10.0/src/belote/belatro/ui/collection.py +155 -0
  29. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/hud.py +9 -18
  30. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/rules.py +27 -31
  31. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/config.py +6 -31
  32. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/deck.py +17 -12
  33. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/game.py +65 -96
  34. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/gameflow.py +243 -257
  35. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/input.py +27 -54
  36. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/main.py +48 -25
  37. belote_cli-4.10.0/src/belote/paths.py +45 -0
  38. belote_cli-4.10.0/src/belote/round_engine.py +118 -0
  39. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/scoring.py +21 -23
  40. belote_cli-4.10.0/src/belote/settings.py +158 -0
  41. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/stats.py +18 -8
  42. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/themes.py +17 -1
  43. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/anim.py +6 -0
  44. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/announce.py +78 -18
  45. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/menu.py +31 -92
  46. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/prompts.py +37 -32
  47. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/render.py +64 -90
  48. belote_cli-4.10.0/src/belote/ui/settings_menu.py +167 -0
  49. belote_cli-4.10.0/src/belote/ui/text.py +21 -0
  50. belote_cli-4.10.0/src/belote/variants.py +49 -0
  51. belote_cli-4.10.0/tests/belatro/_events.py +79 -0
  52. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_audit_round_fixes.py +35 -7
  53. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_boss_contracts.py +91 -15
  54. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_boss_modifiers_integration.py +33 -31
  55. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_competition_malediction.py +9 -17
  56. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_contract_unlocks.py +6 -5
  57. belote_cli-4.10.0/tests/belatro/test_daily.py +108 -0
  58. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_dead_flag_fixes.py +113 -43
  59. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_deck_variants.py +3 -2
  60. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_decks_4_5.py +10 -9
  61. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_design_calls.py +3 -2
  62. belote_cli-4.10.0/tests/belatro/test_economy.py +109 -0
  63. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_event_bus.py +64 -0
  64. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_handler_registry.py +2 -1
  65. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_heist.py +10 -8
  66. belote_cli-4.10.0/tests/belatro/test_jokers.py +520 -0
  67. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_jokers_4_5.py +4 -3
  68. belote_cli-4.10.0/tests/belatro/test_partner_jokers.py +690 -0
  69. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_partner_trust.py +124 -8
  70. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_phase0_coverage.py +17 -15
  71. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_phase1_plumbing.py +2 -2
  72. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_phase2_content.py +5 -4
  73. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_progression.py +113 -0
  74. belote_cli-4.10.0/tests/belatro/test_registry_integrity.py +131 -0
  75. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_round_driver.py +22 -22
  76. belote_cli-4.10.0/tests/belatro/test_run_state.py +185 -0
  77. belote_cli-4.10.0/tests/belatro/test_score_accumulator.py +117 -0
  78. belote_cli-4.10.0/tests/belatro/test_screens.py +187 -0
  79. belote_cli-4.10.0/tests/belatro/test_shop.py +257 -0
  80. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_slot_machine_tally.py +10 -8
  81. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_a11y.py +6 -5
  82. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_ai.py +13 -12
  83. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_ai_deluge_voids.py +3 -7
  84. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_alt_screen_scroll.py +3 -3
  85. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_announce_stats.py +3 -0
  86. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_belote.py +8 -6
  87. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_deluge_legal_cards.py +3 -2
  88. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_g2_ai_signals.py +1 -1
  89. belote_cli-4.10.0/tests/test_gameflow_headless.py +110 -0
  90. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_input_eof.py +47 -2
  91. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_invariants.py +8 -8
  92. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_layout.py +3 -2
  93. belote_cli-4.10.0/tests/test_legal_bids.py +31 -0
  94. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_menu_target_and_exit.py +14 -18
  95. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_official_rules.py +3 -2
  96. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_properties.py +6 -4
  97. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_render_diff.py +4 -10
  98. belote_cli-4.10.0/tests/test_repo_layout.py +46 -0
  99. belote_cli-4.10.0/tests/test_round_engine.py +109 -0
  100. belote_cli-4.10.0/tests/test_settings.py +117 -0
  101. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_stats_persistence.py +73 -1
  102. {belote_cli-4.9.7 → belote_cli-4.10.0}/uv.lock +1 -1
  103. belote_cli-4.9.7/src/belote/belatro/core/scoring.py +0 -689
  104. belote_cli-4.9.7/src/belote/belatro/engine/round_driver.py +0 -615
  105. belote_cli-4.9.7/src/belote/belatro/ui/collection.py +0 -153
  106. belote_cli-4.9.7/tests/belatro/__init__.py +0 -0
  107. belote_cli-4.9.7/tests/belatro/test_belatro.py +0 -2037
  108. belote_cli-4.9.7/tests/belatro/test_partner_jokers.py +0 -357
  109. belote_cli-4.9.7/tests/belatro/test_registry_integrity.py +0 -61
  110. {belote_cli-4.9.7 → belote_cli-4.10.0}/.gitignore +0 -0
  111. {belote_cli-4.9.7 → belote_cli-4.10.0}/.python-version +0 -0
  112. {belote_cli-4.9.7 → belote_cli-4.10.0}/LICENSE +0 -0
  113. {belote_cli-4.9.7 → belote_cli-4.10.0}/scripts/benchmark.py +0 -0
  114. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/achievements.py +0 -0
  115. {belote_cli-4.9.7/src → belote_cli-4.10.0/src/belote/belatro}/__init__.py +0 -0
  116. {belote_cli-4.9.7/src/belote/belatro → belote_cli-4.10.0/src/belote/belatro/core}/__init__.py +0 -0
  117. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/core/economy.py +0 -0
  118. {belote_cli-4.9.7/src/belote/belatro/core → belote_cli-4.10.0/src/belote/belatro/engine}/__init__.py +0 -0
  119. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/engine/event_bus.py +0 -0
  120. {belote_cli-4.9.7/src/belote/belatro/engine → belote_cli-4.10.0/src/belote/belatro/items}/__init__.py +0 -0
  121. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/base.py +0 -0
  122. {belote_cli-4.9.7/src/belote/belatro/items → belote_cli-4.10.0/src/belote/belatro/items/jokers}/__init__.py +0 -0
  123. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/annonces.py +0 -0
  124. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/coinche.py +0 -0
  125. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/contract.py +0 -0
  126. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/corrupted.py +0 -0
  127. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/economy.py +0 -0
  128. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/hand_comp.py +0 -0
  129. {belote_cli-4.9.7/src/belote/belatro/items/jokers → belote_cli-4.10.0/src/belote/belatro/items/partner_jokers}/__init__.py +0 -0
  130. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/partner_jokers/passive.py +0 -0
  131. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/partner_jokers/risky.py +0 -0
  132. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/partner_jokers/shaper.py +0 -0
  133. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/planets.py +0 -0
  134. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/registry.py +0 -0
  135. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/tarots.py +0 -0
  136. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/vouchers.py +0 -0
  137. {belote_cli-4.9.7/src/belote/belatro/items/partner_jokers → belote_cli-4.10.0/src/belote/belatro/partner}/__init__.py +0 -0
  138. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/partner/partner_state.py +0 -0
  139. {belote_cli-4.9.7/src/belote/belatro/partner → belote_cli-4.10.0/src/belote/belatro/progression}/__init__.py +0 -0
  140. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/progression/unlocks.py +0 -0
  141. {belote_cli-4.9.7/src/belote/belatro/progression → belote_cli-4.10.0/src/belote/belatro/run}/__init__.py +0 -0
  142. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/ante.py +0 -0
  143. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/ante_themes.py +0 -0
  144. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/decks.py +0 -0
  145. {belote_cli-4.9.7/src/belote/belatro/run → belote_cli-4.10.0/src/belote/belatro/ui}/__init__.py +0 -0
  146. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/consumables.py +0 -0
  147. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/history.py +0 -0
  148. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/inventory.py +0 -0
  149. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/menu.py +0 -0
  150. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/shop.py +0 -0
  151. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/trust_bar.py +0 -0
  152. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/context.py +0 -0
  153. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/replay.py +0 -0
  154. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/rules.py +0 -0
  155. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/__init__.py +0 -0
  156. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/fit_guard.py +0 -0
  157. {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/layout.py +0 -0
  158. {belote_cli-4.9.7/src/belote/belatro/ui → belote_cli-4.10.0/tests}/__init__.py +0 -0
  159. {belote_cli-4.9.7/tests → belote_cli-4.10.0/tests/belatro}/__init__.py +0 -0
  160. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_collection_logic.py +0 -0
  161. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_consumables_ui.py +0 -0
  162. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_endless.py +0 -0
  163. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_ghost_run.py +0 -0
  164. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_history_overlay.py +0 -0
  165. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_hud_synergy.py +0 -0
  166. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_hud_toggle.py +0 -0
  167. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_inventory_overlay.py +0 -0
  168. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_joker_callouts.py +0 -0
  169. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_joker_contracts.py +0 -0
  170. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_phase3_meta.py +0 -0
  171. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_run_summary.py +0 -0
  172. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_shop_animations.py +0 -0
  173. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_shop_empty_pools.py +0 -0
  174. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_voucher_idempotency.py +0 -0
  175. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/conftest.py +0 -0
  176. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/perf_baselines.json +0 -0
  177. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_achievements.py +0 -0
  178. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_ai_sans_atout.py +0 -0
  179. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_anim_helpers.py +0 -0
  180. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_ansi_helpers.py +0 -0
  181. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_belote_stinger.py +0 -0
  182. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_benchmark_smoke.py +0 -0
  183. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_bidding_all_pass.py +0 -0
  184. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_declaration_tiebreak.py +0 -0
  185. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_extended.py +0 -0
  186. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_g1_coinche_classic.py +0 -0
  187. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_game_logic.py +0 -0
  188. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_gameflow.py +0 -0
  189. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_hand_auto_sort.py +0 -0
  190. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_history_overlay_cache.py +0 -0
  191. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_hud_fit.py +0 -0
  192. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_input_escape_seqs.py +0 -0
  193. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_input_wasd.py +0 -0
  194. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_new_coverage.py +0 -0
  195. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_no_color.py +0 -0
  196. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_perf_regression.py +0 -0
  197. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_render_felt_polish.py +0 -0
  198. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_render_golden.py +0 -0
  199. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_render_hand_lift.py +0 -0
  200. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_replay.py +0 -0
  201. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_scoring_totals.py +0 -0
  202. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_undo.py +0 -0
  203. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_wcwidth.py +0 -0
  204. {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_winner_glow.py +0 -0
@@ -5,6 +5,103 @@ 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.10.0] - 2026-11-01
9
+
10
+ Verification pass over two external audit reports (every claim checked
11
+ against the code), then the full follow-up: BelAtro becomes a true expansion
12
+ of the core game, one shared round engine, the god functions split, and three
13
+ features (Settings, achievement toasts, Daily Challenge). Refactors were
14
+ verified with seeded whole-round fingerprints (BelAtro: 800 rounds across all
15
+ bosses/jokers/flags; classic: 450 rounds incl. heavy undo) — byte-identical
16
+ except the intended undo fix. Tests 1228 → 1286, coverage 79% → 83%.
17
+
18
+ ### Added
19
+ - **Settings screen** (main menu → Settings), persisted to `settings.json` in
20
+ the config dir: AI difficulty (per seat), target, speed, theme, animations,
21
+ fast redraw, screen-reader hints, replay analysis, and expansion toggles
22
+ (BelAtro ghost recording). `BELOTE_*` env vars still override while set; the
23
+ CLI flags now override the saved values for one session.
24
+ - **Achievement toasts:** unlocking an achievement pops a boxed toast (and a
25
+ screen-reader line) at the end of the round / game.
26
+ - **Daily Challenge:** one date-seeded BelAtro run per day, identical for
27
+ everyone (fixed deck, starter shop pool, no endless), with a share code and
28
+ a local best-result record (`daily.json`).
29
+ - **Screen-reader hints now announce declarations** (`a11y.announce_declaration`
30
+ existed but was never called).
31
+
32
+ ### Fixed
33
+ - **Classic: undo during play re-ran the coinche flow** — the player was
34
+ re-prompted to coinche and the AI defenders were silently re-polled
35
+ (shifting their RNG) every time an undo resumed the play phase.
36
+ - **BelAtro collection gallery** pinned the cursor to the top row (the list
37
+ was sliced from the selected item), hiding every item above it.
38
+ - **BelAtro rules screen** called `hide_cursor()` without writing it (no-op).
39
+ - **Le Divorce / La Trahison** could leave partner trust frozen at 0 if the
40
+ round aborted; the restore now runs in a `finally`.
41
+ - **BelAtro: Sans Atout bid from the normal prompt became a pass.**
42
+ `UICallbacks.prompt_bid` filtered UI sentinels with `isinstance(res, str)`,
43
+ which also matched `SANS_ATOUT_BID` (the plain string `"sans_atout"`).
44
+ - **L'Aristocrate ace payout ignored La Rupture / L'Anarchie.** Aces are now
45
+ credited via `compute_trick_winners` (new `_aces_won_by_team` helper) instead
46
+ of raw `trick_winner_seat` with the final trump.
47
+ - **mypy was blind to every absolute `belote.*` import.** A stray empty
48
+ `src/__init__.py` made mypy name modules `src.belote.*`; with
49
+ `ignore_missing_imports` all `from belote.… import` resolved to `Any`.
50
+ Removed it (guarded by `tests/test_repo_layout.py`) and fixed the 9 errors
51
+ it was hiding (incl. a possible `None.lower()` in the BelAtro rules screen).
52
+ - **Theme file ignored `XDG_CONFIG_HOME` / `%APPDATA%`** (hardcoded
53
+ `~/.config`, which also let the test suite write the real user config).
54
+ - **Main menu flicker:** the 300ms steam animation no longer clears the whole
55
+ screen each tick; it repaints in place.
56
+ - **Achievements are now visible** on the Statistics screen (they were
57
+ evaluated and saved but never shown).
58
+
59
+ ### Changed — architecture
60
+ - **The core never imports BelAtro** (enforced by `tests/test_repo_layout.py`).
61
+ Boss/deck/voucher rule switches moved from BelAtro's `BossModifiers` +
62
+ `_joker_state` keys to a core `RuleVariants` (`belote/variants.py`,
63
+ `GameState.variants`). BelAtro's chips/mult/money/joker state moved off the
64
+ core `GameState` onto `belatro/core/state.py::BelatroState`, a subclass that
65
+ core transitions (now generic over `GameState` subclasses) carry along.
66
+ The stats screen, bid-hint line, I/V keys and main-menu modes are now
67
+ registration hooks wired from the entry point via `belatro/integration.py`.
68
+ - **One round engine** (`belote/round_engine.py`): classic `gameflow` and
69
+ BelAtro `round_driver` are two `RoundPolicy` implementations of
70
+ `play_round`. Coinche level now lives on `state.coinche_level` in both modes.
71
+ - **God functions split:** `process_event` (369 lines) → per-event handler
72
+ methods; `drive_round` (450) → a round context + phase functions;
73
+ `_play_blind` (344) → setup / boss / settlement helpers.
74
+ - **Tests:** the 2037-line `test_belatro.py` split by topic (economy, score
75
+ accumulator, run state, jokers, shop, …); 2 duplicate tests removed; new
76
+ coverage for the round engine, headless classic rounds, the collection /
77
+ rules / shop screens, settings and the daily challenge.
78
+
79
+ ### Changed
80
+ - AI / target / speed / theme moved from the main menu into Settings.
81
+ - Shared key-alias table for the Unix and Windows readers; one
82
+ `ThemeManager.cycle()`; table-driven card art (byte-identical output);
83
+ `wrap_words` text helper; `BELOTE_NO_DIFF` read once instead of per frame.
84
+ - One `legal_bids(state)` in `game.py` replaces four hand-rolled copies
85
+ (bid prompt, bid renderer, BelAtro joker hooks, partner legality gate).
86
+ - One `deck.is_trump_card()` replaces the inline "trump suit or Déluge 7/8"
87
+ predicate in `deck.py`, `game.py`, `ai.py` and `round_driver.py`
88
+ (`_card_beats` keeps its Tout-Atout-exclusive variant, now commented).
89
+ - New `belote/paths.py` owns data/config locations (was 5 copies; the BelAtro
90
+ ones fell back to the CWD on Windows without `%APPDATA%`).
91
+ - Dix de Der uses `GLOBAL_CONFIG.LAST_TRICK_BONUS` everywhere (3 hardcoded 10s).
92
+ - BelAtro history entries are built from typed `GameState`/`ScoringBreakdown`
93
+ instead of `object` + `getattr`.
94
+
95
+ ### Removed
96
+ - `BelAtroAnnounce.score_popup` (slated for removal in 4.8) and its dead
97
+ overlay flag helpers.
98
+ - `ScoreAccumulator.partner_jokers_double` / `TrustTrack.partner_jokers_double`
99
+ (deprecated since 3.5.0; `partner_tier` replaces them).
100
+ - Dead code: `Seat.prev_seat`, `GameState.current_bidder`, `set_announced`,
101
+ `Config.ensure_stats_dir`, legacy `Config.CARD_*` / `MIN_TERM_*` /
102
+ `THEME_NAME`, `render.CARD_W/CARD_H/CARD_GAP/SIDE_COL_W`,
103
+ `ansi.save_cursor/restore_cursor/scroll_region`, unused HUD tables.
104
+
8
105
  ## [4.9.7] - 2026-11-01
9
106
 
10
107
  Repo-wide audit: four parallel reviewers swept the classic engine, the
@@ -171,6 +171,26 @@ PYTHONPATH=src python scripts/benchmark.py
171
171
 
172
172
  Use these as a regression-detection floor for future changes.
173
173
 
174
+ ## Architecture: core game vs. BelAtro expansion
175
+
176
+ - **The core never imports BelAtro.** `tests/test_repo_layout.py` enforces
177
+ it; only the composition root `belote/main.py` wires the expansion in via
178
+ `belote/belatro/integration.py` (menu modes, stats section, settings
179
+ toggle) and BelAtro installs its run-scoped hooks itself (history
180
+ override, bid hint, extension keys I/V).
181
+ - **One round engine.** `belote/round_engine.py::play_round` owns the phase
182
+ order (bidding with all-pass handling → contract → tricks). Classic
183
+ (`gameflow.ClassicRound`) and BelAtro (`belatro/engine/round_driver.py::
184
+ _RoundContext`) are `RoundPolicy` implementations — change the round's
185
+ flow there, once.
186
+ - **Rule variants are core.** Boss / deck / voucher effects that change how
187
+ Belote is played live on `GameState.variants` (`belote/variants.py`);
188
+ bosses only switch variants on.
189
+ - **Expansion state rides on a subclass.** BelAtro's chips / mult / money /
190
+ joker state live on `belatro/core/state.py::BelatroState`. Core
191
+ transitions are generic over `GameState` subclasses and build states with
192
+ `replace()`, so the subclass survives every move.
193
+
174
194
  ## Accessibility
175
195
 
176
196
  Set `BELOTE_A11Y=1` to emit one-line plain-text descriptions of card plays,
@@ -196,8 +216,10 @@ run won/lost).
196
216
 
197
217
  ## Optional Runtime Flags
198
218
 
199
- The following environment variables enable opt-in features. Each is read
200
- once at startup; toggling mid-run has no effect.
219
+ Players toggle these from the in-game **Settings** screen (`belote/settings.py`
220
+ → `settings.json`; `settings.apply()` pushes the values into the modules).
221
+ The environment variables below still work and *override* the saved setting
222
+ while set — handy for debugging and CI. Each is read once at startup.
201
223
 
202
224
  - `BELOTE_REPLAY=1` — after every Classic round, print a one-line summary
203
225
  of how often South's plays matched the Hard-AI's preferred line
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: belote-cli
3
- Version: 4.9.7
3
+ Version: 4.10.0
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
@@ -113,14 +113,14 @@ belatro
113
113
  .-'``'|-._ ) _.-|
114
114
  / .--.| `''---...........---''` |
115
115
  / / | BelAtro |
116
- | | | > Start Game < |
117
- | | | AI: < Hard > |
118
- | | | Target: < 1000 > |
119
- | | | Speed: < Normal > |
120
- | | | Theme: < Classic Green > |
116
+ | | | Daily Challenge |
117
+ | | | > Start Game < |
118
+ | | | Settings |
121
119
  | | | Rules & History |
122
- \ \ | Statistics |
123
- `\ `\ | Quit |
120
+ | | | Statistics |
121
+ | | | Quit |
122
+ \ \ | |
123
+ `\ `\ | |
124
124
  `\ `| |
125
125
  _/ /\ /
126
126
  (__/ \ /
@@ -166,10 +166,26 @@ belote
166
166
  # Play the new Roguelite expansion
167
167
  belatro
168
168
 
169
- # Custom settings
169
+ # One-off overrides of the saved settings (not persisted)
170
170
  belote --difficulty hard --target 500 --seed 123 --speed fast
171
171
  ```
172
172
 
173
+ **Settings** live in the main menu's *Settings* screen and are saved to
174
+ `settings.json` in your config directory (`$XDG_CONFIG_HOME/belote`, else
175
+ `~/.config/belote`; `%APPDATA%\belote` on Windows): AI difficulty (per seat
176
+ via Enter), target score, speed, theme, animations, fast redraw,
177
+ screen-reader hints, post-round replay analysis and BelAtro ghost-run
178
+ recording. The old `BELOTE_*` environment variables still work and override
179
+ the saved value while set.
180
+
181
+ **Daily Challenge** (main menu): one seeded BelAtro run per day, the same for
182
+ everyone (fixed deck, date-derived seed, starter shop pool, no endless).
183
+ The intro shows the day's number and a share code; your best result per day
184
+ is kept locally.
185
+
186
+ **Achievements** pop a toast the moment you unlock one and are listed on the
187
+ Statistics screen.
188
+
173
189
 
174
190
  ## Controls
175
191
 
@@ -219,7 +235,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
219
235
  - **Polished Felt Mat (3.9.4):** The trick mat now has a subtle vignette at its edges, a faint deterministic braille pip-dot texture (fabric-weave feel without intrusive glyphs), and — at standard/spacious terminal sizes — a decorative `╔═══◆═══...═══◆═══╗` outer frame with corner ornaments.
220
236
  - **Selection HUD (3.9.4):** Selecting a card in hand now paints a highlighted bar under it AND a centered `► A♠ — Trump ◄` readout below, color-coded by suit / trump / legality.
221
237
  - **Hand Sorting:** Strategic "play value" organization (honors grouped together) for better tactical awareness.
222
- - **Main Menu:** Simple single-player entry point with configurable AI difficulty, Target Score, and Speed.
238
+ - **Main Menu:** Simple single-player entry point; AI difficulty, Target Score, Speed and Theme live in the Settings screen.
223
239
  - **Undo/Redo:** Press `Z` to undo your last move during bidding or play.
224
240
  - **Statistics:** unified global tracking of games played, win rates, best rounds, and BelAtro expansion milestones.
225
241
  - **Responsive Layout (3 tiers):** Three preset layouts — **compact** (80×32, fits 1366×768), **standard** (96×38), **spacious** (120×48+). The game picks the largest preset that fits your terminal on every render, so resizing mid-game adapts automatically; cards, side columns, and HUD verbosity all scale with the preset. Vertical centering pads tall terminals so the game never clings to the top.
@@ -280,7 +296,7 @@ belote/
280
296
  PYTHONPATH=src pytest
281
297
  ```
282
298
 
283
- Currently **1228 tests** passing with 100% coverage on game-logic modules (4.9.7).
299
+ Currently **1286 tests** passing with 100% coverage on game-logic modules (4.10.0).
284
300
 
285
301
  ## Technical Integrity
286
302
 
@@ -70,14 +70,14 @@ belatro
70
70
  .-'``'|-._ ) _.-|
71
71
  / .--.| `''---...........---''` |
72
72
  / / | BelAtro |
73
- | | | > Start Game < |
74
- | | | AI: < Hard > |
75
- | | | Target: < 1000 > |
76
- | | | Speed: < Normal > |
77
- | | | Theme: < Classic Green > |
73
+ | | | Daily Challenge |
74
+ | | | > Start Game < |
75
+ | | | Settings |
78
76
  | | | Rules & History |
79
- \ \ | Statistics |
80
- `\ `\ | Quit |
77
+ | | | Statistics |
78
+ | | | Quit |
79
+ \ \ | |
80
+ `\ `\ | |
81
81
  `\ `| |
82
82
  _/ /\ /
83
83
  (__/ \ /
@@ -123,10 +123,26 @@ belote
123
123
  # Play the new Roguelite expansion
124
124
  belatro
125
125
 
126
- # Custom settings
126
+ # One-off overrides of the saved settings (not persisted)
127
127
  belote --difficulty hard --target 500 --seed 123 --speed fast
128
128
  ```
129
129
 
130
+ **Settings** live in the main menu's *Settings* screen and are saved to
131
+ `settings.json` in your config directory (`$XDG_CONFIG_HOME/belote`, else
132
+ `~/.config/belote`; `%APPDATA%\belote` on Windows): AI difficulty (per seat
133
+ via Enter), target score, speed, theme, animations, fast redraw,
134
+ screen-reader hints, post-round replay analysis and BelAtro ghost-run
135
+ recording. The old `BELOTE_*` environment variables still work and override
136
+ the saved value while set.
137
+
138
+ **Daily Challenge** (main menu): one seeded BelAtro run per day, the same for
139
+ everyone (fixed deck, date-derived seed, starter shop pool, no endless).
140
+ The intro shows the day's number and a share code; your best result per day
141
+ is kept locally.
142
+
143
+ **Achievements** pop a toast the moment you unlock one and are listed on the
144
+ Statistics screen.
145
+
130
146
 
131
147
  ## Controls
132
148
 
@@ -176,7 +192,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
176
192
  - **Polished Felt Mat (3.9.4):** The trick mat now has a subtle vignette at its edges, a faint deterministic braille pip-dot texture (fabric-weave feel without intrusive glyphs), and — at standard/spacious terminal sizes — a decorative `╔═══◆═══...═══◆═══╗` outer frame with corner ornaments.
177
193
  - **Selection HUD (3.9.4):** Selecting a card in hand now paints a highlighted bar under it AND a centered `► A♠ — Trump ◄` readout below, color-coded by suit / trump / legality.
178
194
  - **Hand Sorting:** Strategic "play value" organization (honors grouped together) for better tactical awareness.
179
- - **Main Menu:** Simple single-player entry point with configurable AI difficulty, Target Score, and Speed.
195
+ - **Main Menu:** Simple single-player entry point; AI difficulty, Target Score, Speed and Theme live in the Settings screen.
180
196
  - **Undo/Redo:** Press `Z` to undo your last move during bidding or play.
181
197
  - **Statistics:** unified global tracking of games played, win rates, best rounds, and BelAtro expansion milestones.
182
198
  - **Responsive Layout (3 tiers):** Three preset layouts — **compact** (80×32, fits 1366×768), **standard** (96×38), **spacious** (120×48+). The game picks the largest preset that fits your terminal on every render, so resizing mid-game adapts automatically; cards, side columns, and HUD verbosity all scale with the preset. Vertical centering pads tall terminals so the game never clings to the top.
@@ -237,7 +253,7 @@ belote/
237
253
  PYTHONPATH=src pytest
238
254
  ```
239
255
 
240
- Currently **1228 tests** passing with 100% coverage on game-logic modules (4.9.7).
256
+ Currently **1286 tests** passing with 100% coverage on game-logic modules (4.10.0).
241
257
 
242
258
  ## Technical Integrity
243
259
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "belote-cli"
7
- version = "4.9.7"
7
+ version = "4.10.0"
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.9.7"
1
+ __version__ = "4.10.0"
2
2
 
3
3
  __all__ = ["__version__"]
@@ -55,6 +55,17 @@ def is_enabled() -> bool:
55
55
  return _ENABLED
56
56
 
57
57
 
58
+ def set_enabled(on: bool) -> None:
59
+ """Switch screen-reader hints on/off (the Settings screen)."""
60
+ global _ENABLED, _sink
61
+ if on == _ENABLED:
62
+ return
63
+ _ENABLED = on
64
+ if _sink is not None and _sink is not sys.stderr:
65
+ _sink.close()
66
+ _sink = None
67
+
68
+
58
69
  def _refresh_enabled_from_env() -> None:
59
70
  """Re-read BELOTE_A11Y from the live environment. Public for tests."""
60
71
  global _ENABLED, _sink
@@ -4,7 +4,7 @@ import random
4
4
  from collections import Counter
5
5
  from enum import Enum
6
6
 
7
- from .deck import _NONTRUMP_ORDER, Card, Contract, Rank, Suit, trick_rank
7
+ from .deck import _NONTRUMP_ORDER, Card, Contract, Rank, Suit, is_trump_card, trick_rank
8
8
  from .game import (
9
9
  SANS_ATOUT_BID,
10
10
  BidValue,
@@ -70,7 +70,7 @@ class AIPlayer:
70
70
  # unseeded Random() for legacy callers that construct an AIPlayer
71
71
  # directly (e.g. test fixtures).
72
72
  self._rng = rng if rng is not None else random.Random()
73
- # Set per decide_card() call from state.boss_modifiers.seven_eight_trump.
73
+ # Set per decide_card() call from state.variants.seven_eight_trump.
74
74
  # All ranking helpers in this class read it via self._se.
75
75
  self._se = False
76
76
 
@@ -140,7 +140,7 @@ class AIPlayer:
140
140
  # set at round start in current code, but the assumption is
141
141
  # defensive — a future boss or voucher that toggles visibility
142
142
  # mid-round would otherwise see stale cached partner cards.
143
- hide = bool(state.boss_modifiers.hide_partner_hand)
143
+ hide = bool(state.variants.hide_partner_hand)
144
144
  partner_key: tuple[int, int, bool] = (completed_count, current_count, hide)
145
145
  if self.memory.last_partner_hand_key != partner_key:
146
146
  p = partner(self.seat)
@@ -199,9 +199,9 @@ class AIPlayer:
199
199
  or None (pass). TA and SA are only considered in round 2; round 1 is
200
200
  "take the up-card suit at the standard contract" only.
201
201
  """
202
- self._se = state.boss_modifiers.seven_eight_trump
202
+ self._se = state.variants.seven_eight_trump
203
203
  # Boss: La Solitude (Partner forced pass)
204
- if state.boss_modifiers.partner_forced_pass and self.seat == partner(Seat.SOUTH):
204
+ if state.variants.partner_forced_pass and self.seat == partner(Seat.SOUTH):
205
205
  return None
206
206
 
207
207
  hand = state.hand_of(self.seat)
@@ -329,7 +329,7 @@ class AIPlayer:
329
329
  jack-heavy hand under Le Sauvage even though those jacks would score
330
330
  zero in actual play.
331
331
  """
332
- bm = state.boss_modifiers
332
+ bm = state.variants
333
333
  ta_pts = sum(card_points_with_modifiers(c, Suit.TOUT_ATOUT, bm) for c in hand)
334
334
  # The Jack-count bonus is the AI's TA strength signal — drop it if
335
335
  # jacks are suppressed.
@@ -364,17 +364,13 @@ class AIPlayer:
364
364
  # see deck.py::trick_rank and ::card_points). Every ranking / point
365
365
  # read in this method must thread `_se` through or the AI under-
366
366
  # values trump cards that should beat them.
367
- self._se = state.boss_modifiers.seven_eight_trump
367
+ self._se = state.variants.seven_eight_trump
368
368
 
369
369
  # Boss: L'Agent Double (Partner sabotages on 3 random tricks)
370
- if state.boss_modifiers.agent_double_active and self.seat == partner(Seat.SOUTH):
370
+ if state.variants.agent_double_active and self.seat == partner(Seat.SOUTH):
371
371
  current_trick = len(state.completed_tricks) + 1
372
- raw_tricks = state._joker_state.get("agent_double_tricks", frozenset())
373
- sabotage_tricks: frozenset[int] = (
374
- raw_tricks if isinstance(raw_tricks, frozenset) else frozenset()
375
- )
376
372
  trump = state.trump
377
- if current_trick in sabotage_tricks and trump is not None:
373
+ if current_trick in state.variants.sabotage_tricks and trump is not None:
378
374
  return min(legal, key=lambda c: trick_rank(c, trump, self._se))
379
375
 
380
376
  if not hand:
@@ -459,7 +455,7 @@ class AIPlayer:
459
455
  # aces_zero, jacks_zero, tens_zero, ban_clubs). Pre-4.1.1 medium AI used
460
456
  # raw card_points and would "preserve" a K of trump under kings_zero as
461
457
  # if it still scored 20 — picking it as the highest-value card to keep.
462
- bm = state.boss_modifiers
458
+ bm = state.variants
463
459
 
464
460
  # Update void inferences
465
461
  self._update_voids(state)
@@ -588,7 +584,7 @@ class AIPlayer:
588
584
  if c.suit in suit_cards:
589
585
  suit_cards[c.suit].append(c)
590
586
 
591
- bm = state.boss_modifiers
587
+ bm = state.variants
592
588
  # Honors are J/9/A; drop a rank from the count if it's zeroed by a
593
589
  # boss flag (3.9.3 — honor-counting was previously boss-blind).
594
590
  # 4.6.4: hoisted out of the per-suit loop. The closure was being
@@ -716,7 +712,7 @@ class AIPlayer:
716
712
  if lead_suit_for_ranks is not None and c.suit != lead_suit_for_ranks:
717
713
  if is_sa or trump is Suit.TOUT_ATOUT:
718
714
  return -1
719
- if c.suit != trump and not (se and c.rank in (Rank.SEVEN, Rank.EIGHT)):
715
+ if not is_trump_card(c, trump, se):
720
716
  return -1
721
717
  return _NONTRUMP_ORDER[c.rank] if is_sa else trick_rank(c, trump, se)
722
718
 
@@ -852,7 +848,7 @@ class AIPlayer:
852
848
  # (kings_zero / aces_zero / jacks_zero / tens_zero / ban_clubs).
853
849
  # Propagates to _score_discarding_strategy / _score_winning_strategy
854
850
  # via the `points` parameter and to _score_leading_strategy via `bm`.
855
- bm = state.boss_modifiers
851
+ bm = state.variants
856
852
  points = card_points_with_modifiers(card, trump, bm)
857
853
  # Small per-card tiebreaker: when win/loss heuristics are otherwise
858
854
  # equal, slightly bias toward playing the higher-value card. The
@@ -1124,7 +1120,7 @@ class AIPlayer:
1124
1120
  # Le Républicain (or any deck/voucher that sets the flag): 7s and 8s
1125
1121
  # are wild and may be played on any suit, so an off-suit 7/8 doesn't
1126
1122
  # prove void in lead suit.
1127
- wild_active = bool(state._joker_state.get("republicain_wild"))
1123
+ wild_active = state.variants.wild_seven_eight
1128
1124
 
1129
1125
  # 1. Process new completed tricks
1130
1126
  trump = state.trump
@@ -236,6 +236,11 @@ def clear_screen() -> str:
236
236
  return "\x1b[H\x1b[2J"
237
237
 
238
238
 
239
+ def clear_below() -> str:
240
+ """Erase from the cursor to the end of the screen."""
241
+ return "\x1b[J"
242
+
243
+
239
244
  def clear_line() -> str:
240
245
  return "\x1b[2K"
241
246
 
@@ -260,18 +265,6 @@ def alt_screen_off() -> str:
260
265
  return "\x1b[?1049l"
261
266
 
262
267
 
263
- def save_cursor() -> str:
264
- return "\x1b7"
265
-
266
-
267
- def restore_cursor() -> str:
268
- return "\x1b8"
269
-
270
-
271
- def scroll_region(top: int, bottom: int) -> str:
272
- return f"\x1b[{top};{bottom}r"
273
-
274
-
275
268
  # Palette accessors. Read precomputed escape strings from the active palette
276
269
  # (rebuilt by `_refresh_theme_cache()` via theme_manager's change callback).
277
270
  def felt_bg() -> str:
@@ -1,10 +1,10 @@
1
1
  """4.6.2 — RoundLedger.
2
2
 
3
3
  A mutable side-channel that lives for one BelAtro round and replaces the
4
- per-event `dataclasses.replace(GameState, ...)` pattern in
4
+ per-event `dataclasses.replace(BelatroState, ...)` pattern in
5
5
  `ScoreAccumulator.update_state`.
6
6
 
7
- Frozen-GameState contract still holds at round boundaries: `seal_round()` does
7
+ Frozen-BelatroState contract still holds at round boundaries: `seal_round()` does
8
8
  the single `replace()` that produces the final canonical state at round end.
9
9
 
10
10
  Why this design:
@@ -31,7 +31,7 @@ from contextlib import contextmanager
31
31
  from dataclasses import dataclass, field, replace
32
32
  from typing import Any
33
33
 
34
- from belote.game import GameState
34
+ from .state import BelatroState
35
35
 
36
36
 
37
37
  @dataclass(slots=True)
@@ -55,8 +55,8 @@ class RoundLedger:
55
55
  # updates.
56
56
  joker_state: dict[str, Any] = field(default_factory=dict)
57
57
 
58
- def seal_round(self, state: GameState) -> GameState:
59
- """Produce the canonical sealed GameState for the end of the round.
58
+ def seal_round(self, state: BelatroState) -> BelatroState:
59
+ """Produce the canonical sealed BelatroState for the end of the round.
60
60
 
61
61
  Single `replace()` that stamps the ledger's chips/mult/money into the
62
62
  frozen state. `joker_state` is already live on `state` (shared dict)
@@ -95,6 +95,9 @@ class BelAtroRun:
95
95
 
96
96
  # ── Determinism ────────────────────────────────────────
97
97
  seed: int | None = None
98
+ # ISO date when this is a Daily Challenge run (see belatro/daily.py):
99
+ # starter shop pool, no endless offer.
100
+ daily: str | None = None
98
101
  _rng: Any = None
99
102
 
100
103
  # ── Idempotency guard for voucher.apply() ──────────────