belote-cli 4.7.2__tar.gz → 4.8.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.
- {belote_cli-4.7.2 → belote_cli-4.8.0}/CHANGELOG.md +176 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/DEVELOPMENT.md +14 -1
- {belote_cli-4.7.2 → belote_cli-4.8.0}/PKG-INFO +7 -6
- {belote_cli-4.7.2 → belote_cli-4.8.0}/README.md +6 -5
- {belote_cli-4.7.2 → belote_cli-4.8.0}/pyproject.toml +1 -1
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/__init__.py +1 -1
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/ai.py +5 -3
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/core/run_state.py +10 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/core/scoring.py +11 -1
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/jokers/corrupted.py +7 -1
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/main.py +15 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/announce.py +149 -2
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/hud.py +84 -26
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/shop.py +98 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/trust_bar.py +49 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/gameflow.py +32 -5
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/themes.py +23 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/ui/__init__.py +18 -2
- belote_cli-4.8.0/src/belote/ui/anim.py +211 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/ui/announce.py +60 -5
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/ui/render.py +108 -2
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_decks_4_5.py +34 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_hud_toggle.py +49 -0
- belote_cli-4.8.0/tests/belatro/test_joker_callouts.py +31 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_joker_contracts.py +23 -0
- belote_cli-4.8.0/tests/belatro/test_shop_animations.py +64 -0
- belote_cli-4.8.0/tests/test_anim_helpers.py +99 -0
- belote_cli-4.8.0/tests/test_belote_stinger.py +37 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_render_diff.py +31 -0
- belote_cli-4.8.0/tests/test_winner_glow.py +53 -0
- belote_cli-4.7.2/.antigravitycli/69dd05ce-2c1c-4419-8755-e4dd0d4495e8.json +0 -1
- {belote_cli-4.7.2 → belote_cli-4.8.0}/.claude/settings.local.json +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/.gitignore +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/.python-version +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/LICENSE +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/scripts/benchmark.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/a11y.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/achievements.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/ansi.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/core/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/core/economy.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/core/round_ledger.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/engine/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/engine/event_bus.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/engine/modifier_patch.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/engine/round_driver.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ghost_run.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/base.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/jokers/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/jokers/annonces.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/jokers/coinche.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/jokers/contract.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/jokers/economy.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/jokers/hand_comp.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/jokers/trick_timing.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/partner_jokers/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/partner_jokers/passive.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/partner_jokers/risky.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/partner_jokers/shaper.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/planets.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/registry.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/tarots.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/items/vouchers.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/partner/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/partner/partner_state.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/partner/personality.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/partner/trust.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/progression/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/progression/save.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/progression/unlocks.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/run/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/run/ante.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/run/ante_themes.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/run/boss.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/run/decks.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/run/shop.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/run_summary.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/collection.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/consumables.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/history.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/inventory.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/menu.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/belatro/ui/rules.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/config.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/context.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/deck.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/game.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/input.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/main.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/replay.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/rules.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/scoring.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/stats.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/ui/fit_guard.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/ui/layout.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/ui/menu.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/src/belote/ui/prompts.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/__init__.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_belatro.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_boss_contracts.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_boss_modifiers_integration.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_collection_logic.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_consumables_ui.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_contract_unlocks.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_dead_flag_fixes.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_deck_variants.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_endless.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_event_bus.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_ghost_run.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_heist.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_history_overlay.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_hud_synergy.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_inventory_overlay.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_jokers_4_5.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_partner_jokers.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_partner_trust.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_phase0_coverage.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_phase1_plumbing.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_phase2_content.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_phase3_meta.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_progression.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_round_driver.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_run_summary.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_shop_empty_pools.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_slot_machine_tally.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/belatro/test_voucher_idempotency.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/perf_baselines.json +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_a11y.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_achievements.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_ai.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_alt_screen_scroll.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_announce_stats.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_ansi_helpers.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_belote.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_benchmark_smoke.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_bidding_all_pass.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_declaration_tiebreak.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_extended.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_game_logic.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_gameflow.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_hand_auto_sort.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_history_overlay_cache.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_input_eof.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_input_wasd.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_invariants.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_layout.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_new_coverage.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_no_color.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_official_rules.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_perf_regression.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_properties.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_render_felt_polish.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_replay.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/tests/test_undo.py +0 -0
- {belote_cli-4.7.2 → belote_cli-4.8.0}/uv.lock +0 -0
|
@@ -5,6 +5,182 @@ 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.8.0] - 2026-05-21
|
|
9
|
+
|
|
10
|
+
Feature release: a twelfth theme, a coordinated animation polish pass for
|
|
11
|
+
BelAtro, and a tactile feel pass for classic-mode Belote. All additions are
|
|
12
|
+
UI-only — zero rules / scoring / AI changes. Every new helper follows the
|
|
13
|
+
established `try / finally invalidate_diff()` architectural rule (4.6.4 /
|
|
14
|
+
4.0.0). New `BELOTE_NO_ANIM=1` env-var kill switch short-circuits every new
|
|
15
|
+
animation to its end-state for slow terminals or scripted runs.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **Twelfth theme: Sunset Magma** (`src/belote/themes.py::sunset_magma`).
|
|
20
|
+
Deep-magenta felt, coral suits, smoky-purple "blacks", amber-sunset
|
|
21
|
+
highlights, magenta-on-cream banners. Fills the warm orange/magenta niche
|
|
22
|
+
none of the existing 11 themes occupied. Cycles via the `T` key and
|
|
23
|
+
shows up in the main-menu theme selector automatically — no other code
|
|
24
|
+
changes needed because `ThemeManager.set_current` validates dynamically.
|
|
25
|
+
- **Shared animation toolkit** (`src/belote/ui/anim.py`). New module with
|
|
26
|
+
three easing helpers (`ease_out_quad`, `ease_in_out_quad`,
|
|
27
|
+
`ease_out_cubic`) and three painted-frame helpers (`pulse_text`,
|
|
28
|
+
`float_text`, `tick_bar`). Every painted helper ends with
|
|
29
|
+
`invalidate_diff()` in `finally`; every interruptible delay routes
|
|
30
|
+
through `reader.read_timeout(...)` so test stubs (and the existing
|
|
31
|
+
`slot_machine_tally` idiom) work uniformly. Env switch
|
|
32
|
+
`BELOTE_NO_ANIM=1` short-circuits to the end-state, paired with
|
|
33
|
+
`_refresh_animations_enabled_from_env()` for test fixtures.
|
|
34
|
+
- **(BelAtro / B1) Joker trigger callouts in the slot-machine tally**
|
|
35
|
+
(`belatro/ui/announce.py`). Per-trick log entries added by
|
|
36
|
+
`ScoreAccumulator._log` (joker firings, planet/Carnet/Architecte
|
|
37
|
+
attributions) now float above the bucket row as labelled callouts
|
|
38
|
+
(`⚡ Foo: +25 chips` / `✦ Bar: ×2.5 Mult` / `$ Architecte: +$2 …`).
|
|
39
|
+
New module-level `_last_log_count` cursor + `_classify_callout` /
|
|
40
|
+
`_emit_callouts` helpers; reset by `reset_tally_state()`. Capped at 4
|
|
41
|
+
callouts per trick to keep the moment under ~600 ms.
|
|
42
|
+
- **(BelAtro / B2) Shop purchase and reroll feedback**
|
|
43
|
+
(`belatro/ui/shop.py`). New `_animate_purchase(idx, num_items, money_before,
|
|
44
|
+
money_after)` (gold pulse on the bought slot + `tick_bar` money tick-down)
|
|
45
|
+
and `_animate_reroll(num_items)` (pulsed "↻ rerolling..." cue before the
|
|
46
|
+
new inventory paints). Hooked from the existing `run()` loop without
|
|
47
|
+
changing shop state semantics.
|
|
48
|
+
- **(BelAtro / B3) Target-crossing celebration**
|
|
49
|
+
(`belatro/ui/announce.py::_emit_target_celebration`). Fires once per
|
|
50
|
+
round on the first tally where the running total crosses
|
|
51
|
+
`acc.target_score`: gold pulse on the odometer line plus a `★ TARGET ★`
|
|
52
|
+
floater above it. The existing 1.2× flame row is unchanged.
|
|
53
|
+
- **(BelAtro / B4) Trust-bar tick-up animation**
|
|
54
|
+
(`belatro/ui/trust_bar.py::TrustBar.animate_change`). After the
|
|
55
|
+
round-end trust mutations in `belatro/main.py` (`blind_beaten` /
|
|
56
|
+
`blind_failed` / `big_margin_win` / `chute` / `capot_together`), the bar
|
|
57
|
+
animates from the pre-round value to the post-round value via `tick_bar`.
|
|
58
|
+
Snapshot taken right after `trust = self.run.partner.trust`; animation
|
|
59
|
+
fires only when `trust.value != pre_trust_value`.
|
|
60
|
+
- **(Classic / C3) Tactile play-trail on SOUTH plays**
|
|
61
|
+
(`belote/ui/render.py::slide_card_to_table_hint`). A brief vertical
|
|
62
|
+
sparkle trail (`✦/✧/·`) from the south hand toward the south trick
|
|
63
|
+
slot, painted just before the played card lands via `patch_trick_card`.
|
|
64
|
+
AI plays are unchanged. Self-cleaning trail; ~120 ms, skippable.
|
|
65
|
+
- **(Classic / C4) Trick-winner glow + hold**
|
|
66
|
+
(`belote/ui/render.py::pulse_winner_glow`). After all four cards land
|
|
67
|
+
and the MIN_TRICK_DWELL pause completes, a 3-frame gold→white→gold pulse
|
|
68
|
+
on the bottom hint row announces `★ <Direction> wins the trick ★`
|
|
69
|
+
before the trick sweeps. Computed via `trick_winner_seat` (Rupture
|
|
70
|
+
swing takes effect at scoring, so the on-table label stays accurate).
|
|
71
|
+
- **(Classic / C5) Dramatic centered Belote / Rebelote stinger**
|
|
72
|
+
(`belote/ui/announce.py::belote_stinger`). Replaces the modest
|
|
73
|
+
one-line `announce("Belote!")` / `announce("Rebelote!")` call with a
|
|
74
|
+
4-row centered banner framed in `╔═╗║╚═╝` chars, painted in the active
|
|
75
|
+
theme's `banner_bg()` + `gold_fg()`. Falls back to the slim
|
|
76
|
+
`announce()` path on terminals narrower than 32 columns. Routed through
|
|
77
|
+
`gameflow.py`'s existing `current.announced` dispatch.
|
|
78
|
+
|
|
79
|
+
### Internal
|
|
80
|
+
|
|
81
|
+
- `belote/ui/__init__.py` now re-exports `belote_stinger`,
|
|
82
|
+
`pulse_winner_glow`, and `slide_card_to_table_hint` alongside the
|
|
83
|
+
existing UI surface.
|
|
84
|
+
- Test count baseline: **1028** (4.7.3 had 1007; +21 in 4.8.0). New files:
|
|
85
|
+
`tests/test_anim_helpers.py` (8), `tests/test_belote_stinger.py` (2),
|
|
86
|
+
`tests/test_winner_glow.py` (5), `tests/belatro/test_joker_callouts.py`
|
|
87
|
+
(5), `tests/belatro/test_shop_animations.py` (2). All anim tests use
|
|
88
|
+
`sys.modules["belote.ui.render"]` to bypass the `belote.ui` re-export
|
|
89
|
+
shadow (per the 4.0.0 architectural note).
|
|
90
|
+
- **Deferred from this release:** the original plan included a
|
|
91
|
+
"card lift on selection" (C1) and "smooth highlight slide between cards"
|
|
92
|
+
(C2) for the south-hand cursor. Both require deeper changes to the
|
|
93
|
+
multi-row horizontal hand renderer than fit cleanly in the same release
|
|
94
|
+
as the new toolkit; tracked for a follow-up. The remaining tactile
|
|
95
|
+
effects (C3 trail, C4 winner glow, C5 stinger) ship as planned.
|
|
96
|
+
|
|
97
|
+
## [4.7.3] - 2026-05-21
|
|
98
|
+
|
|
99
|
+
Patch release: targeted bug-hunt + performance + code-logic audit. Three
|
|
100
|
+
parallel exploration passes (classic engine, BelAtro layer, UI/render)
|
|
101
|
+
surfaced ~30 candidate findings; verifying each against the live code
|
|
102
|
+
rejected the false positives (deck.py "deluge scores 0", LePasseur "missing
|
|
103
|
+
re_emit guard", show_rules "missing invalidate on scroll", show_history
|
|
104
|
+
"missing term_h in cache key", legal_cards "missing trick_rank hoist") and
|
|
105
|
+
shipped only the verified-true delta. All baselines green: `ruff` 0
|
|
106
|
+
violations, `mypy --strict` 0 errors, `pytest` 1007/1007.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- **(Bug) `announce()` did not invalidate the render-diff baseline.** The
|
|
111
|
+
function paints a transient banner with absolute cursor positioning,
|
|
112
|
+
bypassing `display()`. Without a post-paint `invalidate_diff()` the next
|
|
113
|
+
`display()` diffed against `_last_emitted_lines` (which has no record of
|
|
114
|
+
the banner) and could leave the banner visible as a ghost on the bottom
|
|
115
|
+
row. Same architectural rule as `show_help` / `show_history` /
|
|
116
|
+
`show_rules` / `show_card_detail` / `show_round_summary` /
|
|
117
|
+
`animate_score_update`. `announce()` was the last unfixed site of the
|
|
118
|
+
4.0.0 / 4.6.4 finally-pattern sweep. Pinned by
|
|
119
|
+
`test_announce_invalidates_diff_baseline` in `tests/test_render_diff.py`.
|
|
120
|
+
- **(Bug) L'Infiltré × La Déluge interaction.** The `ghost_lead` deck rule
|
|
121
|
+
paid `+2 Mult / +$1` when NS won a trick by playing trump on a "non-trump
|
|
122
|
+
lead". The is-trump-lead check at `belatro/core/scoring.py:414-417`
|
|
123
|
+
considered only `lead_suit == event.trump` and the TOUT_ATOUT case —
|
|
124
|
+
it did not honour `seven_eight_trump` (La Déluge), so a 7-led or 8-led
|
|
125
|
+
trick was incorrectly treated as a non-trump lead and the bonus could
|
|
126
|
+
fire even though the lead was effectively trump. Pinned by
|
|
127
|
+
`test_ghost_lead_silent_when_lead_is_seven_under_deluge` in
|
|
128
|
+
`tests/belatro/test_decks_4_5.py`.
|
|
129
|
+
- **(Defensive) `LeDemon.on_purchase` is now idempotent.** Re-running the
|
|
130
|
+
hook on an already-owned joker (a future save/load round-trip or replay-
|
|
131
|
+
resume tool) would have compounded the trust subtraction. New
|
|
132
|
+
`_applied_purchase_ids: set[str]` field on `BelAtroRun` (mirrors
|
|
133
|
+
`_applied_voucher_ids` from 3.9.3) short-circuits the second call. Pinned
|
|
134
|
+
by `test_le_demon_on_purchase_is_idempotent` in
|
|
135
|
+
`tests/belatro/test_joker_contracts.py`.
|
|
136
|
+
|
|
137
|
+
### Changed
|
|
138
|
+
|
|
139
|
+
- **AI comment about La Déluge corrected** (`src/belote/ai.py:293-296`).
|
|
140
|
+
The pre-4.7.3 comment claimed "promotes 7s/8s of trump above the Jack" —
|
|
141
|
+
but `deck.py::trick_rank` puts the 7 at rank 8 and the 8 at rank 9
|
|
142
|
+
(the two LOWEST trumps, scoring 0). The boss description in
|
|
143
|
+
`boss.py:95` ("become trump") matches the code, not the comment. The
|
|
144
|
+
comment now states the actual behaviour so future maintainers don't
|
|
145
|
+
chase a phantom bug.
|
|
146
|
+
- **`render_joker_pip_strip` / `render_synergy_tooltip` split into
|
|
147
|
+
builders + writers** (`belatro/ui/hud.py`). Pre-4.7.3 each helper did
|
|
148
|
+
its own `sys.stdout.write + flush` outside `BelAtroHUD._render`'s
|
|
149
|
+
batched parts list, costing 2–3 syscalls per HUD refresh instead of
|
|
150
|
+
one. New `build_joker_pip_strip` / `build_synergy_tooltip` return
|
|
151
|
+
strings; the legacy `render_*` wrappers (used by direct callers and
|
|
152
|
+
tests) still exist for backward compatibility. `BelAtroHUD.render`
|
|
153
|
+
and `_render_compact` now embed both builders into a single batched
|
|
154
|
+
write. Pinned by `test_belatro_hud_render_writes_once` in
|
|
155
|
+
`tests/belatro/test_hud_toggle.py`.
|
|
156
|
+
|
|
157
|
+
### Performance
|
|
158
|
+
|
|
159
|
+
- **`_get_card_face` reads `_cached_theme_name` instead of
|
|
160
|
+
`theme_manager.current_name`** (`belote/ui/render.py:314`). The module
|
|
161
|
+
already caches the theme name (line 84) and refreshes it via the theme
|
|
162
|
+
callback (line 95); `_get_card_face` is called ~52 times per game
|
|
163
|
+
frame, so eliminating the per-call property lookup shaves a few
|
|
164
|
+
microseconds off the render budget. The cache is kept in sync via the
|
|
165
|
+
existing theme-change callback. `clear_card_cache` ensures the card
|
|
166
|
+
face cache is reset on every theme change, so reading the cached name
|
|
167
|
+
is safe.
|
|
168
|
+
|
|
169
|
+
### Internal
|
|
170
|
+
|
|
171
|
+
- New `BelAtroRun._applied_purchase_ids: set[str]` field for joker
|
|
172
|
+
on_purchase idempotency (analogous to `_applied_voucher_ids`). Empty
|
|
173
|
+
by default; populated lazily by corrupted jokers with non-idempotent
|
|
174
|
+
on_purchase actions.
|
|
175
|
+
- `build_joker_pip_strip(...) -> str` / `build_synergy_tooltip(...) -> str`
|
|
176
|
+
public builder API in `belatro/ui/hud.py`; `render_joker_pip_strip` /
|
|
177
|
+
`render_synergy_tooltip` retained as thin wrappers around the builders.
|
|
178
|
+
|
|
179
|
+
### Test count baseline
|
|
180
|
+
|
|
181
|
+
- 1007 (4.7.2 had 1003; +4 in 4.7.3 across `test_render_diff.py`,
|
|
182
|
+
`test_decks_4_5.py`, `test_joker_contracts.py`, `test_hud_toggle.py`).
|
|
183
|
+
|
|
8
184
|
## [4.7.2] - 2026-05-20
|
|
9
185
|
|
|
10
186
|
Patch release: external-model audit verification pass. A prior audit (pasted
|
|
@@ -84,7 +84,7 @@ PYTHONPATH=src mypy --strict src/
|
|
|
84
84
|
# Linting (0 violations expected)
|
|
85
85
|
ruff check src/ tests/
|
|
86
86
|
|
|
87
|
-
# Full test suite (
|
|
87
|
+
# Full test suite (1007 tests expected)
|
|
88
88
|
PYTHONPATH=src pytest
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -143,6 +143,19 @@ once at startup; toggling mid-run has no effect.
|
|
|
143
143
|
`fg()` / `bg()` per the [no-color.org](https://no-color.org/) spec.
|
|
144
144
|
Bold/dim/underline/reverse/strikethrough and cursor sequences remain
|
|
145
145
|
(they aren't color). Added in 3.9.0. Backed by `src/belote/ansi.py`.
|
|
146
|
+
- `BELOTE_NO_ANIM=1` — short-circuit every 4.8.0 animation helper
|
|
147
|
+
(`pulse_text`, `float_text`, `tick_bar`, the joker callouts, the shop
|
|
148
|
+
purchase/reroll feedback, the trust-bar tick-up, the classic-mode
|
|
149
|
+
trail / winner glow) to its end-state with no perceptible delay.
|
|
150
|
+
Useful on slow terminals, in CI, or under scripted runs. Read once at
|
|
151
|
+
import; tests that mutate it must call
|
|
152
|
+
`belote.ui.anim._refresh_animations_enabled_from_env()` after the
|
|
153
|
+
patch. Independent of `BELOTE_NO_DIFF` — each lever is its own
|
|
154
|
+
toggle. Backed by `src/belote/ui/anim.py`.
|
|
155
|
+
- `BELOTE_NO_DIFF=1` — disable the render-diff layer in
|
|
156
|
+
`belote/ui/render.py::display`; every call paints a full frame
|
|
157
|
+
instead of only changed rows. Escape hatch for debugging visual
|
|
158
|
+
artifacts on uncommon terminal emulators.
|
|
146
159
|
|
|
147
160
|
## Releasing a New Version
|
|
148
161
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: belote-cli
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.8.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
|
|
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
|
|
44
44
|
# Belote – 4-Player Terminal Card Game
|
|
45
45
|
|
|
46
|
-
Complete implementation of the French card game Belote for the terminal, with a full-screen themed felt table (
|
|
46
|
+
Complete implementation of the French card game Belote for the terminal, with a full-screen themed felt table (12 palettes), full card graphics at compass positions (N/W/E/S), vignette and braille pip-texture polish, and an optional decorative outer frame.
|
|
47
47
|
|
|
48
48
|
## BelAtro Expansion
|
|
49
49
|
|
|
@@ -207,13 +207,14 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
207
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
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
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.
|
|
210
|
+
- **Animation Polish (4.8.0):** Per-joker callouts float above the slot-machine tally as each joker fires, target-crossing pops a `★ TARGET ★` flag, shop purchases pulse + tick the money down, rerolls fade, and the trust bar ticks up between rounds. Belote / Rebelote now get a dramatic 4-row centered stinger. Classic-mode SOUTH plays paint a tactile sparkle trail; the trick winner glows briefly before the trick clears. All animations skip on any key press and respect `BELOTE_NO_ANIM=1` for slow terminals.
|
|
210
211
|
- **Collection (Almanac):** Persistent tracker to browse every Joker, Planet, and Voucher you've discovered across your runs.
|
|
211
212
|
- **Full Boss Blind Suite:** All 21 unique bosses implemented, including complex mechanics like *L'Anarchie* (dynamic trump) and *La Rupture* (no consecutive wins).
|
|
212
213
|
- **Multiplier Scoring:** Use items to stack Multipliers and reach scores in the millions.
|
|
213
214
|
- **Partner Trust:** Build a relationship with your AI partner to unlock synergies.
|
|
214
215
|
- **Rich Terminal UI:** Full-screen themed felt table with detailed card graphics and "You" vs "Partner" terminology.
|
|
215
216
|
- **Enhanced Hard AI**: Advanced void inference and 2-ply lookahead for critical tricks (Dix de Der).
|
|
216
|
-
- **Customizable Themes:** Switch between
|
|
217
|
+
- **Customizable Themes:** Switch between twelve color palettes (Classic Green, Dark Mode, Blue Velvet, Red Casino, Sepia Vintage, High Contrast, Colorblind, Forest Night, Moonlit Tavern, Royal Purple, Emerald Isle, Sunset Magma) using the `T` key during gameplay.
|
|
217
218
|
- **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.
|
|
218
219
|
- **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.
|
|
219
220
|
- **Hand Sorting:** Strategic "play value" organization (honors grouped together) for better tactical awareness.
|
|
@@ -263,7 +264,7 @@ belote/
|
|
|
263
264
|
│ ├── input.py # Platform-dispatched key reader and interruptible sleep
|
|
264
265
|
│ ├── stats.py # Global and session statistics tracking
|
|
265
266
|
│ └── rules.py # Game rules content
|
|
266
|
-
├── tests/ # Comprehensive test suite (
|
|
267
|
+
├── tests/ # Comprehensive test suite (1007 tests)
|
|
267
268
|
├── scripts/ # Performance benchmarks
|
|
268
269
|
├── pyproject.toml # Build system and dev dependencies (ruff/mypy)
|
|
269
270
|
├── LICENSE # MIT License
|
|
@@ -278,14 +279,14 @@ belote/
|
|
|
278
279
|
PYTHONPATH=src pytest
|
|
279
280
|
```
|
|
280
281
|
|
|
281
|
-
Currently **
|
|
282
|
+
Currently **1028 tests** passing with 100% coverage on game-logic modules (4.8.0).
|
|
282
283
|
|
|
283
284
|
## Technical Integrity
|
|
284
285
|
|
|
285
286
|
The codebase is strictly validated with the following tools:
|
|
286
287
|
- **mypy**: 0 errors (strict type safety)
|
|
287
288
|
- **ruff**: 0 violations (linting & formatting)
|
|
288
|
-
- **pytest**:
|
|
289
|
+
- **pytest**: 1007/1007 passed
|
|
289
290
|
- **Functional Architecture**: Purely immutable state transitions using `dataclasses.replace`
|
|
290
291
|
- **Performance**: High-efficiency rendering and sub-millisecond AI decision times (see `scripts/benchmark.py`)
|
|
291
292
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Belote – 4-Player Terminal Card Game
|
|
2
2
|
|
|
3
|
-
Complete implementation of the French card game Belote for the terminal, with a full-screen themed felt table (
|
|
3
|
+
Complete implementation of the French card game Belote for the terminal, with a full-screen themed felt table (12 palettes), full card graphics at compass positions (N/W/E/S), vignette and braille pip-texture polish, and an optional decorative outer frame.
|
|
4
4
|
|
|
5
5
|
## BelAtro Expansion
|
|
6
6
|
|
|
@@ -164,13 +164,14 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
164
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
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
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.
|
|
167
|
+
- **Animation Polish (4.8.0):** Per-joker callouts float above the slot-machine tally as each joker fires, target-crossing pops a `★ TARGET ★` flag, shop purchases pulse + tick the money down, rerolls fade, and the trust bar ticks up between rounds. Belote / Rebelote now get a dramatic 4-row centered stinger. Classic-mode SOUTH plays paint a tactile sparkle trail; the trick winner glows briefly before the trick clears. All animations skip on any key press and respect `BELOTE_NO_ANIM=1` for slow terminals.
|
|
167
168
|
- **Collection (Almanac):** Persistent tracker to browse every Joker, Planet, and Voucher you've discovered across your runs.
|
|
168
169
|
- **Full Boss Blind Suite:** All 21 unique bosses implemented, including complex mechanics like *L'Anarchie* (dynamic trump) and *La Rupture* (no consecutive wins).
|
|
169
170
|
- **Multiplier Scoring:** Use items to stack Multipliers and reach scores in the millions.
|
|
170
171
|
- **Partner Trust:** Build a relationship with your AI partner to unlock synergies.
|
|
171
172
|
- **Rich Terminal UI:** Full-screen themed felt table with detailed card graphics and "You" vs "Partner" terminology.
|
|
172
173
|
- **Enhanced Hard AI**: Advanced void inference and 2-ply lookahead for critical tricks (Dix de Der).
|
|
173
|
-
- **Customizable Themes:** Switch between
|
|
174
|
+
- **Customizable Themes:** Switch between twelve color palettes (Classic Green, Dark Mode, Blue Velvet, Red Casino, Sepia Vintage, High Contrast, Colorblind, Forest Night, Moonlit Tavern, Royal Purple, Emerald Isle, Sunset Magma) using the `T` key during gameplay.
|
|
174
175
|
- **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.
|
|
175
176
|
- **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.
|
|
176
177
|
- **Hand Sorting:** Strategic "play value" organization (honors grouped together) for better tactical awareness.
|
|
@@ -220,7 +221,7 @@ belote/
|
|
|
220
221
|
│ ├── input.py # Platform-dispatched key reader and interruptible sleep
|
|
221
222
|
│ ├── stats.py # Global and session statistics tracking
|
|
222
223
|
│ └── rules.py # Game rules content
|
|
223
|
-
├── tests/ # Comprehensive test suite (
|
|
224
|
+
├── tests/ # Comprehensive test suite (1007 tests)
|
|
224
225
|
├── scripts/ # Performance benchmarks
|
|
225
226
|
├── pyproject.toml # Build system and dev dependencies (ruff/mypy)
|
|
226
227
|
├── LICENSE # MIT License
|
|
@@ -235,14 +236,14 @@ belote/
|
|
|
235
236
|
PYTHONPATH=src pytest
|
|
236
237
|
```
|
|
237
238
|
|
|
238
|
-
Currently **
|
|
239
|
+
Currently **1028 tests** passing with 100% coverage on game-logic modules (4.8.0).
|
|
239
240
|
|
|
240
241
|
## Technical Integrity
|
|
241
242
|
|
|
242
243
|
The codebase is strictly validated with the following tools:
|
|
243
244
|
- **mypy**: 0 errors (strict type safety)
|
|
244
245
|
- **ruff**: 0 violations (linting & formatting)
|
|
245
|
-
- **pytest**:
|
|
246
|
+
- **pytest**: 1007/1007 passed
|
|
246
247
|
- **Functional Architecture**: Purely immutable state transitions using `dataclasses.replace`
|
|
247
248
|
- **Performance**: High-efficiency rendering and sub-millisecond AI decision times (see `scripts/benchmark.py`)
|
|
248
249
|
|
|
@@ -290,9 +290,11 @@ class AIPlayer:
|
|
|
290
290
|
"""Decide which card to play."""
|
|
291
291
|
hand = state.hand_of(self.seat)
|
|
292
292
|
legal = legal_cards(state, self.seat)
|
|
293
|
-
# La Déluge boss
|
|
294
|
-
#
|
|
295
|
-
#
|
|
293
|
+
# La Déluge boss makes 7s and 8s of any suit rank as trump (the two
|
|
294
|
+
# LOWEST trumps — 7 at rank 8, 8 at rank 9, both scoring 0 points;
|
|
295
|
+
# see deck.py::trick_rank and ::card_points). Every ranking / point
|
|
296
|
+
# read in this method must thread `_se` through or the AI under-
|
|
297
|
+
# values trump cards that should beat them.
|
|
296
298
|
self._se = state.boss_modifiers.seven_eight_trump
|
|
297
299
|
|
|
298
300
|
# Boss: L'Agent Double (Partner sabotages on 3 random tricks)
|
|
@@ -104,6 +104,16 @@ class BelAtroRun:
|
|
|
104
104
|
# future save/load round-trip — gets the same protection automatically.
|
|
105
105
|
_applied_voucher_ids: set[str] = field(default_factory=set)
|
|
106
106
|
|
|
107
|
+
# ── Idempotency guard for Joker.on_purchase() ──────────
|
|
108
|
+
# Corrupted jokers with non-idempotent on_purchase actions (LeDemon's
|
|
109
|
+
# trust subtraction) record their id here on first apply. Re-application
|
|
110
|
+
# paths (a future save/load round-trip, replay tooling) consult this set
|
|
111
|
+
# and short-circuit so a once-paid cost never compounds. Joker on_purchase
|
|
112
|
+
# actions that are already idempotent (boolean flags like
|
|
113
|
+
# LeTraitre.partner_throws_trick, LAgentDouble.agent_double_joker) don't
|
|
114
|
+
# need to consult this set, but adding their id is harmless. 4.7.3.
|
|
115
|
+
_applied_purchase_ids: set[str] = field(default_factory=set)
|
|
116
|
+
|
|
107
117
|
# ── Recent-boss tracker (3.9.3 Phase 5) ────────────────
|
|
108
118
|
# Used by the BelAtro main loop to suppress immediate boss repeats in
|
|
109
119
|
# endless mode. The deque holds at most 2 recent boss ids; the selector
|
|
@@ -407,13 +407,23 @@ class ScoreAccumulator:
|
|
|
407
407
|
# of lead (legal_cards forbids trumping while holding lead).
|
|
408
408
|
# +2 Mult, +$1.
|
|
409
409
|
if joker_state.get("ghost_lead") and event.trump is not None:
|
|
410
|
-
|
|
410
|
+
lead_card = event.cards[0] if event.cards else None
|
|
411
|
+
lead_suit = lead_card.suit if lead_card else None
|
|
411
412
|
# Under TOUT_ATOUT every card is trump, so no play can be
|
|
412
413
|
# "void of the led suit" — is_trump_lead resolves to True
|
|
413
414
|
# and the bonus is correctly gated off.
|
|
415
|
+
# Under La Déluge (seven_eight_trump), a 7 or 8 of any
|
|
416
|
+
# suit also functions as trump — a 7-led trick is a
|
|
417
|
+
# trump-led trick even when lead_suit != event.trump.
|
|
418
|
+
se_lead = (
|
|
419
|
+
state.boss_modifiers.seven_eight_trump
|
|
420
|
+
and lead_card is not None
|
|
421
|
+
and lead_card.rank in (Rank.SEVEN, Rank.EIGHT)
|
|
422
|
+
)
|
|
414
423
|
is_trump_lead = (
|
|
415
424
|
lead_suit == event.trump
|
|
416
425
|
or event.trump == Suit.TOUT_ATOUT
|
|
426
|
+
or se_lead
|
|
417
427
|
)
|
|
418
428
|
if lead_suit is not None and not is_trump_lead:
|
|
419
429
|
seat = event.leader_seat
|
|
@@ -40,7 +40,13 @@ class LeDemon(Joker):
|
|
|
40
40
|
is_corrupted = True
|
|
41
41
|
|
|
42
42
|
def on_purchase(self, run: BelAtroRun) -> None:
|
|
43
|
-
# Degrade trust by 3, making partner play worse
|
|
43
|
+
# Degrade trust by 3, making partner play worse. 4.7.3: idempotency
|
|
44
|
+
# guard — without it, a save/load round-trip or replay-resume tool
|
|
45
|
+
# that re-runs on_purchase on already-owned jokers would compound
|
|
46
|
+
# the cost. Voucher.apply() solved the same problem in 3.9.3.
|
|
47
|
+
if self.id in run._applied_purchase_ids:
|
|
48
|
+
return
|
|
49
|
+
run._applied_purchase_ids.add(self.id)
|
|
44
50
|
run.partner.trust.value = max(0, run.partner.trust.value - 3)
|
|
45
51
|
|
|
46
52
|
def on_trick_won(self, event: TrickWonEvent, state: dict[str, Any]) -> JokerResult | None:
|
|
@@ -576,6 +576,11 @@ class BelAtroGame:
|
|
|
576
576
|
from belote.scoring import score_round
|
|
577
577
|
bd = score_round(final_state)
|
|
578
578
|
trust = self.run.partner.trust
|
|
579
|
+
# 4.8.0 / B4: snapshot for the trust-bar tick animation. The mutations
|
|
580
|
+
# in the lock_trust-gated branches below shift this between 0 and 10;
|
|
581
|
+
# we animate from the pre-block value to the final value after the
|
|
582
|
+
# block completes so the player sees the change land.
|
|
583
|
+
pre_trust_value = trust.value
|
|
579
584
|
|
|
580
585
|
# Phase 2.2: drain pending Tierce charges into the run state.
|
|
581
586
|
pending = final_state._joker_state.get("_pending_tierce_charge", 0)
|
|
@@ -668,6 +673,16 @@ class BelAtroGame:
|
|
|
668
673
|
elif bd.is_capot and bd.taker_team == 0:
|
|
669
674
|
trust.capot_together()
|
|
670
675
|
|
|
676
|
+
# 4.8.0 / B4: animate the trust bar from its pre-round value to its
|
|
677
|
+
# post-round value. No-op when nothing changed (lock_trust path, or
|
|
678
|
+
# symmetric mutations that net to zero). Routed through the bar's
|
|
679
|
+
# tick helper so each intermediate value paints + briefly holds.
|
|
680
|
+
# `trust_bar` is the local TrustBar instance constructed earlier in
|
|
681
|
+
# this method (it's also passed into UICallbacks below); `self`
|
|
682
|
+
# (BelAtroGame) does not have its own trust_bar attribute.
|
|
683
|
+
if trust.value != pre_trust_value:
|
|
684
|
+
trust_bar.animate_change(pre_trust_value, self.reader)
|
|
685
|
+
|
|
671
686
|
# 3.3.0: append a BelAtro-side history entry (the [H] overlay reads
|
|
672
687
|
# `self.run.history` via the override hook installed in `start()`).
|
|
673
688
|
self._record_history_entry(
|