コンテンツにスキップ

Changelog

このプロジェクトの変更点はすべてこのファイルに記録する。

フォーマットは Keep a Changelog に、 バージョニングは Semantic Versioning に準拠する。

[Unreleased]

Added

  • TreeSearchPlayer.estimate_opponent_team() / estimate_opponent_selection() — 相手推定を項目別に分けた新フック。estimate_opponent_team(battle) は相手ポケモンの モデル(技・特性・アイテム)に推定値を書き込み、estimate_opponent_selection(battle) は相手の選出インデックスの推定を list[int] | None の返り値で返す。 estimate_opponent(battle) の既定実装はこの2つに順に委譲するテンプレートメソッドに なった。選出推定の返り値はフレームワーク側(estimate_opponent の既定実装)が 公開済みの selected_indexes とマージする(未包含の推定分のみ追加し、公開済みの インデックスは維持する)ため、利用者がマージ処理を書く必要はない

Changed

  • 破壊的変更: TreeSearchPlayer.estimate_opponent() のシグネチャを (battle, opponent) から (battle) に変更した。相手は battle.opponent(self) で 取得する(evaluate/fallback 等、他のフックが「battle のみを受け取り相手は battle.opponent(self) で取得する」という規約に統一するための変更)。 estimate_opponent を直接オーバーライドしているコードは opponent 引数を削除し、 関数内で battle.opponent(self) を呼ぶよう修正する必要がある
  • 破壊的変更: estimate_opponent の呼び出し契約を変更した。従来は「相手の合法手 (battle.available_commands(opponent))が未公開で空のときのみ」呼ばれていたが、 estimate_opponent 系フック(estimate_opponent/estimate_opponent_team/ estimate_opponent_selection)のいずれかをオーバーライドしていれば、探索の最上位 (choose_command/evaluate_commands)のたびに毎回呼ばれるようになった。この結果、 相手候補は「観測スナップショット由来のコマンド」と「推定情報から CommandManager が列挙したコマンド」の和集合になり、相手の技が一部だけ公開されている局面でも 未公開スロットの推定によって探索の相手候補が拡張される(実対戦の型推定に相当)。 観測(battle)は毎ターン再構築されるため推定は毎回書き込む必要があるが、 公開済みの情報(revealed な技・選出)を上書きしないよう未公開分のみ補うこと

[0.2.0] - 2026-07-22

Added

  • jpoke.players.MinimaxPlayer — 自分の各合法手について、相手が最善(自分に とって最悪)の手を選ぶと仮定したミニマックスで評価する木探索プレイヤー。 TreeSearchPlayer の全フック(evaluate/fallback/estimate_opponent/ configure_sim)をそのまま継承する
  • Battle.swap_items()source: Pokemon | None = None 引数を追加。従来は ItemManager.swap_items() が持つ source(交換の発生源となるポケモン。source 自身が持つねんちゃくのみ無効化する判定に使う)を Battle ファサードから渡す 手段がなく、内部ハンドラの「すりかえ」実装が battle.item_manager.swap_items(...) と内部実装へ直接アクセスせざるを得なかった。既定値 None のため既存呼び出しの 挙動は変わらない
  • Battle.get_team(player) — 対戦中のプレイヤーのチーム実体を取得する公開アクセサ (player_states[player].team への直接アクセスを置き換える)
  • Player.add_pokemon(name, **kwargs)Pokemon を直接importせずにチームへ ポケモンを追加できる正規ルート
  • jpoke.players.RandomPlayer — 合法手からランダムに選ぶ Player 実装。既定の Player.choose_command()(常に先頭のコマンドを選ぶ決定的挙動)では battle_against() による統計比較の分散が潰れてしまう問題への対応
  • Battle.play_out(max_turns=100) — 決着がつくかターン上限に達するまで 自動的に対戦を進める便宜メソッド。start()while not battle.finished and battle.turn < N: battle.step() という定型ループの重複を解消する。 Player.battle_against() の内部ループもこれに置き換えた
  • Battle.set_ailment(target, name, count=None) / Battle.set_weather(name, count=5) / Battle.set_terrain(name, count=5) — 状態異常・天候・地形を技を介さず直接 セットする薄い公開ラッパー(既存の ailment_manager / weather_manager / terrain_manager への委譲)。シナリオ構築や calc_lethal() によるダメージ計算 検証で、対戦を進行させずに状態異常・天候込みの複合致死率を確認できる
  • examples/07_replay.pyBattle.build_replay_data() / ReplayPlayer / replay_battle()(リプレイの記録・再生一式)を紹介するサンプルを新設
  • jpoke.testing モジュール — tests/test_utils.py にあった内部テストヘルパー (start_battle / run_move / run_switch / apply_ailment / get_action_order / calc_lethal / fix_damage / fix_random 等14関数と CustomPlayer)を本体パッケージへ昇格。pip install jpoke だけで(リポジトリを clone せずに)任意ターンでのピンポイントな状態検証・技の実行ができるようになった。 tests/test_utils.py は後方互換のための薄い再エクスポート層として残している。 移植にあたり Battle に不足していた公開ラッパーを追加した: Battle.set_ailment()source / overwrite 引数を追加、 Battle.set_volatile() / Battle.set_item() / Battle.activate_global_field() / Battle.activate_side_field() / Battle.resolve_action_order() / Battle.calc_move_priority() / Battle.can_switch() / Battle.end_turn() を新設 (いずれも ailment_manager 等の内部マネージャーへの薄い委譲で、外部コードが battle.<manager>.<method>() を直接呼ばずに済むようにするための追加)
  • Command をトップレベルパッケージから再エクスポート(from jpoke import Command。 従来は from jpoke.enums import Command のみ)。Battle / Player は既にトップ レベルから使えたが Command だけ欠けていたための追加
  • Battle.has_available_bench(player) / Battle.is_floating(pokemon) / Battle.is_trapped(pokemon) / Battle.is_nervous(pokemon) / Battle.is_hazard_immune(pokemon) / Battle.can_use_last_resort(pokemon) / Battle.get_forced_move_name(pokemon) / Battle.is_first_actor(player) / Battle.is_second_actor(player)battle.queryPokemonQuery)のうち Pokemon/Player 単体の引数で完結する判定メソッドを Battle 直下に薄い 委譲として追加。can_switch() 以外は battle.query.<method>() の直接呼び出しが 必要で docs/api/README.md にも未掲載だったための対応(AttackContext/ EventContext を要求する内部専用メソッドは対象外のまま)
  • docs/api/README.md(Pokemon「シナリオ構築系(フォルム変化)」)に Pokemon.set_form(name, hp_policy="keep_absolute", set_default_ability=False) を追記。 実装済み(ロトム・ザシアン/ザマゼンタ・オリジンフォルム等の切り替えに内部で使用) ながら docs/api/README.mdexamples/ のどちらにも未掲載だったための対応。 合わせて examples/03_damage_calc/10_form_change_comparison.py を新設し、 set_form() によるロトムのフォルム変化がタイプ・種族値を通じてダメージ・致死率に 与える影響を比較するサンプルを追加
  • docs/api/README.md(Battle「状態取得系」)に Battle.calc_move_priority(attacker, move) / Battle.resolve_speed_order() を追記。examples/02_ai/04_priority_and_command_debug.py で使用されているが実装済みメソッド自体は docs/api/README.md に未掲載で、 「テストユーティリティ」節のインデックス指定版 jpoke.testing.calc_move_priority(battle, player_index, move_index=0) のみが記載されていたための対応。両者の違い (Pokemon/Move オブジェクト指定かインデックス指定か)を相互に明記した
  • docs/api/README.md(Battle「シナリオ構築系」)に Battle.gain_item(target, name) / Battle.set_item(target, name, source=None) / Battle.remove_item(target, source=None, track_loss=True) / Battle.take_item(target, ignore_sticky_hold=False) / Battle.swap_items(ignore_sticky_hold=False) / Battle.consume_item(target, track_loss=True) を追記。実装済み(item_manager への薄い委譲)ながら docs/api/README.mdexamples/ のどちらにも未掲載だったための対応。状態異常・ 揮発性状態・天候・地形にはそれぞれ set_ailment/set_volatile/set_weather/ set_terrain 用のサンプルがあるのに、持ち物操作系だけシナリオ構築用の直接操作 手段が丸ごと欠落していたため。合わせて examples/03_damage_calc/12_item_manipulation.py を新設し、6メソッドの成功/失敗条件の違い(gain_itemは持ち物なしが前提、 take_itemは奪う側が持ち物なしが前提、swap_itemsは双方が持ち物を持っていても 実行できる等)を単体で確認するサンプルを追加
  • Battle.copy(copy_logs=False)event_logger/command_log(対戦開始からの 全履歴)をdeepcopyせず、複製先に空の新規ログを持たせるオプション引数を追加。 copy_logs=False を指定すると、複製元のログには影響を与えずにコピー負荷を 抑えられる。既定は True で従来通りの挙動を維持する。TreeSearchPlayer の内部シミュレーション(evaluate() の既定実装はログを一切参照しない)で copy_logs=False を使うように変更し、探索ノードごとに発生していた 全履歴の無駄なdeepcopyを削減した
  • Battle.build_observation(observer, copy_logs=True)copy_logs 引数を追加。 従来は内部実装(observation_builder.build())が deepcopy(battle) を直接呼んで おり、copy()copy_logs=False 最適化の恩恵を受けられなかった。 build_observation()command_manager.py/turn_controller.py から毎ターンの 行動選択フェーズで必ず呼ばれるホットパスのため、ログを一切参照しない方策でも ターンが進むほど肥大化する全履歴を無条件にdeepcopyし続けるコストがあった。 既定は Battle.copy() と同じ True で従来通りログを引き継ぐ挙動を維持する (choose_command()/choose_selection() の実装がログを参照する可能性がある 汎用の呼び出し経路の既定は変更していない)。ログを参照しないと分かっている 用途でのみ明示的に copy_logs=False を指定してコピー負荷を削減できる
  • Player.battle_against()on_battle_end: Callable[[Battle], None] | None = None 引数を追加。従来は n_battles ループ内で各対戦の Battle を勝敗判定にのみ使い 破棄しており、自己対戦データ収集(強化学習用リプレイ等)のために battle_against() を使わず seed + 対戦通番 の派生シード規約を手動で再実装する必要があった。 各対戦の play_out() 完了直後に Battle を受け取れるコールバックを追加し、 この重複を解消した。既定値 None のため既存呼び出しの挙動は変わらない
  • Player.add_pokemon()evs: dict[Stat, int] | None = None / ivs: dict[Stat, int] | None = None 引数を追加。指定した場合、生成した Pokemon に対し内部で set_evs() / set_ivs() を呼ぶ。従来は add_pokemon() の戻り値に対して別途 set_evs() / set_ivs() を呼ぶ必要があった
  • Battle.is_struggle_only(player) — 指定プレイヤーがわるあがきしか選べない状態 かどうかを判定する薄いヘルパー。従来は get_available_commands(player) が 空の技コマンドと交代コマンドを両方含みうるため get_available_commands(player)[0] でわるあがきを代用すると、交代コマンドが同時に存在する場合に誤ってそちらを 返してしまう罠があった。実際にわるあがきを選ぶ際は Command.STRUGGLE を そのまま使えばよい(インデックス解決が不要な固定コマンドのため)
  • Battle.copy(omniscient=True) — 複製先の observerNone にするオプション 引数を追加。choose_command() が受け取る battle は情報隠蔽済みの観測 (observer が自分自身のコピー)であることがあり、木探索の内部シミュレーション ではそのまま copy() した後に sim.observer = None を手動で設定する必要が あった。TreeSearchPlayer をこのオプションを使う形に変更した。既定は False で従来通りの挙動を維持する
  • RandomPlayer.choose_selection() — 選出番号を battle.decision_random で ランダムに選ぶよう追加。従来は Player.choose_selection() の既定実装 (先頭から順に選出)を継承しており、choose_command() はランダムでも選出は 固定されたままだった

Changed

  • 破壊的変更: サポートする最小 Python バージョンを 3.10 から 3.11 に引き上げた (requires-python = ">=3.11")。CI のテストマトリクスも 3.10/3.12 から 3.11/3.12 に変更
  • 破壊的変更: TreeSearchPlayer を、具体的な評価アルゴリズムを持たない抽象度の 高い探索フレームワーク基底クラスに変更した。自分の合法手をどう評価するか (_score_command、新設)は具体的なアルゴリズムを実装するサブクラスに委ねる ようになり、TreeSearchPlayer 単体をインスタンス化して choose_command() を 呼ぶと NotImplementedError になる。従来の「相手が最悪の手を選ぶと仮定する」 ミニマックス評価は新設の jpoke.players.MinimaxPlayer に移設したため、 TreeSearchPlayer を直接使っていたコードは MinimaxPlayer に置き換える必要が ある(evaluate/fallback/estimate_opponent/configure_sim の4フックは 従来通り MinimaxPlayer でも利用できる)
  • 破壊的変更: Battle.__init__players: tuple[Player, ...] ではなく *players: Player の可変長引数を受け取るようになった。 Battle((player1, player2), ...) ではなく Battle(player1, player2, ...) と書く
  • Battle(n_selected=...) を省略した場合、min(3, 各プレイヤーの手持ち数) を 自動設定するようになった(従来は常に3固定で、手持ちが3未満だと n_selected の 明示指定が必須だった)
  • Battle.calc_lethal / core.lethal.calc_lethalmoves 引数が技名の文字列 (MoveName)を受け付けるようになった。Move(name) へのラップなしに moves="ドラゴンテール" のように渡せる(Move インスタンスでの指定は従来通り 可能)
  • Battle.print_logs(turn) / Battle.get_log_lines(turn)turn="all" を 受け付けるようになった。1ターン目から現在ターンまでの全ログを一括で 取得・出力できる(turn=None で現在ターンのみ、という既存の挙動は変更なし)
  • 破壊的変更: examples/ をユースケース別ディレクトリ構造に再編した。 従来のフラットな連番(examples/01_quickstart.pyexamples/09_janken_nash_cfr.py)を廃止し、examples/01_basics/, examples/02_ai/, examples/03_damage_calc/, examples/04_research/ の ディレクトリに分割、各ディレクトリ内で独立して連番を振り直した (例: examples/01_quickstart.pyexamples/01_basics/01_quickstart.py)。 独立した作業が同じ番号を取り合う衝突事故を防ぐための変更
  • 破壊的変更: Battle.consume_item() / ItemManager.consume_item() の第1引数名を mon から target にリネームした(gain_item / remove_item / take_item 等、 他のアイテム系メソッドとの引数名を統一するための変更)。キーワード引数で mon=... と呼び出している箇所は target=... に変更する必要がある (リポジトリ内の既存呼び出しはすべて位置引数のため影響なし)
  • 破壊的変更: Pokemon.modify_hp()Pokemon._modify_hp_raw() にリネームした。 従来のdocstringで「内部用。外部からはbattle.modify_hp()を使用」と警告して いたにもかかわらず、アンダースコアなしのメソッド名のままトップレベル from jpoke import Pokemon から到達可能で誤って直接呼び出しやすい状態 だった。直接呼ぶとHPのクランプのみが行われ、ON_HP_CHANGE系ハンドラの 発火・瀕死判定・ログ記録がスキップされる罠になっていたため、命名で 内部専用であることを明示した。外部コードは常に Battle.modify_hp() を使うこと

  • 破壊的変更: Battle.decision_random(行動選択専用の乱数生成器)の種の派生式を hash((seed, "decision")) & 0xFFFFFFFF から (seed + 0x9E3779B9) & 0xFFFFFFFF に 変更した。str を含む tuplehash()PYTHONHASHSEED によりプロセスごとに ランダム化されるため、同じ seed を指定しても decision_random が生成する乱数列が プロセスを跨ぐと再現しなかった(RandomPlayer.choose_command() / TreeSearchPlayer.fallback() はこの乱数列を消費するため、これらを経由するコマンド 選択の再現実験・CI比較が壊れうる問題があった)。新しい派生式は整数の算術演算のみで PYTHONHASHSEED に依存しないため、プロセスを跨いでも同じ seed なら同じ乱数列が 再現される。加算する定数(ハッシュ撹拌でよく使われる黄金比由来の値)が0でないため mod 232 上で不動点を持たず、あらゆる seedBattle.random(種は seed その もの)と衝突しないことが保証される。Battle.copy(reseed=True) 側も同様に修正した。 同じ seed でも decision_random が生成する具体的な乱数列は旧バージョンから 変わる**(ゲーム進行用の Battle.random の乱数列には影響しない)

  • examples/ 配下のファイル構成を「1ファイル=1事例」の原則で再点検し、詰め込み 過ぎていたファイルを分割・renumberした(01_basics/ 3→5ファイル、02_ai/ 3→4ファイル、03_damage_calc/ 3→9ファイル、04_research/03_janken_nash_cfr.py / 04_janken_nash_fictitious_play.py の番号を入れ替え)。 04_research/03_janken_nash_cfr.pyhp_bucket() はHP満タン (fraction=1.0)を専用の最上位バケットとして切り出す仕様に変更し、バケット 総数が HP_BUCKETS から HP_BUCKETS + 1 になった

  • 破壊的変更: Command.is_switch() をプロパティ Command.is_switch に変更した。 同じ真偽値判定を行う is_regular_move / is_terastal / is_megaevol / is_gigamax / is_zmove がすべて @property である中で is_switch() のみ メソッド呼び出しになっており、if cmd.is_switch: と書くとbool化されず bound methodが常にTruthyになるバグの温床だったため統一した。呼び出し側は cmd.is_switch()cmd.is_switch に変更する必要がある
  • Pokemon.set_ivs() / Pokemon.set_evs() の引数が list[int] に加えて dict[Stat, int] を受け付けるようになった。Pokemon.set_stats() が先に辞書形式に 対応したのに対し、こちらは [HP, 攻撃, 防御, 特攻, 特防, 素早さ] の暗黙の固定順 リストのままで、要素の並び順を覚えていないと意図しないステータスに書き込まれる (例外は出ない)という set_stats() と同型の罠が残っていたための対応。dict 指定 時は指定したステータスのみ更新し、未指定のステータスは既存値を維持する。従来通り list[int](6要素で全体を置き換え)でも呼び出せるため既存呼び出しの挙動は変わらない
  • 破壊的変更: jpoke.testing の「0/1のどちらの陣営・アクティブポケモンか」を表す インデックス引数名を全関数で player_idx に統一した。従来は build_context / run_move / calc_lethalatk_idxapply_ailmentactive_indexcalc_move_priorityplayer_indexrun_switch / can_switchplayer_idx と4通りに分裂しており、覚えた引数名を別の関数にそのまま使うと TypeError になる罠だった。キーワード引数で atk_idx= / active_index= / player_index= を指定している呼び出し箇所は player_idx= に変更する必要がある (位置引数での呼び出しは影響なし)
  • Pokemon.render_info()Pokemon.show() の出力)で、アイテム未所持・ テラスタル未設定時の表示が英語ハードコード("No item" / "No terastal")に なっていたため、それぞれ「アイテムなし」「テラスタルなし」に変更した。 出力文字列のみの変更で構造(区切り文字・並び順)は変わらないため、 この文字列を直接パースしているコードがあれば影響を受ける
  • examples/03_damage_calc/ を「生ダメージロール(01) → 状態操作の単体確認(02) → calc_lethal基本(03) → シナリオ比較(04) → …」という順に組み直し、10→11 ファイルにrenumberした(新設: 02_direct_state_manipulation.py。旧 01_basic_lethal_calculation.py03_basic_lethal_calculation.py、旧 02_ailment_and_scenario_comparison.py04_ailment_and_scenario_comparison.py、 旧03_raw_damage_rolls.py01_raw_damage_rolls.py、以降04100511にそれぞれ1つずつ繰り下げ)

Fixed

  • pyproject.tomlpackage-datajpoke.dataregulation/*.csvsrc/jpoke/data/regulation/item.csv / pokemon.csv)が未登録だったため、 ビルドした wheel から pip install すると pokedex.pyregulation/pokemon.csv を読めず FileNotFoundError になっていた (import jpoke が失敗する状態だった)。登録を追加して解消した
  • src/jpoke/core/lethal.py_pokemon_states() で、LethalPokemonState.boostsdict[Stat, int])に dict(state.attacker_boosts or ()) / dict(state. defender_boosts or ())dict[str, int] 相当)をそのまま代入しており mypy の 型チェックが通っていなかった。既存の ailment フィールドと同様に cast で 明示するよう修正した(実行時の挙動に変更なし)
  • src/jpoke/utils/__init__.py__all__ リストがタブインデントになっており ruff(W191)が警告していたため、スペースインデントに統一した
  • examples/01_getting_started/03_custom_player.ipynb / examples/03_lethal/02_multi_move.ipynb の末尾・行末の余分な空白を除去し、 ruff(W291/W293)の警告を解消した
  • docs/api/README.mdCommand 章「インスタンスプロパティ・メソッド」表に is_type(command_type)examples/02_ai/01_custom_player.py で使用している、 "any" / "move" / "switch" を指定できる汎用の種別判定メソッド)が掲載されて いなかったため追記。is_regular_move との違い("move" は通常技コマンドに加え、 テラスタル・メガシンカ・ダイマックス・Zワザを伴う技コマンドも含む)も明記した
  • README.md の「クイックスタート」節が while battle.judge_winner() is None and battle.turn < 100: / winner = battle.judge_winner() という旧パターンのままで、 examples/01_basics/02_quickstart.py(docstringで「READMEのクイックスタートと 同内容」と明記)や docs/api/README.md が採用している while not battle.finished and battle.turn < 100: / winner = battle.winner という表記と食い違っていたため 修正し、表記を統一した(judge_winner() 自体は引き続き公開APIとして利用可能)
  • Battle(seed=None) のフォールバックが int(time.time())(秒精度)だったため、 短時間に複数の Battle を生成すると同一シードになり、Player.battle_against() などの多数回対戦がすべて同じ展開になってしまう問題を修正。OSの乱数源から 高エントロピーな値を生成するように変更した
  • Player.battle_against(..., seed=...) を指定した場合、対戦ごとに seed + 対戦通番 の派生シードを自動的に使うようにし、n_battles を1回の 呼び出しで指定しても展開が固定化しないようにした
  • Battle.roll_damage() の通常ロール抽選が random.choice()(PRNG内部状態に 依存し random() のみを固定するテストヘルパーでは制御できない)だったため random.random() ベースの抽選に変更。上記のseed高エントロピー化に伴い、 乱数固定が不十分だった一部テストがダメージ比較で偶発的に失敗する状態を解消した
  • Battle.finishedself.winner is not None だけを見ていたため、 judge_winner() を一度も呼ばずに finished だけを参照すると、TODスコアで 実際には決着している対戦でも False を返し続ける不整合を修正。 judge_winner() 経由で判定するように変更した
  • Battle.build_observation()choose_command()/choose_selection() に渡される 観測用コピーを構築する)が乱数生成器 random をdeepcopyで独立複製していたため、 RandomPlayer 等が観測用コピー側の random を消費しても本体の battle.random が 一切進まなかった。技を使わず交代コマンドのみが選ばれ続けるターンでは本体の乱数状態が 毎ターン同一のまま固定され、build_observation() が同じ乱数状態から同じコピーを 作り直すことで同じ選択が繰り返される無限交代ループに陥っていた (examples/02_team_battle.pyseed=1,2,3,5 で再現)。当初は観測用コピーの random を本体と同一のオブジェクト参照に差し替えて解消したが、これには 「行動選択に使う乱数」と「ゲーム進行に使う乱数」を分離していなかったことに起因する 重大な設計上の欠陥があった(後述の追加修正を参照)
  • 上記修正で random を観測用コピーと本体で共有した結果、Player.choose_command(sim) 内で方策が sim.random を直接触ると、本来は技実行後(ダメージロール・命中判定・ 急所判定等)に消費されるはずの乱数列を行動選択の時点で先取り消費できてしまい、 「これから打つ技が急所に当たるか」を打つ前に知った上で行動を選べるチート的先読みが 可能になる欠陥があった。Battle に行動選択専用の乱数生成器 decision_randomseed から決定的に派生させ、Battle.copy(reseed=True) でも派生シードで 再初期化される)を新設し、RandomPlayer.choose_command() / TreeSearchPlayer.fallback() はこちらを使うように変更した。 build_observation()decision_random だけを本体と共有し(無限交代ループ対策を 維持)、ゲーム進行用の random は元通りdeepcopyによる独立コピーに戻すことで 先読みを不可能にした
  • TreeSearchPlayer._worst_case_over_opponent() 内の sim = battle.copy()reseed 引数を省略(既定 False)していたため、同じ my_cmd に対する各 opp_cmd 分岐・各 my_cmd 分岐が複製元の random/decision_random の状態を そのまま共有し、探索木の兄弟ノード間で乱数系列が相関していた (examples/04_research/03_janken_nash_cfr.py の自作ロールアウトは既に reseed=True を使っており対象外)。battle.copy(reseed=True) に変更し、 各分岐が派生シードで独立に再初期化された乱数系列を使うようにした。 configure_sim で命中判定・ダメージ乱数等の確率的要素を固定している場合は 探索結果(選ばれるコマンド)に変化はないが、固定していない場合は評価値の 相関が解消されることで変わりうる
  • Pokemon.set_stats(stats) が辞書のキー(Stat)を無視し、enumerate() による 挿入順を暗黙の固定順(0=HP, 1=攻撃, 2=防御, ...)とみなして書き込んでいたため、 {"atk": 150, "def": 100} のようにキー順が典型順と異なる辞書や6項目に満たない 辞書を渡すと、例外を出さずに意図と異なるステータスへ書き込まれる不具合を修正。 stats.items() からキーで対象インデックスを引くように変更した

[0.1.0] - 2026-07-11

Added

  • 初回 PyPI 公開。pip install jpoke でイベント駆動のポケモンチャンピオンズ シングルバトルシミュレーションを利用できる
  • 特性 310 件・アイテム 247 件・技 733 件・揮発性状態 66 件・状態異常 7 件・ 場の効果(天候・地形・グローバル・サイド)31 件を実装 (詳細は .internal/progress/ 配下を参照)