016 やわらかいゴール：5段階の日本語教材

どこから蹴るか、どちらの壁を使うか、どれくらい溜めるか。
「弱い球なら間に合う」原作Cを、入力・衝突・古い観測・逃げる行動に分けて作ります。
原作一式ではありません。最後の段階は原作Cの物理と、明記した操作だけを保持します。

まず実行
PythonとPyxelを用意した環境で、このフォルダーへ移動：
  python -m pip install -r requirements.txt
  python 01_charge.py
01が分かったら02_collision.py → 03_observation.py → 04_flee.py → 05_controller.py。
それぞれ単独のファイルです。元ゲーム、他の段階、画像、フォント、インターネットは実行時に不要。
各ファイルを単独で別のフォルダーへ移しても起動できます（Pyxelのインストールは必要）。
新規OS別インストールやWindows/macOS GUIは未検証。検証環境はPython3.12.14/Pyxel2.9.9/Linux。
文字を読む処理はUTF-8を指定。モデルのimportとtest_stages.pyはPyxelを読み込みません。

遊び方
01：中央から発射。左右で照準、上下で細かな照準。SPACEを新しく押すと溜め開始、離すと発射。
02以降：最初は左右の新押下で上/中/下の場所を選択。SPACEで決定し、一度離してから溜めます。
照準では左右の保持で毎秒60度、上下の保持で毎秒12度。下向きが正、±75度。
力は20〜100。押下後21入力フレーム保持で最大。最大でも勝手に発射しません。
Zで場所選択へ（飛行中は無効）。失敗後SPACEで再照準。Rは同じ面の最初から。
05：SPACEでタイトル開始。Pで停止/再開。Pは溜めを取り消し、全物理/AI時間を止めます。
終了はEsc。音は実装していません。

04/05の実行例
下の場所、+28度、力20：短いSPACEで弱く蹴る。下壁で一度跳ね、移動ゴールへ入ります。
同じ場所/角度の力100は支柱に接触します。上の場所、+32度、力100も成功例です。
02/03の静止ゴールでは同じ弱い壁返しは失敗します。これは段階の意図した違いです。

学び方と証拠
LESSON.mdを読む。各段階の「見える結果」「よくある間違い」「課題」に沿って試す。
lessons.jsonは同じ5項目を機械可読で保存。diffs/は隣り合うファイルの正確な差分。
python -B test_stages.py
python -B tools/check_freeze.py
任意の原作直接比較：
  python -B tools/verify_original.py "原作016_fleeing_goalフォルダーの絶対パス"
通常テストはqa/canonical_reference.jsonの原作由来fixtureで完結し、原作を探しません。

実描画の再現（合成入力であり、人間の試遊ではありません）
python 05_controller.py --headless --demo --frames 150 --screenshot result.png --report result.json
出力パスは現在の作業ディレクトリを基準に解決します。親フォルダーは事前に作ってください。
--replay-jsonはUTF-8 JSONの配列の配列（各フレームで保持するキー名）。未指定フレームは全解放。
--framesは12〜100000。--demoと--replay-jsonは併用不可。QA入出力には--headlessが必要。
Pyxelの更新回数と描画回数は等しいとは限らないため、reportのactual_framesに実測値を保存します。
qa/のPNGは新規実Pyxel描画、qa/render_runs.jsonでファイル・入力・状態と対応付けています。

省略範囲と権利
A/B面、面選択、N、F12、録画、日本語字形、元の起動スクリプトを移植していません。
LICENSEは原作MITの全文をそのまま同梱。各段階の先頭にも全文を埋め込みます。
元LICENSEが参照するassets/NOTO-LICENSE.txtもバイト一致で同梱。Notoの字形データは使用しません。
詳細な比較範囲と未検証事項はQA.txtとlesson_meta.json。原作ファイルは変更していません。
