issue scoreで上達をわかるようにしたい #10
これまでの課題
- 1つsuccessごとに1pts.獲得
- いくら失敗しても1pts.獲得は同じ
- いくら入力が遅くても1pts.獲得は同じ
- 上達したのかがわからない
ブレインストーミング
- 失敗が少ないほうが上達。
- 楽器の練習と同じで、「最初は丁寧にミスなく」「だんだん素早く」
- 失敗の判定は厳密にやると開発コストが大きいので、シンプルなルールにする
右上なら、右上が「success」、右と上は「ノーカン」、それ以外は「失敗」右と上を分解して扱う新定義がtomlに必要な想定
右 + 弱 + 中なら、右と弱と中とそれらの組み合わせは「ノーカン」、それ以外は「失敗にする」
- ノーカンや失敗時は、successするまで同じmissionを続ける。楽器の練習と同じ。これは今までどおりなので実装に影響しない。
- ノーカンは成績に影響しない。失敗は成績に影響する。成績は後述する。
- 入力が素早いほうが上達。
- 「かかった時間」を、新規mission開始から、successまでのミリ秒、と定義する。
- 失敗と関連し、失敗ごとに1000ミリ秒、「かかった時間」を増やす。
- これで、スコアは「かかった時間」で一元管理をして、わかりやすくする。
- 成績の表示
- 成績の表示タイミング。
- 最初の1周が終わるまでは「ラップタイム計測中」と表示する。
- missionを全問successした瞬間を1周の区切りとする。
- 次の1周をする間、さきほどの1周のラップタイムを表示する。
- 成績の表現。
- 「かかった時間」の「全missionを1周」の「平均フレーム数」で表現する。10のmissionがあり、「かかった時間」の合計が1000フレームなら、平均は100フレームで、それが表示される成績「あなたの素早さは平均100フレです」となる。
- 成績の表示タイミング。
- ラップタイム
- ラップタイムは伝わりやすいので、目標になりやすい。
- リセット
- Windowが背面になってから1分経過すると、mission状況とラップタイムはリセットされる。
- 累積score
- これまでのscoreは累積scoreとして残しておく。クッキークリッカーのように、入力だけで増えるスコアがあるのも楽しい。
- 進め方
- シンプル優先。漸進的な開発。まず上記を切り分けて実現してゆく。一歩一歩を可視化しながら進めてゆく。
- 実装方式
- ラップタイムの影響範囲は、mission関連に閉じるとよさげ。例えば、
on_greenで開始時刻を保存、check_mission_successで終了時刻を保存して「かかった時間」を算出して保存(かかった時間list にappendすればよいだろう)、on_all_mission_greenでそれらを総合してラップタイム算出、ができるか検討する。timerは使わずシンプルに実現する。 - 累積scoreは追加実装不要。既存のscoreの実装をそのまま累積scoreとして扱う。
- 影響範囲の狭いもの、ミスっても戻しやすいものから、一歩ずつ漸進的に変更してゆく。
- ラップタイムの影響範囲は、mission関連に閉じるとよさげ。例えば、
LLMに可視化させる
- プロンプト「上記のブレインストーミング欄について、レビューし、listしてください」
- → 結果、要約しただけの同じ内容がでてきた → OK。「LLM出力が下振れしたときに検知できる安全装置」として今後もしばらく試すとよさげ
LLMに可視化させる
- プロンプト「上記のブレインストーミング欄について、一歩ずつ漸進的に実装する場合、どのように進めるか検討し、listしてください」
結果
- 1.がおかしい
- 指示曖昧なことが可視化された
- → ブレインストーミング欄をなおした
- 登場人物(関数名)がないため、妥当性が可視化されていない
- プロンプト不足なことが可視化された
- → プロンプトをなおす
- 壁打ちする
- 壁打ちとみなす。LLMの生成をみて、ブレインストーミングの不足を修正、のサイクルをまわす
- 次のプロンプトを投げる
- プロンプト「上記のブレインストーミング欄について、一歩ずつ漸進的に実装する場合、どのように進めるか検討し、listしてください。listには、既存のコードを参照の上、新規関数名、既存関数名を含めてください」
- 結果、低品質。
- 「既存のコードを参照の上」を指定しないとハルシネーション。
- 既存関数へのlinkはついたりつかなかったり。プロンプトで明示してもつかない。
- 内容が低品質。理解不足で「そこは変更しないやろ」という部分をさわり、ムダに影響範囲を広げることが、毎回あちこちで発生。
- 試しに「ブレインストーミング欄」の「実装方式」欄を追記してみたが、あまり改善されず。
- 仮説、contextあふれで、ハルシネーション。
- どうする?
- 今のLLMの低性能ぶりにあわせる。
- まず狭いcontextにしぼり、「ブレインストーミング欄の実装を実現するための一般的な進め方」のlistを生成させる。
- コードベースとの乖離が大きすぎると根本的にどうしようもないので、それを防止するためコードベースは参照させる。
- だがそれでも乖離はする。そこは低品質と割り切って、一旦そのままとする。
- コードベースとの乖離が大きすぎると根本的にどうしようもないので、それを防止するためコードベースは参照させる。
- プロンプト「上記のブレインストーミング欄について、一歩ずつ漸進的に実装する場合、どのように進めるか検討し、listしてください。listには、既存のコードを参照の上、新規関数名、既存関数名を含めてください。実際にはその実装では間違いである(ハルシネーションを含む)ため、あとでuser側がlistと違う修正を入れることは、織り込み済みとします。listをmd末尾に追記してください。」
- md末尾の「あとでやる」欄が消されてしまうので、退避しておく(追記しろ、消すな、などを指示しても消される)
- 仮説、contextあふれで、LLMの品質が下がっている状態
- contextあふれが目に付く。
- 対策、品質の下振れを織り込み済みとし、ある程度userがフォローする。
- LLMのうまみが減るのは割り切る
- LLMの用途を「認知負荷を下げること」に割り切り、その方向で使う
- 認知負荷が下がればよいので、userが認知負荷の低いとこでちょくちょく手をいれるのはOK
- もし認知負荷の高い「大量の単純チェック作業」等をuserがやる羽目になった場合は、
- 全体の進め方を見直す
- もし認知負荷の高い「大量の単純チェック作業」等をuserがやる羽目になった場合は、
LLM出力 : 実装ステップlist
- 失敗判定の厳格化
check_mission_success(既存)を修正し、「ノーカン」「失敗」「成功」の3値を返すようにする- 必要に応じて新規関数
is_no_count_caseを追加
- 失敗時の成績反映
check_and_update_mission(既存)で「失敗」時に失敗回数をカウントする変数(例:fail_count)を追加- 失敗時はscoreを増やさず、fail_countのみ加算
- missionごとのタイマー計測
on_green(既存)でmission開始時刻を記録する(例: 新規変数mission_start_time)check_mission_success(既存)で成功時に現在時刻との差分を計算し、「かかった時間list」(例:mission_times)にappend
- 失敗ごとに「かかった時間」を加算
- 失敗時、
mission_timesの最新要素に1000msを加算する処理を追加
- 失敗時、
- ラップタイムの算出と表示
on_all_mission_green(既存)でmission_timesの合計・平均を計算し、成績として保存- 新規関数
show_lap_timeでラップタイムをUIに表示
- 成績表示タイミングの制御
main_loop(既存)で「1周目終了時」「次の1周開始前」に成績表示を制御- 新規変数
is_first_lapで状態管理
- リセット処理
main_loopまたはcheck_playing_game_and_do_backmost(既存)で、ウィンドウが背面になってから1分経過したらmission_timesや成績をリセットする処理を追加
- 累積scoreの維持
- 既存のscore変数を「累積score」としてそのまま残す
一歩ずつやる
- プロンプト「1. 失敗判定の厳格化 について、外部設計、内部設計、変更する関数名のlist、引数追加のある関数のlist、新規関数名のlist、を書いてください。tomlの仕様変更もあるはずです。対象tomlの名称と、どう変更するかを書いてください。そこからuserが仕様変更する可能性も織り込み済みとします」
LLM出力 : 1. 失敗判定の厳格化:設計まとめ
外部設計
- missionごとに「成功」「ノーカン」「失敗」を厳密に判定する。
- 例:
- 指定された入力以外は「失敗」
- missionによっては「ノーカン」パターンも許容(例:方向キーの一部のみ押下など)
内部設計
- 判定ロジックを
check_mission_successで拡張し、3値(成功/ノーカン/失敗)を返す。 - ノーカン判定用の新規関数
is_no_count_caseを追加。 - 失敗判定の条件をmission定義(toml)で柔軟に指定できるようにする。
変更する関数名のリスト
- check_mission_success
- check_and_update_mission(失敗時の処理追加)
引数追加のある関数のリスト
- check_mission_success(判定結果の3値化に伴い返り値型や引数の見直しが必要)
新規関数名のリスト
- is_no_count_case
tomlの仕様変更
- 対象toml:missions.toml(またはbutton_challenge.toml等、mission定義ファイル)
- 変更内容例:
- 各missionに「ノーカン」条件や「失敗」条件を明示的に記述できるフィールドを追加
- 例:
[[missions]] name = "右上" success = ["右上"] no_count = ["右", "上"] fail = ["左", "下", "左上", ...] - 既存のmission定義に「no_count」「fail」などの新フィールドを追加
所感
- mission単位は複雑すぎる。そうでなく、
lever_names.tomlに、[[no_count_names]]として書いた。 - プロンプト「
lever_names.tomlに、[[no_count_names]]として書きました。configs.pyでreadする処理を書いてください」 - readする処理を書かせて、いくつか会話して、
no_count_namesがいくつか伝播するところまで書いた - 状況がわかりづらいぞ
- 可視化させられるか試す
- プロンプト「1.失敗判定の厳格化 はどこまで進捗しましたか?可視化してください」
LLM出力 : 進捗可視化 : 1. 失敗判定の厳格化
現状まとめ(2025-06-06時点)
- 設計・仕様
- 3値判定(成功/ノーカン/失敗)導入方針は決定済み。
- mission単位のtoml拡張案は複雑すぎるため、lever_names.tomlの[[no_count_names]]方式に仕様変更。
- 実装
- lever_names.tomlに[[no_count_names]]を記述済み。
- configs.pyでno_count_namesをreadする処理は実装済み。
- no_count_namesが一部ロジックに伝播し始めている。
- check_mission_successの3値化やis_no_count_caseの本格実装は未着手または進行中。
| 項目 | 状態 | 備考 |
|---|---|---|
| 3値判定の設計 | 完了 | 仕様・設計方針は明確 |
| toml仕様の決定 | 完了 | mission単位→lever_names.tomlのno_count_namesに変更 |
| toml記述 | 完了 | lever_names.tomlに[[no_count_names]]記述済み |
| configs.pyでのread実装 | 完了 | no_count_namesの読み込み処理あり |
| no_count_namesの伝播 | 一部完了 | いくつかの箇所で利用開始 |
| 判定ロジックの本実装 | 未着手/途中 | check_mission_successの3値化/is_no_count_case等 |
次のアクション例:
- check_mission_successの3値化実装
- is_no_count_caseの新規実装
- 既存ロジックの「ノーカン」対応への修正
(この進捗可視化はmd末尾に自動追記されたものです)
所感
- 可視化できてると思う。userの認知負荷を下げることができてる。素晴らしい。この手は今後も利用しよう。
is_no_count_case のアルゴリズムを類推させる
- プロンプトは、以下のブロックを投げる
is_no_count_case のアルゴリズム検討
- greenでない場合に、is_no_count_case を実行して判定します
- is_no_count_case 内部の処理
- missionを元に、all_no_count_names を生成します
- さらに、all_no_count_names を加工します
- all_no_count_names をforでまわして
- no_count_names をforでまわして
- 例、no_count_names.success に一致したら
- no_count_names.no_count をappendします
- 例、no_count_names.success に一致したら
- no_count_names をforでまわして
- all_no_count_names をforでまわして
- さらに、all_no_count_names を加工します
- missionを元に、all_no_count_names を生成します
- かなり省略して書きましたが類推してみてください
結果
- キャッチボールを数回繰り返して、以下を生成させた
def is_no_count_case(mission_success, input_name, no_count_names_list): 略(バグってたので)
所感
- 上記関数のレビューは認知負荷が高い
- ためしに判定例をlistさせた
- ハルシネーションとcontextあふれが発生した
- 今のproject方針から考えると、別の手を試そう
- TDDが使えるケースだ
- TDDさせよう
- test~pyを生成させた
- TDDさせた
- is_no_count_case のtest red、LLM提案の案はミスっていたので、userが正しい案を指摘して修正させた
- is_no_count_case のtest green
- よってno_count_namesの伝播は完了したと判断する
- いくつか実装した。よって、以下も完了したと判断する
- check_mission_successの3値化実装
- 既存ロジックの「ノーカン」対応への修正
- プロンプト「1.失敗判定の厳格化 はどこまで進捗しましたか?可視化してください。もし完了であれば改善の提案として重要度の高い上位2つを書いてください、それをみてそこまでやるかを判断します」
LLM出力 : 1. 失敗判定の厳格化:進捗可視化(2025-06-07時点)
| 項目 | 状態 | 備考 |
|---|---|---|
| 3値判定の設計 | 完了 | 仕様・設計方針は明確 |
| toml仕様の決定 | 完了 | mission単位→lever_names.tomlのno_count_namesに変更 |
| toml記述 | 完了 | lever_names.tomlに[[no_count_names]]記述済み |
| configs.pyでのread実装 | 完了 | no_count_namesの読み込み処理あり |
| no_count_namesの伝播 | 完了 | 主要ロジックに伝播済み |
| 判定ロジックの本実装 | 完了 | check_mission_successの3値化/is_no_count_case等 |
| テスト | 完了 | TDDでtest_is_no_count_case.py作成・green |
総評
- 「失敗判定の厳格化」は設計・実装・テストまで完了しています。
- 仕様変更(mission単位→lever_names.toml)も反映済み。
- 既存ロジックの「ノーカン」対応も完了。
改善提案(重要度上位2つ)
- ノーカン・失敗パターンの柔軟性向上
- 現状はlever_names.tomlベースで一括管理だが、missionごとに細かく例外や特殊パターンを指定できる仕組みを検討。
- 例:特定missionのみ独自のno_countやfail条件を持たせる。
- 判定理由の可視化・デバッグ支援
- 判定結果(成功/ノーカン/失敗)だけでなく、「なぜその判定になったか」をログやUIで確認できるようにする。
- テストや運用時の認知負荷をさらに下げる。
所感
- その2つはprojectの方向性からみて不要。
-
- 失敗時の成績反映 に進もう
- プロンプト「2. 失敗時の成績反映 について、外部設計、内部設計、変更する関数名のlist、引数追加のある関数のlist、新規関数名のlist、を書いてください」
- 結果、ハルシネーションだらけで使い物にならなかった。仮説、contextあふれ。対策、プロンプトを見直して、以下とした。
- プロンプト:
-
- 失敗時の成績反映 について、外部設計、内部設計、変更する関数名のlist、引数追加のある関数のlist、新規関数名のlist、を書いてください。
- 観点は以下としてください。そして、結果を10.mdの末尾に追記してください。
-
- 失敗時の成績反映
check_and_update_mission(既存)で「失敗」時に失敗回数をカウントする変数(例:fail_count)を追加- 失敗時はscoreを増やさず、fail_countのみ加算
- 失敗時の成績反映
-
-
LLM出力: 2. 失敗時の成績反映 外部設計・内部設計
外部設計
- ミッション失敗時に成績へ「失敗回数」を反映する。
- 失敗時はscore(成功回数)は増やさず、fail_count(失敗回数)のみ加算する。
内部設計
check_and_update_mission関数にて、失敗時にfail_countを加算する処理を追加。- 成績管理用のデータ構造(例: dictやクラス)に
fail_countを追加。 - 失敗時の判定ロジックを明確化し、score加算処理と分離。
変更する関数名のリスト
check_and_update_mission
引数追加のある関数のリスト
- 必要に応じて、成績管理用データ構造を受け渡す関数(例:
check_and_update_missionなど)
新規関数名のリスト
- 失敗回数の初期化や取得用の関数(例:
reset_fail_count,get_fail_countなど、必要に応じて追加)
上記をagent実装させた
- agent実装はスムーズにできていた記憶
- userミス。以降を、うっかり、一歩ずつでなく、まとめて雑にやってしまった
- 指示が雑 → agent実装が雑 → userがagentに指示してリファクタリングさせた → 成功
- 途中で挙動に不満(mission成功時に、次のmissionが表示されてほしいが、そうなってない。以前そうなっていたかは失念) → その場でagentに修正させた
- それも、指示が雑 → agent実装が雑 → userがagentに指示してリファクタリングさせた → 成功
- 「2. 失敗時の成績反映」は完了した。
- 「3. missionごとのタイマー計測」に進む
- と思ったが先に、今回のfail回数可視化の感触が予想よりよかったので、それを踏まえ、以下を先に実現する:
- 現mission開始からのフレーム数を、fail回数の右に表示。場所などはあとで揉み
- できた、agentに実装させ、レビューして修正させた
- さらにminをやりましょう
- できた、agentに実装させ、レビューして修正させた
- さらに最頻値までやりましょう
- ヒストグラムの最頻ビンの中心について、agentに実装させ、レビューして修正させた
- ヒストグラムの最頻ビンの中心について、過去何回を集計対象にするかtomlで設定できるよう、agentに実装させ、レビューして修正させた
- さて「3. missionごとのタイマー計測」に進むか?
- 先に今のをドッグフーディングしたほうがいいかも
- ドッグフーディング結果
- mission1周(すべて成功)のラップタイムがほしい
- 「3. missionごとのタイマー計測」に進もう
プロンプト案
- missionごとにかかった時間を計測したいです。コードベースを元に、以下の設計を検討し、変更対象関数名と内容をlistしてください
-
- missionごとのタイマー計測
- mission開始時刻を記録する
- stateに新規メンバ
mission_start_timeを追加実装する- main_loop で初期化する
- mission開始時、
mission_start_timeを記録する- main_loopと、on_greenそれぞれから、新規関数
on_mission_startを呼び出し、そこで記録する- かかった時間を記録する
- main_loopと、on_greenそれぞれから、新規関数
- 現在時刻と
mission_start_timeの差分を計算し、かかった時間、とする - かかった時間listに、それをappendする
- かかった時間listとは、stateの
mission_timesである- mission_timesは、main_loopで初期化する
- DEBUG用に、かかった時間listを、on_greenにてprintする
- mission_timesは、main_loopで初期化する
- missionごとのタイマー計測
結果
- agentに設計をlistさせ、レビューして修正させ、実装させ、レビューして修正させた
- 途中、横断的にpyをみるのが漏れることがあり、「コードベースから自分で探してください」など明示的に指示した
- DEBUG printはできた、想定通り、60フレ以内なら0.9などの表示になっている、多少見づらいのはひとまずOKとする
- 課題、今回追加した
mission_timesは、これまでフレーム数で計測していたprev_success_frame_countsと内容が丸かぶりなことに気づいた- 分析
- メリット
- フレーム数より正確な時間計測ができる
- 今回追加した
on_mission_startは全体の構造改善に寄与している
- デメリット
- DRYでない、ムダ、重複、複雑
- 分析
- 削って消すのはいつでもできる、それほど大きな問題はない
- むしろ
mission_times側に統合するのもアリな可能性がある
- メリット
- 対策、もう少し実装を進めてトータルの運用データをとるまで様子見
- 分析
次の、4. の要否を検討する
-
- 失敗ごとに「かかった時間」を加算
- 失敗時、
mission_timesの最新要素に1000msを加算する処理を追加
- 失敗時、
- 失敗ごとに「かかった時間」を加算
- これ省略、後回し、がよいと判断する
- なぜなら、1000msという数字が恣意的である
- そもそもガチャプレイしても、このプレイのルールだと、いい成績は出ないので、
- 1000msのペナルティの効果が相対的に低い、と考える
- ペナルティの効果が相対的に高いのは、例えば2択にしてバクチで片方を入力して100msec(6フレ)などウデマエと違う不適切なタイムを出せるようなルール
- 現在もいわばカウンティングすれば可能だが、30個カウンティングは対象プレイヤー層はできない想定(少なくとも自分はできない)なので、スコープ外とする
次の、5. を検討して整理する
これまで
-
- ラップタイムの算出と表示
on_all_mission_green(既存)でmission_timesの合計・平均を計算し、成績として保存- 新規関数
show_lap_timeでラップタイムをUIに表示これからの案
- ラップタイムの算出と表示
-
- ラップタイムの算出と表示
on_all_mission_green(既存)でmission_timesの合計・平均を計算し、lap_timeとaverageとして保存- DEBUG用に 新規関数
show_lap_timeでラップタイムをprint検討
- ラップタイムの算出と表示
- 2.で仕様追加、実装、検証、の結果、状況が変化している
- それを受けて、
on_all_mission_startを用意して、そこでall_mission_start_timeを記録し、on_all_mission_greenで、lap_timeをprint、がよさげ- 想定は、30問で30秒前後、想定プレイヤー心理は「よし30秒を切った、あと1秒縮めよう」など
- そのほうがシンプルそう
- そのあと運用データとって、lap_timeをlistし、averageやヒストグラムをDEBUG printするのもよさげ
- その方向でagentに実装させて運用データとろう
↑あとでやる