009 ふたりで一つの傘 教材QA
検証日：2026-10-05 UTC
対象：01_pair.py / 02_coverage.py / 03_target.py / 04_hazards.py / 05_home.py
環境：cloud Linux、Python 3.12.14、Pyxel 2.9.9、SDL_AUDIODRIVER=dummy。
仕様はPython 3.11以上。Python 3.11そのもの、Windows/macOSの実GUIは未検証です。
既存の指定仮想環境を使用し、新規パッケージインストールは行っていません。

1. 教材の純粋モデル・合成Controller：37テスト PASS
コマンド：python -B -m unittest -v test_stages.py
直接比較用ORIGINAL_009_DIRを指定して実行。全文はqa/model_tests.txt。
・中心±間隔/2、間隔12〜42、身体半径を含む道端制限
・傘半幅18、身体半径3、ちょうど入る境界、間隔30の境界
・offset0.8/フレーム、左右移動・目標到達・行き過ぎなし
・左雨/右雨の身体の端、0.24/フレーム、30回で7.2、100上限
・水たまりは一人一帯+20、傘で防げない、次の帯で再接触可能、厳密な境界
・橋入口でワープなし、停止中の時間・濡れ不変、合法位置で再開、再開した次のtickから進む
・濡れ100の選択停止、続行、帰宅後の不変状態、濡れていても帰宅
・押し続け移動、Space押し始めだけの切替、反対軸の相殺、P停止・R再開
・付属入力の押し始め整合性、実在コード断片、MIT保持、差分再生成一致
合成ControllerテストはOSのキーボードイベント試験ではありません。

2. 原作Cとの直接比較・付属fixture比較：PASS
参照：pyxel100_001_100/games/009_shared_umbrella/model.py、ROADS[2]。
原作model.py SHA256：e0a9154f85a902644c71306808e034d38d5683339d8e186bacfa00d90ceec954
比較範囲：05の通常進行。snapshotに加えてaim/splashed/exposed/alerted。
・7本の固定入力列、5,037入力、初期状態を含めた5,044状態が原作由来hashと一致
・原作を直接読み、同じ5,037入力の後の状態が一致
・7帯の時間・雨・水たまり幅・道幅が一致、総時間690フレーム=23秒
・coveredの577座標×9傘中心＝5,193判定が一致
・追加で濡れ99.9・全面雨の人工境界状態から、100停止・停止中・続行の3状態を直接比較
・qa/canonical_reference.jsonは原作から読み取り生成した入力と各状態hashのfixture
・qa/canonical_comparison.jsonとqa/source_provenance.jsonに出典・比較数・hashを記録
01〜03は意図的な縮小、04は3帯にした別構成。05でもmirror/baseline/retry/prep/log/進入記録は省略。
有限の試験であり、全可能状態や原作全UIの同等性の証明ではありません。

3. 原作自体の既存試験：17テスト PASS（教材37件と別集計）
原作ディレクトリを読み取り、python -B -m unittest -v test_model.py test_ui.py を実行。
全文：qa/original_tests.txt。原作の全ファイルhashは試験前後で変化なし。
最初に記録したmodel/app/README/LICENSEのhashも変化なし。
原作UI試験はPyxel stubです。原作アプリの自動デモは原作mediaへ書き込むため実行していません。

4. 実Pyxel headless描画：12シナリオ PASS
記録：qa/render_runs.json。各PNGに同名JSON（合成入力と状態）・logがあります。
初期5画面：モデル入力なしで初期状態を12フレーム描画。
01_pair：右+下20フレーム、中心80・間隔32・足64/96。
02_outside：下25フレーム、間隔35・両方BODY OUT。
03_smoothing：Space1押しを6フレーム、寄せ先左・offset-4.8の途中。合計12描画フレーム。
04_splash：91フレーム、橋へ入り両方WET 20、1回ずつ水しぶき。
04_blocked：102フレーム、橋入口で中心13、進行90/300・濡れ0の安全停止。
05_return：220フレーム、乾いた戻す道で間隔32へ寄り直す途中・濡れ0。
05_home：690フレーム、WELCOME HOME・濡れ0/0・CLEAN 7/7。
全12PNGを実際に開いて、文字の切れ・表示の重なり・傘と身体の位置・雨と水の区別・
橋入口の乾いた広い手前・途中の寄り直し・帰宅表示を確認しました。
音声は設計上なし。dummy audio driverでの描画で、音が聞こえたという検証ではありません。

5. 単独配布・標準ライブラリ試験：PASS
5本それぞれの.pyだけを、日本語と空白を含む一時フォルダーへコピー。
実Pyxelで初期12フレーム描画、相対パスisolated.pngとisolated.jsonの保存も確認。
結果：qa/isolation_runs.json。前段階・原作・画像資産を読み込まず起動。
外部原作指定なしの標準Python実行も37件中36 PASS、任意の外部原作比較1件だけSKIP。
全文：qa/standalone_tests.txt。付属原作fixture比較はSKIPせずPASSしています。

6. 今回していないこと
人による面白さ・難易度・読みやすさのユーザーテスト、OSキー入力、ブラウザゲーム操作、
Windows/macOSの実GUI、音声確認、Sites公開、原作変更、原作デモ動画生成。
この教材を「ブラウザで遊べる完成ゲーム」とは呼びません。

7. 配布と凍結
5段階・README・LESSON・固定requirements・LICENSE・lesson/meta・test_stages・4差分・qaを同梱。
生成作業用スクリプトは除き、配布.pyは全てMIT全文を保持。原作Noto字形は含めません。
qa/freeze_manifest.jsonはこの記録と全配布ファイル（manifest自身を除く）のSHA256です。
凍結後のコード・入力・文書変更は、影響する試験とmanifestの更新を必要とします。
