秋田の飯は、打てた分だけ
流れてくる秋田の郷土料理をローマ字で打つと、その料理を食べたことになる。60秒でコース料金の元が取れたかを会計で判定する。判定はIMEと同じ寛容さで、しはshiでもsiでも、んはnでもnnでも通る。走行中の皿には先頭キー1文字の札が出ていて、その1打目を押すとどの皿から始めるかを選べる——左端の消えかけを拾うか、奥の高い皿へ手を伸ばすかを時間で賭ける。打ち始めた皿は取り上げないので、遅い人でも打った分だけ食べられる。音は音源ファイルを持たずWeb Audioで生成している
所要時間9時間20分
制作メモ
- AIに任せた
- ローマ字判定エンジンの設計。かなを1〜2文字のチャンクに切り、チャンクごとに通る書き方の集合を持たせる方式。1つの正解と文字列比較する実装を却下したのは、しょっつるなべ が shottsurunabe でも syotturunabe でも通らないと実用にならないため/んの扱い。n単独で確定してよいかは次のかなで変わる(とんぶり=tonburiは通すが、こんやく=konyakuは通してはいけない)。次チャンクの先頭が母音・n・yのときだけn単独を候補から外す実装にした/nを打った瞬間に確定させない保留の仕組み。完全一致してもより長い候補が生きているうちは確定を待ち、次のキーがどの候補にも当たらなかったときに初めて確定して打ち直す。これが無いとnnと打つ癖の人が全員弾かれる/っを次のチャンクと合体させる実装。がっこ の っこ は kko で1チャンク(かな2文字ぶん)として生成する/値段を打鍵数で決める判断。かなの文字数だと はたはた(4かな8打)が じゅんさい(5かな6打)より安くなり、打つ手間と釣り合わなくなる/ユニットテスト36本(別表記の受理、語尾のん、っの重ね、づとずの区別、押してほしかったキーの通知、皿の投入と取りこぼし、会計、つまずき集計)/ベルトの設計。走行時間を固定値にしていたら、速い人がレーンを空にして1〜2秒なにも打てない時間が生まれた。走行時間=投入間隔×4枚に組み直し、コースが速くなってもベルトの詰まり方は変わらず速さだけが変わる形にした。4枚なのは幅375pxの画面でも皿が重ならない上限だから/?duration= で回を短くしたときにコース料金も同じ比率で按分する判断。60秒ぶんの料金のままだと15秒の回は必ず足が出る=結果画面が嘘になる/体験評価ハーネスを2周まわしたこと(別セッションのOpusがコードを見ずに実際に遊んで10項目で採点)。1周目12/20点で不合格、改善を入れて2周目18/20点で合格/1周目の指摘で自分の難易度計算が間違っていたと分かったこと。式では0.8打/秒で元が取れるはずが実測1.9打/秒必要だった。式に「的が常に左端=残り時間が最短の皿に固定される」という構造が入っておらず、遅い人ほど打ちかけの皿を取り上げられ続けていた/根本対策として「1打でも入れた皿は取り上げない(左端で止まって待つ)」を入れたこと。速度と収穫を切り離す設計で、これによりお手軽は1打/秒でも元が取れるようになった/難易度を式ではなく実際にゲームを1分間回して測るテストに置き換えたこと。ミス率10〜30%の初心者も再現する。式ベースのテストは実測と2倍ずれたので削除した/料理20品ぶんの絵と開始画面のキービジュアルを生成したこと(別のCLIに委譲)。20枚で様式を揃えるため、真俯瞰・器1つ・無地の淡いベージュ背景・やわらかい自然光・文字なし、という共通の作法を1つ書いて4本に分けて並列で回した。合計82KBに落としてある/【v2】レビュー→裏取り→計画→実装の3役ループを3周まわしたこと。1周目は表層UI・設定/入力/a11y・音・ゲーム設計の4観点で並列にレビューし、57件の指摘のうちコードで裏を取って実在した54件だけを計画に流した/【v2】走行中の皿に先頭キー1文字の札を出し、語の1打目でどの皿から打ち始めるか選べるようにした設計。それまで的は常に自動で左端に固定され、得点が打鍵速度のほぼ線形関数だった(おすすめ・seed7で 0.8打/秒→¥930 / 2.5→¥3,460 / 5.5→¥6,430)/【v2】乗り換えを語の1打目だけに限る判断。語中でも許すと打ち間違いと選択が区別できず、隣接キー誤打を10%混ぜた実プレイで誤打19回のうち3回(16%)が乗り換えとして発火し、打ちかけの語が無言で全部消えた/【v2】音をWeb Audio APIで生成する設計(音源ファイルを持てないため)。鳴らすのは5つで打鍵音は鳴らさない——見えているものに音を重ねても情報が増えないので、視線が打鍵行に釘付けの間に見えていないもの(金額・残り時間・逃した皿)だけを耳へ運ぶ。BGMはループ素材を持たずseedから拍ごとに組み立て、都節音階は既存の和風BGMに寄るため使わない/【v2】打鍵行をレーン直下へ上げたこと。「レーンのすぐ下」のはずが実測283px離れており、1皿に1度しか見ない絵・豆知識・よみが、1文字ごとに見る行を画面外へ押し出していた。よみは20品中13品が名前と完全に同一だったので行ごと捨て、漢字を含む5品だけrubyにした/【v2】明るいレーンの上では明るい色が合図にならないという指摘への対応(琥珀1.6:1・赤2:1)。合図を濃い茶の縁を軸に作り直した(皿の縁で約16:1)/【v2】始める前の「ためし打ち」。既定のおすすめは2.5打/秒が要り、遅い人はそこで必ず負けていた。結果画面の助言は負けた60秒のあとにしか出せないので、始める前に測れる場所を作った
- 自分でやった
- 「寿司打の秋田バージョン」という題材の指定/提示した4つの舞台(郷土料理の食べ放題/パラソルアイス/難読地名/ハタハタ漁)からの選択
- 失敗と修正
- ゲームの進行テストが5件まとめて落ちた。原因は開始時刻に0を渡していたこと。startedAt が 0 だと if (!startedAt) が真になり、始まっているのに「まだ始まっていない」と判定される。テスト側の値の問題ではなく、実装が0を扱えていない → running という真偽値を別に持たせ、時刻の値で開始判定をしないようにした。ブラウザでも rAF の時刻がページ読み込み直後は0近辺なので、同じ踏み方をしうる形だった
「っは次の子音を重ねる」テストが1件落ちたが、実装ではなくテストの打鍵文字列が間違っていた。しょっつるなべ の っつ を ttu で打つなら「す」は入らないのに、shottusurunabe(tsuとtuを混ぜた形)を期待値にしていた → shotturunabe に直した。ローマ字の別表記を並べるテストは、期待値そのものを間違えやすい
皿の投入テストが、料理名を決め打ちして 'gibasa' と打っていた。皿は山を切って配るので1枚目が何になるかは決まっておらず、別の料理が来ると全打鍵がミスになり、それでも「皿が減っていない」ことは満たすので通ってしまう。落ちて初めて決め打ちに気付いた → いま出ている皿のよみから primaryRomaji でローマ字を組み立てて打つヘルパーに置き換えた。通っていた別のテストも同じ書き方だったので直した
一覧用スクショの1枚目が、レーンに皿が1枚も無い瞬間だった。振り付けが2皿食べたあと250ミリ秒しか待っておらず、次の投入(2秒間隔)より早く撮っていた → 皿が4枚並ぶまで待つヘルパーを足した。ただしこれは撮り方の問題に見えて、実際には「速い人はレーンを空にする」という設計の穴が写っていた
デモ動画でもレーンが空のまま進む時間が目立った。原因は録画スクリプトの打鍵間隔を85ミリ秒=毎秒12打にしていたこと。人が出せない速さで、供給が追いつかないのは当たり前だった → 195ミリ秒(毎秒5打前後)に落とした。あわせてベルトの設計自体も走行時間=投入間隔×4枚に組み直した。速すぎるボットが、本物の設計の穴を先に見つけてくれた形
?duration=15 で短い回を録画したら、コース料金が60秒ぶんの¥3,000のまま残り、どれだけ打っても足が出る結果になった。結果画面が実力ではなく設定の都合で決まってしまう → コース料金を回の長さで按分するようにした(15秒なら¥750)。50円単位に丸めて読みやすくしている
難易度を机上の式で設計したら、実測と2倍以上ずれた。式では「1皿あたり60秒÷必要皿数の時間が使える」と仮定していたが、実際の的は常に左端=残り時間が最も短い皿に固定される。遅い人が打ち始めると数秒で皿が消えて進捗が没収され、次の的もまた消えかけの皿になる。1打/秒でお手軽を遊ぶと18皿中1皿しか食べられず¥900の損だった(体験評価1周目・4シードで再現) → 1打でも入れた皿は取り上げない形にした。左端に着いたらそこで止まり、打ち終わるまで的から外れない。1打も入れていない皿は今までどおり流れて逃すので、時間の緊張は残る。あわせて難易度のテストを、式ではなく実際にゲームを1分間回して測る形に作り直した
「打ち切った瞬間に金額が増えるのが気持ちいい」がこのアプリの狙いなのに、その瞬間の演出を1つも作っていなかった。体験評価1周目で、完成直後175ms分の連続スクショ10枚が1バイトも違わないと指摘された → 皿が上に弾けて消える/金額の右に「+¥350」が浮く/金額そのものが跳ねる、の3つを足した。2周目で「視線は中央の料理名にあるのに演出は左上の金額だけ」と再指摘されたので、中央の表示も緑にフラッシュするようにした
初心者の想定をミス0で置いていた。体験評価2周目で「1打/秒でも正確率79.7%だと¥80の負け」と実測された。実際の初心者は必ず打ち間違える → シミュレーションにミス率を入れて測り直し、お手軽の目標を¥1,000→¥700にした。ミス率30%(正確率70%)でも元が取れる水準。テストもミス率10/20/30%で回している
打ちかけの皿を左端に留めたら、後ろの皿が真下に潜り込んで文字が重なり「ぎじゅばんさい」のように読めなくなった。半透明にしても文字同士は重なるので解決しない → つかんでいる皿はレーンから消し、レーン左端の帯と、下の大きな表示に付けた「つかみ中 — 打ち終わるまで待ちます」で伝える形にした
ローマ字の案内が打鍵の途中で書き換わった。`kiritanpo` の n を打った瞬間に `kiritannpo` へ変わり、押す必要のない2つ目の n を指していた。残りを毎回その場で組み立て直していたのが原因 → 語の頭で決めた綴りを持ち回る方式にした。別表記(si / jyu など)に分岐したときだけ組み直す。案内が最後まで動かないことをテストで固定した
「つかみ中」バッジが、まだ1文字も打っていないのに出たままになった。CSSで `.held-badge { display: inline-block }` と書いたため、ブラウザ既定の `[hidden] { display: none }` を上書きしていた。作者スタイルはUA既定に勝つ → `.held-badge[hidden] { display: none }` を足した。あわせて hidden 属性を使う全要素に display 指定が無いかを機械的に確認した(他には無し)
スマホで1文字も打てなかった。keydown だけを見ており、Androidのソフトキーボードが返す keyCode 229 を「IMEで変換中」とみなして捨てていた。実際には変換していなくても229が来る → beforeinput からも1文字ずつ拾う経路を足し、keyCode 229 の判定をやめた(変換中の判定は isComposing と compositionstart に任せる)。物理キーは preventDefault しているので二重に数えない。両方をE2Eで固定した
生成した料理の絵をそのまま円く切り抜いたら、画像の背景(淡いベージュ)が円の外周に輪として残り、中央の大きな表示が「皿の上に皿が乗っている」ように見えた → 中央を切り詰めて器が円いっぱいになるようにした。1254pxの元画像を消したあとに気付いたので、224pxのWebPから172pxへ切り直している。次に作るときは、縮小する前に切り詰める
【v2】難易度テストの擬似誤打が z/q 固定で、20品の先頭キー {a,b,d,g,h,i,j,k,m,s,t} に一度も当たらなかった。そのため「皿の乗り換え」の経路がテストの中で構造的に一度も発火せず、実プレイで起きていた事故(打ちかけの語が無言で消える)を1件も捕まえないまま、難易度の梯子が1行も変わらずに通っていた → 擬似誤打を「押すはずのキーの隣」に替えて測り直した。お手軽・1打/秒・ミス率30% は旧仕様 3/5勝(平均¥734・目標¥700)に対し、乗り換えを1打目に限った後は 5/5勝(平均¥792)。「初心者が3割ミスしても元が取れる」という約束が実プレイでは破れていたのに、テストは緑のまま通っていた。この誤打表は元に戻さないこと
【v2】デモ動画の1コマ目が、レーンに皿が1枚しか無く画面の大半が空の瞬間だった。v2で打鍵行を上へ寄せたぶん収録の頭出し位置がずれ、切り落とす秒数(3.3秒)が合わなくなっていた → 5.0秒に変更し、レーンに3皿が並んで札が見え、奥の皿を選んだ直後の瞬間を1コマ目にした(15.3秒)。あわせてdemo-scenario.mjsに「奥の皿の札を押す」振り付けを足し、v2の目玉が動画に写るようにした
【v2】apps/ に meta.json の無いフォルダが1つあるだけで、tests/e2e/shared-share.spec.mjs がモジュール読み込み時にENOENTで落ち、E2Eスイート全体が0件になる。終了コードは0なので、テストが全部消えたことに気づけない → 暫定でdraftのmeta.jsonを置いて回避した。恒久対策はspec側で「meta.jsonがあるディレクトリだけを見る」ようにすること(未実施)
















