• v0.11.1 1dac191330

    umpire274 released this 2026-04-21 14:44:33 +02:00 | 35 commits to main since this release

    Internal refactor + UX polish. v0.11.1 is a non-breaking release:
    the grammar accepted by the parser, the engine behaviour, and the
    public API via bs_scoring::* are all unchanged compared to
    v0.11.0. Your v0.11.0 games, scripts, and workflows continue to
    work exactly the same way.

    ♻️ Command-taxonomy refactor

    The scoring-command pipeline now has a single source of truth for
    the vocabulary it accepts: the new CommandKind enum in
    engine::commands::kind.

    • Flat 26-variant enumeration, one variant per lexical verb shape.
    • Companion CommandFamily enum groups variants by structural role
      (Control, Status, Pitch, Hit, BatterOut, FielderChoice,
      Steal, Advance).
    • CommandKind::family() and CommandKind::canonical_name() helpers.
    • 4 invariant tests verify that every variant has a family, every
      variant has a canonical name, and the family partition covers the
      whole enum.

    Parallel sub-enums removed

    The tag sub-enums that used to duplicate the taxonomy at each
    pipeline layer are gone. Each layer now references CommandKind
    directly:

    Layer Before v0.11.1 After v0.11.1
    tokens::TokenKind::Verb HitVerb / PitchVerb / StealVerb / Keyword Verb(CommandKind)
    segment::Segment::Control Control(ControlKind) Control(CommandKind)
    segment::Segment::Status Status(StatusKind) Status(CommandKind)
    segment::Segment::Pitch Pitch(PitchKind) Pitch(CommandKind)
    segment::Segment::Hit { kind } HitKind CommandKind
    validator::Resolved::Pitch / Hit PitchKind / HitKind CommandKind

    BatterOutKind is retained — its variants carry fielder identifiers,
    not just a tag — but gains a .command_kind() helper. Note that
    FlyOut { foul: true } maps to CommandKind::FoulFlyOut, which is
    a lexically distinct verb (ff<n>) in the grammar.

    Lexer simplification

    Three regexes (RE_HIT_VERB, RE_PITCH_VERB, RE_STEAL_VERB) have
    been removed. Parameter-less verbs are now recognised by a single
    exact-match match on lowercased text — faster and more readable
    than carrying a regex per one-letter keyword. Regex use is reserved
    to verbs with numeric parameters (o<n>, f<n>, l<n>, if<n>,
    fielding sequences).

    Why this matters

    Adding a new command in a future release (caught stealing, errors,
    sacrifice variants, etc.) now requires touching one enum variant
    and the handful of handlers that dispatch on its family, instead of
    updating four or five parallel sub-enums across tokens.rs,
    segment.rs, validator.rs, and their docs. The module doc on
    kind.rs includes a step-by-step "adding a command" checklist.

    🎨 Scoreboard UX polish

    The TUI scoreboard has been redesigned for clearer at-a-glance
    comprehension:

    • Batting team row highlighted with yellow + bold and a subtle
      left-edge marker — the active half-inning is immediately obvious.
    • Linescore header reworked to render with Line / Span instead
      of a flat string; the current inning is emphasised via reversed
      style.
    • Count styling. Dynamic highlighting per count via the new
      styled_count_span helper:
      • Full count (3-2) → reversed + bold.
      • Critical counts (3-1, 2-2) → yellow + bold.
    • Outs indicator. Two dots ( / ) instead of three via the
      new styled_outs_spans helper. Active outs in yellow + bold,
      inactive in dark gray.
    • Status line re-rendered with mixed styled spans and proper
      centring; redundant inning indicator ("4↑") removed.
    • Internal helper rename: build_linescore_lines
      build_linescore_header_and_range. These helpers are
      module-private fn, not pub — no public API break.

    🧪 Tests

    All existing command-pipeline tests (tokens, segment,
    grammar::mod, validator, parser facade) pass unchanged —
    structural refactor only, same inputs produce the same outputs.

    4 new invariant tests in kind.rs verify the coverage of the new
    taxonomy. Total: 130+ existing + 4 = green on Rust 1.95 stable.

    📦 Status

    Stable release. Fully backward compatible with v0.11.0.

    Full details in CHANGELOG.md,
    STRUCTURE.md, and
    SCORING_GUIDE.md.

    Downloads
  • v0.11.0 dc0f16b962

    umpire274 released this 2026-04-21 10:14:55 +02:00 | 44 commits to main since this release

    First stable release of the v0.11.0 milestone.

    v0.11.0 aggregates the structural refactor from alpha1, the grammar
    refactor from alpha2, and eight issue fixes (#55, #56, #59, #60, #61,
    #62, #64, #66) that landed on the v0.11.0-alpha2-fix_codex branch.
    Public API via bs_scoring::* is unchanged compared to the previous
    v0.10.x releases.

    ♻️ Grammar refactor (from alpha2)

    The scoring-command parser has been rebuilt on top of a formal grammar.
    Parsing is now split into two clearly-separated stages:

    • A stateless syntactic layer that classifies tokens and parses
      comma-separated segments via regex-assisted recognisers.
    • A state-aware validator that cross-checks each segment against
      the current GameState.

    Every error found in a single input line is surfaced at once, with a
    1-based segment index, rather than the parser stopping at the first
    failure.

    The subject rule

    The batting-order subject (19) is mandatory on every action
    segment
    , with one documented exception: verbs whose shape cannot be
    confused with a lone digit may omit the subject, in which case it
    defaults to the current batter.

    Verb family Subject is Implicit default
    Hit verbs (h, 2h, 3h, hr) optional current batter
    Fielding sequence (63, 6-3, 862, …) optional current batter
    Fly (f8), foul-fly (ff3) optional current batter
    Line-out (l6), infield-fly (if4) optional current batter
    Fielder's choice (o6 1b) optional current batter
    Unassisted single-digit (5) mandatory
    Steal (st) mandatory
    Standalone runner-advance (2b) mandatory
    Pitches (b, k, s, f, fl) forbidden
    Control / status (exit, playball, …) forbidden

    Order-independent segments

    The following three inputs all produce the same triple play:

    5 l6, 3 64, 4 43
    3 64, 5 l6, 4 43
    4 43, 5 l6, 3 64
    

    Accumulated error diagnostics

    Every syntactic or semantic problem is reported as:

    error at segment <N>: '<text>': <reason>

    See SCORING_GUIDE.md section 11 for the full diagnostic reference.

    🏗️ Structural refactor (from alpha1)

    • core/ absorbed into engine/: game logic now lives in a single
      coherent subtree.
    • Top-level commands/ removed and moved under engine/commands/,
      eliminating ambiguity with cli/commands/.
    • cli/commands/ renamed to cli/screens/ to reflect its real role.
    • Anti-homonym renames: utils/cli.rsutils/term.rs,
      ui/cli.rsui/cli_impl.rs.
    • Deprecated compatibility shims from v0.8.1 removed.

    🐛 Correctness fixes on top of alpha2

    Issue Fix
    #55 Composite defensive plays — live and replay converge on the same GameState for triple plays, runner outs on FC, and FC-safe advances on runners.
    #56 FC-to-home run credit — batter reaching home directly on a fielder's choice now credits the run.
    #59 Steal mixing — steals combined with end-of-PA actions (hit, out, FC, standalone advance) are rejected.
    #60 Fielding-sequence lexer — invalid fielder 0 in 60 or 6-0 is now rejected at lex time.
    #61 Resume double-scoring — walk and hit movements are no longer re-applied in the replay's composite pass.
    #62 Composite replay pass whitelist — only ground_out, fly_out, line_out, infield_fly, unassisted_out, fielders_choice are replayed via the composite pass.
    #64 Inning buckets on HOME movements — replay path now increments per-inning away_innings / home_innings.
    #66 Cross-half steal-home — credited to the team/inning recorded on the row, not the current traversal position.

    🔁 Migration notes

    External consumers of engine::commands::parser::parse_engine_commands
    must use the new signature introduced in v0.11.0-alpha2:

    let commands = match parse_engine_commands(line, &state) {
        Ok(cmds) => cmds,
        Err(errors) => {
            for err in errors {
                ui.emit(UiEvent::Error(err.to_string()));
            }
            continue;
        }
    };
    for cmd in commands { /* … */ }
    

    Input strings that used to parse under the old pre-v0.11.0 ad-hoc
    rules but do not follow the subject-always grammar are now rejected
    at input time. In particular:

    • st 2b, 2b, 5 b → now errors.
    • 5 alone → now an error; use <n> 5 (e.g. 5 5) for a batter
      unassisted by the third baseman.

    The top-level re-exports from bs_scoring::* (Database, Menu,
    GameState, Player, Team, League, scoring types, …) are
    unchanged.

    🧪 Tests

    83 unit tests shipped with the grammar refactor, plus unit tests
    added for each fix (10 new tests across engine::apply::tests and
    engine::commands::validator::tests).

    📦 Status

    Stable release. Ready for production use.

    Full details in CHANGELOG.md,
    SCORING_GUIDE.md, and STRUCTURE.md.

    Downloads
  • v0.11.0-alpha2 0892abcff0

    umpire274 released this 2026-04-20 16:36:22 +02:00 | 57 commits to main since this release

    Second alpha of the v0.11.0 milestone.

    This alpha rebuilds the scoring-command parser on top of a formal
    grammar. Parsing is now split into two clearly-separated stages — a
    stateless syntactic layer that classifies tokens and parses
    comma-separated segments via regex-assisted recognisers, and a
    state-aware validator that cross-checks each segment against the
    current GameState. Every error found in a single input line is
    surfaced at once, with a 1-based segment index, rather than the parser
    stopping at the first failure.

    ♻️ Grammar refactor

    The subject rule

    The batting-order subject (19) is mandatory on every action
    segment
    , with one documented exception: verbs whose shape cannot be
    confused with a lone digit may omit the subject, in which case it
    defaults to the current batter.

    Verb family Subject is Implicit default
    Hit verbs (h, 2h, 3h, hr) optional current batter
    Fielding sequence (63, 6-3, 862, …) optional current batter
    Fly (f8), foul-fly (ff3) optional current batter
    Line-out (l6), infield-fly (if4) optional current batter
    Fielder's choice (o6 1b) optional current batter
    Unassisted single-digit (5) mandatory
    Steal (st) mandatory
    Standalone runner-advance (2b) mandatory
    Pitches (b, k, s, f, fl) forbidden
    Control / status (exit, playball, …) forbidden

    Order-independent segments

    Composite plays, runner overrides after a hit, and mixed lines can be
    typed in any order relative to one another. For example, the
    following three inputs all produce the same triple play:

    5 l6, 3 64, 4 43
    3 64, 5 l6, 4 43
    4 43, 5 l6, 3 64
    

    Accumulated error diagnostics

    Every syntactic or semantic problem is reported as:
    error at segment <N>: '<text>': <reason>

    When a line contains multiple problems, every one is emitted in a
    single pass. See SCORING_GUIDE.md section 11 for the full diagnostic
    reference.

    🏗️ Implementation

    New modules under src/engine/commands/:

    Module Responsibility
    errors.rs ParseError / ValidationError / CommandError with segment index
    grammar/tokens.rs Lazy-compiled regexes + TokenKind classifier
    grammar/segment.rs Segment enum + parse_segment / parse_line
    validator.rs State-aware classification, line-level checks, coalescing
    parser.rs Facade: parse_engine_commands(&str, &GameState)

    🔁 Migration notes

    External consumers of engine::commands::parser::parse_engine_commands
    must update their call sites:

    // Before (v0.11.0-alpha1 and earlier):
    let commands: Vec<EngineCommand> = parse_engine_commands(line);
    for cmd in commands {
        if let EngineCommand::Unknown(msg) = &cmd { /* handle */ }
        /* … */
    }
    
    // After (v0.11.0-alpha2):
    let commands = match parse_engine_commands(line, &state) {
        Ok(cmds) => cmds,
        Err(errors) => {
            for err in errors {
                ui.emit(UiEvent::Error(err.to_string()));
            }
            continue;
        }
    };
    for cmd in commands { /* … */ }
    

    Input strings that used to parse under the old ad-hoc rules but do not
    follow the subject-always grammar will now be rejected at input time.
    In particular:

    • st 2b, 2b, 5 b → now errors, previously handled in ad-hoc ways.
    • 5 alone → now an error; use <n> 5 (e.g. 5 5) for a batter
      unassisted by the third baseman.

    The top-level re-exports from bs_scoring::* (Database, Menu,
    GameState, Player, Team, League, scoring types, …) are
    unchanged.

    🧪 Tests

    130 new unit tests covering:

    • token classification (12 tests)
    • segment parsing, happy paths + every error variant (38 tests)
    • parse_line facade including error accumulation (5 tests)
    • validator: per-segment checks, line-level checks, coalescing,
      order invariance (20 tests)
    • parser facade integration (6 tests)

    📦 Dependencies

    • Adds regex = "1.11.1" as a runtime dependency.

    📦 Status

    Pre-release. Use v0.10.6 for production.

    Full details in CHANGELOG.md,
    SCORING_GUIDE.md, and STRUCTURE.md.

    Downloads
  • v0.11.0-alpha1 019ecf857c

    umpire274 released this 2026-04-20 10:44:55 +02:00 | 61 commits to main since this release

    First alpha of the v0.11.0 milestone.

    This alpha ships only the structural refactor of src/. Additional
    features planned for v0.11.0 final will be delivered in subsequent
    alphas.

    ♻️ Refactor

    The source tree has been reorganized for readability and maintainability.
    Runtime behaviour is unchanged and the public API re-exported from
    lib.rs (Database, Menu, GameState, Player, Team, League,
    scoring types, …) is preserved.

    What moved

    From To
    core/runner_logic.rs engine/runners.rs
    core/play_ball_apply.rs engine/apply.rs
    core/play_ball_reducer.rs engine/reducer.rs
    core/parser.rs engine/notation.rs
    core/scoring/ engine/scoring/
    core/menu.rs cli/menu.rs
    commands/engine_parser.rs engine/commands/parser.rs
    commands/types.rs engine/commands/types.rs
    cli/commands/* cli/screens/*
    utils/cli.rs utils/term.rs
    ui/cli.rs ui/cli_impl.rs

    What was removed

    • core/play_ball.rs — deprecated compatibility shim since v0.8.1.
    • models/play_ball.rs — deprecated compatibility shim since v0.8.1.

    Why

    • Eliminated the ambiguity between the top-level commands/ module
      (engine-level commands) and cli/commands/ (user-facing screens).
    • Collapsed core/ into engine/ so all game logic lives in a single
      coherent subtree.
    • Removed two module-name homonyms (utils/cli vs ui/cli) that made
      use sites harder to read.
    • Dropped two compatibility shims whose callers had long since migrated
      to the canonical paths.

    🔁 Migration notes

    External consumers that still imported the old paths should update their
    use statements:

    • crate::core::*crate::engine::*
    • crate::commands::engine_parsercrate::engine::commands::parser
    • crate::commands::typescrate::engine::commands::types
    • crate::cli::commands::*crate::cli::screens::*
    • crate::core::menu::*crate::cli::menu::*
    • crate::utils::clicrate::utils::term
    • crate::ui::clicrate::ui::cli_impl

    📦 Status

    Pre-release. Use v0.10.6 for production.

    Full details in CHANGELOG.md and the updated
    STRUCTURE.md.

    Downloads