Skip to content

Releases: XeldarAlz/FFXIV-AutoMahjongSolver

v0.1.2.3

Choose a tag to compare

@github-actions github-actions released this 27 May 11:46

Full Changelog: v0.1.2.2...v0.1.2.3

v0.1.2.2

Choose a tag to compare

@github-actions github-actions released this 27 May 09:43

Full Changelog: v0.1.2.1...v0.1.2.2

v0.1.2.1

Choose a tag to compare

@github-actions github-actions released this 26 May 14:28

A point release: a re-tune of the discard weights on the post-furiten simulator, plus the play-stats tool that drove the verification numbers.

DiscardWeights re-tune

DiscardWeights re-tuned via an evolutionary sweep (8 pop x 20 gen x 500 hands/eval, sigma=0.15) on the post-furiten, kabe/no-chance/tedashi-aware simulator under DomanRuleSet. Mean shift vs the previous defaults: Dora 37 -> 266 (~7x), Yakuhai 19 -> 163 (~8x), IsolatedTerminal 55 -> 840 (~15x), DealInCost 0.020 -> 0.063 (~3x), with small shrinks to UkeireKinds and UkeireWeighted. The policy pursues hand value harder and folds isolated terminals more aggressively.

Verification across three 1000-hand seeds (vs previous defaults): +3, +82, +315 pts/hand, avg +133. The baseline wins more individual hands each run; the tuned policy wins bigger on average, consistent with selective push/fold and value pursuit.

play-stats analyzer

New tool reads <pluginConfigDir>/Mahjong.Plugin.Dalamud/games/*.ndjson, pairs each hand-start with the next-file hand-end (the plugin rolls the writer before emitting the trailing hand-end), reconstructs hanchans by chaining scores_after to the next start's scores, and reports per-hand win/deal-in/draw rate plus per-hanchan placement.

Init-artifact hand-ends are dropped: a real hand-end has deltas that sum to ~0, while the first hand after plugin attach often shows deltas=[25000,25000,25000,25000] from the addon overwriting the plugin's pre-init zero snapshot.

Rank-gating currently falls back to "any chain of at least four real hand-ends" rather than a full E1-E4 dealer cycle, because DealerSeat is not validated yet and every hand-end emits dealer=0/honba=0. Will tighten once DealerSeat lands.

Full Changelog: v0.1.2.0...v0.1.2.1

v0.1.2.0

Choose a tag to compare

@github-actions github-actions released this 25 May 22:34

Track 0 lands: the test-corpus scaffold and ruleset-aware tuner that the policy work needed, plus a major rewrite of how push/fold decides.

Push/fold on expected value

The old policy gated push vs fold on shanten thresholds, which underweighted high-value tenpai and over-folded from useful iishanten. The decision now compares the expected value of pushing (probability of completing the hand times its value, less the chance of dealing in) against folding to a safe tile. The opponent danger model widened to read more signals (reach declarations, dora exposure, late-round shifts) and the value head expanded along with it.

Ruleset-aware EfficiencyPolicy and HeuristicCallPolicy

Both policies now honor the active ruleset for Tsumo and yaku checks. Under Riichi (1-han min) the policies behave as before. Under Doman (no min-han requirement) the Tsumo and call-yaku gates relax so the engine no longer refuses winning hands the ruleset accepts. Yaku coverage and YakuPotential.TargetHan widened so the value head sees more of the legal surface.

Also: declining a kan at state-6 used to silently swallow the turn; the call policy now falls through to a regular discard. Self-play suppresses ron when the discarder is furiten.

AutoPlay stuck-state fixes

Several freezes that surfaced over the v0.1.1.x cycle:

  • Hand-result modal: rewritten to fire as a real ButtonClick event, with a stability window before auto-advance so the modal isn't dismissed mid-animation.
  • Tsumo: routes through opcode 11 (SelectItem) instead of the call-prompt path.
  • Chi accept: collapsed to a single button slot so the input dispatcher never picks the wrong one.
  • Out-of-sync hand: AutoPlay recovers from the state where the snapshot and the addon disagree on hand size.
  • Snapshot cache: cleared when the Emj addon goes away, so a stale cache cannot feed the next session.

Tuner

The tuner threads the active ruleset through every run (defaulting to Doman) so weights trained against the wrong rules cannot silently land in the policy. Strategy diagnostics emit per-run, giving loss-mode analysis the signals it needs.

Track 0 test corpus (#38)

The fixture-corpus scaffold lands with the first replay seeds:

  • state-15 pon-offer fixture (call-prompt path),
  • EmjL state-30 our-turn-discard fixture (post-call hand layout).

Plus an extract-fixture tool that pulls replay fixtures from telemetry memdumps, so growing the corpus from real captures is a one-command operation.

UI / internal

  • Mode card embeds header icons directly; the separate status pill is gone.
  • Spotlight highlight style dropped.
  • StateAggregator caches the policy verdict and discard scores per frame.
  • LayoutProfile threaded through InputDispatcher so input routing reads layout once.
  • BuildSnapshotFromMemory extracted as a pure entry on BaseEmjVariant.

CI

  • update-issue-template-versions.yml auto-refreshes the plugin-version dropdowns when a release publishes.
  • data/weights tuner outputs ignored so local sweeps no longer dirty the tree.

Full Changelog: v0.1.1.1...v0.1.2.0

v0.1.1.1

Choose a tag to compare

@github-actions github-actions released this 25 May 12:34

Two stuck-state fixes that should clear the most common Auto-play freezes, plus a long-requested chi-variant upgrade.

Stuck-state after pon/chi

After a pon or chi, the addon parks the claimed tile at hand-array slot 13 and leaves slots [10..12] zeroed. The previous reader stopped at the first zero, reported a 10-tile hand instead of 11, and the discard gate (which requires hand.Count % 3 == 2) refused to fire — the bot froze at state-30 with no error.

The hand-array scan now walks the full 14 slots (skipping zeros rather than terminating on them) and the post-call discard goes through cleanly. Extracted the decode into a pure HandArrayDecoder helper with unit-test coverage off the addon thread.

Chi-variant freeze

The chi-variant-select sub-popup needs FireCallback opcode 12 with [opcode, variantIndex], not the opcode-11 button-row path the rest of the call prompts use. The previous build dispatched [11, 0] three times into silent no-ops and froze at state-30 whenever you got a multi-variant chi prompt.

Routes the click through a new DispatchChiVariant method that fires opcode 12 directly, bypassing the label-heuristic misroute.

Multi-chi variant selection

While we were down there: multi-chi prompts no longer default to the leftmost button. The plugin now reads the variant tile-triples from the popup's AtkValues, scores each by the post-call closed-hand shanten across all three (claim, hand-pair) splits, and dispatches the lowest. Doman akadora aliases (5m*/5p*/5s*) handled correctly across the migration.

Tied scores resolve to the lower variant index. Falls back to opt=0 if the AtkValue layout doesn't match the captured format or if no variant is formable from the current hand.

Misc

  • About window redesigned — centered icon, version line, shimmer-banded greeting, hover-pulse on links. Drops the old "alpha — expect bugs" paragraph.
  • New Compatibility report issue template for confirming JP / OC / NA client status — single emj-variant-dump.txt attachment is enough to wire a new client up. Aims at growing the client compatibility matrix in the README.
  • Bug-report template trimmed (no more game-version dropdown) and rewritten instruction-first to match how the dev-console actually surfaces the captures.
  • Style sweep on README / CONTRIBUTING / roadmap — em-dashes that were reading as parentheticals get replaced with semicolons or colons.

Tests

608 tests across eight suites (was 593). New HandArrayDecoderTests cover the captured 2026-05-25 stuck-state layout, contiguous hands, akadora aliases, FindAddonSlot tie-break to slot 13, and absent-tile lookups.

Full Changelog: v0.1.1.0...v0.1.1.1

v0.1.1.0

Choose a tag to compare

@github-actions github-actions released this 23 May 22:11

Full Changelog: v0.1.0.12...v0.1.1.0

v0.1.0.9

Choose a tag to compare

@github-actions github-actions released this 23 May 09:12

Full Changelog: v0.1.0.8...v0.1.0.9

v0.1.0.12

Choose a tag to compare

@github-actions github-actions released this 23 May 13:30

Telemetry visibility pass

No user-facing functional changes. Adds the observability needed to catch the class of regression that masked the v0.1.0.10 discard-handshake bug for 13 months — "FireCallback reports Ok but the tile never leaves the hand."

What's new in the corpus

Field / event What it tells us
dispatch_outcome finding Per-dispatch verification — commit=true if (state, hand, melds) changed within ~500 ms, commit=false if the window expires unchanged. Silent no-op dispatches are now corpus-detectable automatically.
stuck_state finding Once per stuck-window when the same (state, hand, legal) repeats for 10+ s with no dispatch commits. Post-MinKan and post-2nd-pon freezes self-report instead of needing local-log triage.
state_code on games-stream state events Raw addon state code (6/15/28/30 etc.). Analyzers can distinguish dispatch contexts the legal enum can't — e.g. state-6 hand=14 self-declare popup vs. state-30 hand=14 classic discard surface.
tracker block on decision events MeldTracker internal state — melds, deferred_ticks, pending_seat, meld_akadora. Distinguishes clean inference from mid-race deferral from missed-and-given-up.
cur_state/cur_hand/cur_melds/cur_legal on dispatch_attempted Dispatcher-time snapshot in the finding payload. Same context as the chat-log annotations, now in the field corpus stream.

Schema bumps

  • StateSnapshot.CurrentSchemaVersion: 3 → 4 (added AddonStateCode)
  • GameLogger.SchemaVersion: 2 → 3 (added state_code, tracker)

Both increases are forward-compatible — older analyzers ignore unknown fields, newer ones see the richer payload.

New tool

tools/cleanup-local-corpus.mjs ages out .local/ pulled corpora older than N days (default 7), dry-run by default with --apply to actually delete. Spares the master b2-manifest.ndjson, secrets.ps1, and *.md notes. Useful after a heavy analysis cycle when the corpus is ~1.6 GB and you want disk back.

Why this matters

The headline fix in v0.1.0.10 was a 13-month-old bug — the dispatcher always reported Ok regardless of whether the addon actually committed the click. The 4-day field corpus showed 0% hand-end rate across 609 hand-starts while every action event also reported Ok. No automated signal caught the divergence between "dispatch returned Ok" and "tile actually left the hand."

The dispatch_outcome finding is specifically designed for that mismatch. The next time the dispatcher silently no-ops a click — different opcode, different addon patch, different variant — the corpus tells us within hours instead of months.

Tests

593 passing (unchanged — new code is additive to existing hot paths).

Full Changelog: v0.1.0.11...v0.1.0.12

v0.1.0.11

Choose a tag to compare

@github-actions github-actions released this 23 May 12:53

Agari opcodes + Riichi popup fixes

The bot can now actually win — Tsumo and Ron at the self-declare popup fire the right opcodes for the first time in field play.

Headline fix: agari opcodes

Tsumo and Ron at the self-declare popup now fire their dedicated agari opcodes (9 and 10) instead of routing through the call-prompt opcode-11 button-row. Pre-fix, the bot detected the win, dispatched, reported Ok, but the popup never closed and the winning hand was silently lost — the cause of the 0% win rate observed across earlier sessions even when the bot reached tenpai. Tsumo opcode-9 is corpus-confirmed across 14 installs; Ron opcode-10 is still speculative pending live capture.

Riichi/Pass popup unblocked

The Riichi/Pass popup at state-6 hand=14 now surfaces Discard as a legal action so the policy can decline-and-discard via the tile-click handshake. Pre-fix, the addon's "Pass" button on this popup doesn't dismiss it — you pass by clicking a tile — but the variant never emitted Discard so the policy returned Pass and the dispatcher fired [11, 1] which no-ops here. Live freeze 2026-05-23T15:03.

Riichi-accept now uses opcode-11 popup click + FSM riichi-confirm latch + next-tick tsumogiri, replacing the dead opcode-8 path that had zero corpus records.

Defensive guard

EfficiencyPolicy.Choose now catches DiscardScorer ArgumentException on shanten-invalid mid-transition states (post-MinKan-pre-rinshan, MeldTracker mid-race) and returns Pass cleanly instead of crash-looping the dispatch every 3 seconds. Live freeze 2026-05-23T15:29..32 — 50+ throws in 3 minutes.

Known limitations

Two stuck states remain — they freeze the bot until manual click, fix needs RE captures:

  • Post-MinKan transient (state-6 hand=10 melds=1): rinshan replacement tile not propagating to the hand-array as expected. Capture: /mjauto capture minkantest.
  • Post-2nd-consecutive-pon (state-6 hand=10 melds=1 via different path): 2nd pon dispatch advances state but no meld registers. Capture: /mjauto capture pon2test.

See docs/dispatch-protocol.md for the verified/broken/unknown status of every popup type.

Tests

593 passing. The fixes are in code paths that the unsafe native-memory dispatcher tests can't easily exercise; coverage comes from the protocol doc + capture-driven validation.

Full Changelog: v0.1.0.10...v0.1.0.11

v0.1.0.10

Choose a tag to compare

@github-actions github-actions released this 23 May 11:51

Auto-play actually commits discards now

The addon's discard handshake requires two FireCallback calls back-to-back, not one — [15, textureId] (selects the tile + dismisses popup), then [7, slotIndex] (commits). The dispatcher used to fire only one of them depending on state, which is why the 4-day field corpus showed a 0% hand-end rate across 609 hand-starts despite 326+ dispatches reporting Ok. Captured live 2026-05-23 via the FireCallback hook during a manual discard; the dispatcher now mirrors the sequence and tiles actually leave the hand.

Secondary fixes

  • MeldTracker chi/pon race — defer state update up to 30 ticks (~500 ms) when the closed-hand shrink lands before the opp discard-count signal propagates. Rescues ~62 % of "hand state out of sync — pausing hints" Pass decisions seen in the corpus.
  • TsumogiriFallback kan accounting — kan tiles now count as 4 in the 14-tile invariant (was 3), so kan-bearing hands no longer trip the fallback.
  • Self-AnKan tracking — successful AnKan dispatch records the meld via MeldTracker.Record so suggestions don't pause for the rest of the hand.
  • Chi/Pass call prompt — sends the correct Pass button index instead of an inflated one (reverted a broken brute-force chi candidate fallback that was overproducing entries).
  • AutoPlayLoop FSM — no longer clears context on transient legal=None animation ticks, preventing duplicate-action firing within the 3-second cooldown.
  • GameLogger hand-end — events now land in the new hand-file so they survive concurrent telemetry uploads; mid-deal wall jumps no longer consume the roll signal.

Diagnostics

  • Per-dispatch chat-log lines annotate schedState/curState/path so dispatcher regressions are one log line away.
  • New docs/dispatch-protocol.md inventories every popup/state and its dispatch shape with verified/broken/unknown status — single source of truth going forward.

Known gaps (alpha)

  • OurSeat and OurRiichi still hardcoded — affects yakuhai weighting for hands 2-4 of a tonpuusen and post-riichi tsumogiri restriction. Blocked on v2-schema memdumps for the addon-offset RE work.
  • Multi-chi variant select, AnKan opcode-12, Tsumo, Ron, and state-28 novice prompts: not yet confirmed in live play.

Tests: 593 passing (was 587). Added regression coverage for the MeldTracker deferral path and the GameLogger hand-end / wall-jump behavior.

Full Changelog: v0.1.0.9...v0.1.0.10