AIドパガキ Game Boy Color 版 ゲーム仕様書
本書は GBC 版「AIドパガキ」のゲーム仕様と全数値の正典である。判定・スコア・観客・レーン・ランク・進行・テキストの値が他文書と食い違った場合、本書を正とする。
対象: NES 版 nes_aiDopagaki(main = aaedebc)の GBC 移植 逐語出典: nes-port-brief.md(原典 1,281 行) 技術的実現手段: ../design/architecture.md / ../design/assets.md 版: v1(2026-09-02 起草。実測値による更新は各 issue の受入条件で行う)
0. 一行サマリと NES 版からの差分
| 項目 | 値 |
|---|---|
| タイトル | AIドパガキ(AIDOPAGAKI) |
| ジャンル | ノーツレーン型リズムゲーム(固定 1 画面・スクロールなし) |
| ハード | Game Boy Color 専用($0143 = $C0)。MBC5 / ROM 128 KB / セーブなし |
| 画面 | 160×144 px = 20×18 タイル |
| フレームレート | 59.72750 Hz(4194304 / 70224) |
| 判定 | ±4 フレーム = PERFECT(+100) / ±8 フレーム = GOOD(+50) / それ以外 = MISS |
| 収録曲 | 5 曲(うち 4 曲に譜面。曲 4 はエンディング専用) |
| 進行の永続化 | なし。電源が入っている間だけ(NES 版と同じ思想) |
NES 版から変えたもの(全リスト):
| # | 項目 | NES | GBC | 理由 |
|---|---|---|---|---|
| 1 | 画面 | 32×30 タイル | 20×18 タイル | ハード |
| 2 | NOTE_SPEED_88 | 2 px/f | 1.5 px/f(8.8 固定小数 $0180) | 96f × 1.5px = 144px でレーンが 160px 幅に収まる |
| 3 | LANE_MARKER_X | 32 | 16 | 左マージン 2 タイル |
| 4 | 観客 | 3 段 × 28 タイル幅 | 2 段 × 16 席 = 32 席(1 人 = 1 タイル幅 × 2 行) | 幅 20 タイル |
| 5 | 視聴率ゲージ | なし | 16 タイル(新規、HUD 行 1) | AUD_MAX = 16 と 1:1。ゲームルールは 1 バイトも変えない |
| 6 | 一枚絵 | なし | タイトル 1 + プロローグ 4 = 5 枚 | ユーザー要望(§9) |
| 7 | 実在サービス名 | TIKTOK が 1 箇所残存 | SNS に置換 | ユーザー確定ルール(原典 §1.2) |
| 8 | 画面遷移 | ソフトリセット(jmp reset) | シーン状態機械 | テストから状態注入するため |
| 9 | 講師 CHR ストリーミング | 自己書換えコード 385 B | 廃止(VRAM 常駐) | VRAM 2 バンク |
| 10 | DPCM ドラム | サンプル 2,307 B | CH4+CH3 のフレーム表 | GB に DPCM がない |
NES 版から変えていないもの(明示): NOTE_APPROACH_FRAMES = 96、判定窓 ±4/±8、moves 配列(全 4 曲・1 バイト不変)、スコア規則、観客増減 +2/+1/−2・初期 8・上限 16・0 で GAME OVER、ランク S/A/B60%/C40%/D、EASY(偶数拍休符化)、チュートリアル 6 ノーツ、ポーズと裏技、flow_state の番号 0–12、全テキストの逐語内容。
1. 画面フロー
1.1 状態機械
flow_state の値は NES 版と同一の 0–12 を維持する(NES engine.s:75-87)。
FLOW_TITLE(9) ─A→ START → flow_goto(FLOW_ENTRANCE(8))
│ ↓ enter が entrance_opening_init を呼ぶ(コーチ歩き入場)
│ FLOW_DIALOG_1(0) → FLOW_DIALOG_2(1) → FLOW_CLEAR_TO_TUTORIAL(4)
│ → FLOW_TUTORIAL(2)〔6ノーツ練習〕
│ → FLOW_DIALOG_3(3) → FLOW_CLEAR_TO_PERFORMANCE(5)
│ → LETS DANCE バナー(180f) → FLOW_PERFORMANCE(6)
│ ↑ enter が performance_countin_init を呼ぶ(カウントイン 96f)
│ ├ 譜面 $FF 完走 → FLOW_CLEAR(7) RANK D〜S
│ └ 観客 0 → GAME OVER (game_state=1)
├─A→ CONTINUE → 全曲クリア済なら flow_goto(FLOW_SONG_SELECT(11))
│ それ以外は flow_goto(FLOW_PERFORMANCE(6))(本番から直接)
├─A→ PROLOGUE → flow_goto(FLOW_PROLOGUE(10))(手動 A 送り)
└─600f 無操作 → flow_goto(FLOW_PROLOGUE(10))(240f 自動送りアトラクト)
FLOW_CLEAR(7) ─START→ 全曲クリアなら FLOW_ENDING(12)、それ以外は次曲へ
FLOW_ENDING(12) ─12ページ→ タイトルへ⚠ entrance_opening_init / performance_countin_init は flow_state ではない。 どちらも該当状態の enter ルーチンの中で呼ばれる内部サブルーチンであり、状態番号を持たない。NES 版の ENTRY_OPENING / ENTRY_COUNTIN に相当するが、GBC 版では entry request 機構ごと廃止したので、タイトルからの START / CONTINUE はどちらも flow_goto() を 1 回呼ぶだけである。flow_state の値域は 0-12 のまま増えない。
| サブルーチン | 呼ぶ enter | 内容 |
|---|---|---|
entrance_opening_init | flow_enter_entrance(8) | コーチを画面外 COACH_X = 0 に置き、歩行 4 コマ(フレーム 26-29)で COACH_X = 48 まで移動させる状態を初期化 |
performance_countin_init | flow_enter_performance(6) | song_frame = 0、next_note_index = 0、coach_chart_pos = 0 にし、カウントイン 96 フレーム後に sound_play_song が走るようにする(§7.4) |
⚠ flow_goto(a) は「現状態の exit → flow_state 書換え → q_hiwater_reset → 新状態の enter」を順に行う。 exit テーブルも 13 エントリすべてが実体を持つ(何もしない状態は ret のみのスタブ)。詳細は ../design/architecture.md §6.2。
| 値 | 状態 | シーン構成(§2.5) | 一枚絵 |
|---|---|---|---|
| 0 | FLOW_DIALOG_1 | GAME | — |
| 1 | FLOW_DIALOG_2 | GAME | — |
| 2 | FLOW_TUTORIAL | GAME | — |
| 3 | FLOW_DIALOG_3 | GAME | — |
| 4 | FLOW_CLEAR_TO_TUTORIAL | GAME | — |
| 5 | FLOW_CLEAR_TO_PERFORMANCE | GAME | — |
| 6 | FLOW_PERFORMANCE | GAME | — |
| 7 | FLOW_CLEAR | GAME | — |
| 8 | FLOW_ENTRANCE | GAME | — |
| 9 | FLOW_TITLE | ART | タイトル 1 枚 |
| 10 | FLOW_PROLOGUE | ART | プロローグ 4 枚(ページごとに差し替え) |
| 11 | FLOW_SONG_SELECT | GAME | — |
| 12 | FLOW_ENDING | GAME(ビジョン内テキスト) | — |
⚠ game_state(0 = プレイ中 / 1 = ゲームオーバー)は flow_state とは別変数。NES 版と同じく GAME OVER は状態ではなくフラグで表す。
⚠ ソフトリセットによる遷移は廃止。progress_magic_a/b($C3/$3C)、範囲チェック、progress_entry_request は存在しない。遷移は flow_goto(state) 1 本のみが flow_state を書き換え、exit → enter を順に呼ぶ。PyBoy は flow_state を peek するだけで進行を検証できる(../design/e2e.md §3)。
1.2 進行・アンロック(NES と同一)
STARTは常に曲 0 から新規プレイ(cleared_maskを初期化)- ランク A 以上でその曲をクリア扱いにし
cleared_maskのビットを立てて次の未クリア曲へ。B 以下は同じ曲をリトライ - 譜面のない曲 4(トワイライト・スターズ)は最初からクリア済み扱い(
CHARTED_SONGS_MASK = %00001111) - 全曲クリアで
CONTINUEが SONG SELECT 画面に変わる - 電源を切ると
cleared_maskは失われる。 これは仕様であって不具合ではない(NES 版の「バズは一夜の夢」を継承) - ⚠
CONTINUEは進行が空でも常にFLOW_PERFORMANCEへ入る(全曲クリア後だけFLOW_SONG_SELECT)。NES 版は進行が空ならSTARTと同じフルオープニングへ落ちるが、GB 版は意図的にそうしない。オープニング(入場)はSTARTの専有物で、CONTINUEは常に演奏から再開する(§9.3)
2. 画面レイアウト(20×18 タイル)
2.1 プレイ画面 ASCII 図
col: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 0 | S | C | : | 0 | 0 | 1 | 6 | 0 | 0 | . | . | . | . | . | A | U | D | : | 1 | 6 | HUD BGP0
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 1 |###0|###1|###2|###3|###4|###5|###6|###7|###8|###9|##10|##11|##12|##13|::14|::15| . | N | R | M | 視聴率ゲージ16 + MODE BGP7/BGP0
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 2 |====================== ビジョン上枠 =========================================================| BGP1
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 3 | | ◎ | | ← | | | | ↑ | | | | A | | | | | B | | | | ★レーン上半 (OBJ)
row 4 | | ◎ | | ← | | | | ↑ | | | | A | | | | | B | | | | ★レーン下半 (OBJ)
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 5 | | | | | | | P | E | R | F | E | C | T | | | | | | | | 判定文字 col 6-12 BGP2
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 6 | L | E | T | S | | D | A | N | C | E | . | . | . | . | . | . | . | . | . | . | バナー 20 タイル BGP1
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 7 |====================== ビジョン下枠 =========================================================| BGP1
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 8 |################################ 電脳グリッド壁 ############################################| BGP3
row 9 |############ [COACH 16x32] #################### [ AI 16x32 ] ###########################| + キャラ OBJ
row 10 |############ OBJ 8x16 x4 #################### OBJ 8x16 x4 ###########################|
row 11 |############ #################### ##########################|
row 12 |############ #################### ##########################|
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 13 |~~~~~~~~~~~~~~~~~~~~~~ ステージ床エッジ(ネオンライン)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| BGP4
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 14 | | |s16 |s17 |s18 |s19 |s20 |s21 |s22 |s23 |s24 |s25 |s26 |s27 |s28 |s29 |s30 |s31 | | | 観客 後段 上半 BGP6
row 15 | | |s16 |s17 |s18 |s19 |s20 |s21 |s22 |s23 |s24 |s25 |s26 |s27 |s28 |s29 |s30 |s31 | | | 観客 後段 下半 BGP6
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
row 16 | | | s0 | s1 | s2 | s3 | s4 | s5 | s6 | s7 | s8 | s9 |s10 |s11 |s12 |s13 |s14 |s15 | | | 観客 前段 上半 BGP5
row 17 | | | s0 | s1 | s2 | s3 | s4 | s5 | s6 | s7 | s8 | s9 |s10 |s11 |s12 |s13 |s14 |s15 | | | 観客 前段 下半 BGP5
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+凡例: . = 空白タイル / # = 壁パターン / ~ = 床エッジ / sN = 観客 N 席目(1 タイル幅 × 2 行 = 8×16 px)/ ###n = 視聴率ゲージ n 本目(点灯)/ ::n = 同(消灯)
⚠ キャラの足元は row 13 の上端(画面 y = 104)に接地する。 AI_Y = 72 + 高さ 32 = 104 ✓
2.2 座標定数(画面座標。OAM は X+8 / Y+16)
| 対象 | 画面 X | 画面 Y | OAM X | OAM Y | 占有行 |
|---|---|---|---|---|---|
アイ AI_X / AI_Y | 96 | 72 | 104 | 88 | row 9-12 |
お手本ガール COACH_X / COACH_Y | 48 | 72 | 56 | 88 | row 9-12 |
| 判定マーカー ◎ | 16 | 24 | 24 | 40 | row 3-4 |
| ノーツ 0-5 | 16 … 160 | 24 | 24 … 168 | 40 | row 3-4 |
床ライン FLOOR_Y | — | 104 | — | — | row 13 上端 |
検算:
- キャラ間隔 = 96 − (48+16) = 32 px(NES の
COACH_X=88/AI_X=120と同一)✓ - アイ右端 = 96+16 = 112 < 160 ✓ / コーチ左端 = 48 > 0 ✓
- 2 人の中心 = (56+104)/2 = 80 = 画面中央 ✓
- 足元 72+32 = 104 = FLOOR_Y ✓
2.3 BG マップアドレス定数
アドレス = $9800 + row*32 + col。⚠ BG マップは $9800 の 1 面だけである(../design/architecture.md D6 / §6.3)。$9C00 は使わないので、下記はすべて実アドレスの即値でよく、実行時にベースを足す必要はない。
SCORE_LBL_ADDR = $9800 ; row 0 col 0 "SC:" 3
SCORE_ADDR = $9803 ; row 0 col 3 6 桁 6
AUD_LBL_ADDR = $980E ; row 0 col 14 "AUD:" 4
AUD_ADDR = $9812 ; row 0 col 18 2 桁 2
GAUGE_ADDR = $9820 ; row 1 col 0 視聴率ゲージ 16
MODE_ADDR = $9830 ; row 1 col 16 " NRM"/" ESY" 4(先頭 1 タイルは空白)
TEXT_LINE_0_ADDR = $9860 ; row 3 col 0 本文 1 行目 20
TEXT_LINE_1_ADDR = $9880 ; row 4 col 0 本文 2 行目 20
TEXT_LINE_2_ADDR = $98A0 ; row 5 col 0 本文 3 行目 20
JUDGE_ADDR = $98A6 ; row 5 col 6 判定 7 文字 7
RANK_ADDR = $98A6 ; row 5 col 6 "RANK: S" 7
TEXT_LINE_3_ADDR = $98C0 ; row 6 col 0 本文 4 行目 20
BANNER_ADDR = $98C0 ; row 6 col 0 バナー 20
AUDIENCE_R3_ADDR = $99C2 ; row 14 col 2 後段 上半 16
AUDIENCE_R2_ADDR = $99E2 ; row 15 col 2 後段 下半 16
AUDIENCE_R1_ADDR = $9A02 ; row 16 col 2 前段 上半 16
AUDIENCE_R0_ADDR = $9A22 ; row 17 col 2 前段 下半 16
; ART シーン専用
ART_TEXT_0_ADDR = $99A0 ; row 13 col 0 かな 1 行目 20
ART_TEXT_1_ADDR = $99C0 ; row 14 col 0 かな 2 行目 20
ART_TEXT_2_ADDR = $99E0 ; row 15 col 0 かな 3 行目 20
ART_PROMPT_ADDR = $9A26 ; row 17 col 6 "PUSH A" 6⚠ 不変条件: どの 1 op も $xx00 境界をまたがない。 最長は 20 B の $98C0+20 = $98D4 と $99E0+20 = $99F4、16 B の $9A22+16 = $9A32。これによりキュー flush の内側ループが 16bit inc de ではなく 8bit inc e で書ける。ビルド時 ASSERT と PyBoy の両方で検査する(../design/architecture.md §4.3、../design/e2e.md §4)。
⚠ JUDGE_ADDR と RANK_ADDR、BANNER_ADDR と TEXT_LINE_3_ADDR は同一アドレスを共有する。 どちらも同時には出ないため。NES の MODE: / JUDGE: ラベルは 20 タイル幅に入らないので廃止し、判定文字だけを中央 7 タイルで出す。
2.4 バナー文字列(全て 20 タイル以内)
NES の GAME OVER - PUSH START(22 文字)は入らない。確定文字列:
| 状況 | 文字列 | 長さ |
|---|---|---|
| 本番開始 | LETS DANCE | 10 |
| ポーズ | PAUSE | 10 |
| ゲームオーバー | GAME OVER START | 16 |
| CLEAR ランク B 以下 | RETRY - START | 13 |
| CLEAR ランク A 以上 | NEXT SONG - START | 17 |
| 全曲クリア | ALL CLEAR - START | 17 |
| 消去 | 空白 × 20 | 20 |
2.5 シーン構成 3 種
| 構成 | LCDC | BG タイル索引 | OBJ | 使用シーン |
|---|---|---|---|---|
| GAME | $87 | $8800 符号付き(LCDC.4=0)。index $00-$7F → $9000-$97FF | ON | flow 0-8, 11, 12 |
| ART | $95 | $8000 符号なし(LCDC.4=1)。index $00-$FF → $8000-$8FFF | OFF | flow 9(タイトル), 10(プロローグ) |
| BLANK | $05(LCD OFF) | — | — | シーン切替中の GDMA 実行窓 |
⚠ LCDC が取る値はこの 3 つだけであり、bit3(BG マップ選択)は常に 0 = $9800 である。 BG マップ二面切替は廃止した(../design/architecture.md D6)。$8F(map $9C00)を書くコードは存在しない。
⚠ ART 構成では BG が $8000-$8FFF を索引するため、通常 OBJ CHR が置かれている領域を絵が占有する。ART → GAME 復帰時は LCD OFF 中にアイ CHR と講師 CHR を分割 GDMA で再ロードする(../design/architecture.md §5.4)。
⚠ ART のテキスト(メニュー・かな・PUSH A・区切り線)は VRAM bank1 の $8000-$8FFF から索引する(BG 属性 bit3 = 1)。bank0 側は一枚絵 240 タイル専用である(../design/architecture.md §3.4)。
⚠ LCDC 値の検算(bit7 LCD / bit6 WinMap / bit5 WinEn / bit4 BGData / bit3 BGMap / bit2 OBJSize / bit1 OBJEn / bit0 BGEn): GAME $87 = %1000_0111 → LCD ON, Win OFF, BG data $8800, BG map $9800, OBJ 8×16, OBJ ON, BG ON ✓ ART $95 = %1001_0101 → LCD ON, Win OFF, BG data $8000, BG map $9800, OBJ 8×16, OBJ OFF, BG ON ✓ BLANK $05 = %0000_0101 → LCD OFF, Win OFF, BG data $8800, BG map $9800, OBJ 8×16, OBJ OFF, BG ON ✓(LCD が消えているので BG data / map の値は表示に影響しない)
⚠ LCD の OFF は必ず VBlank 中(LY >= 144)に行う。ON はいつでもよい。 LCD OFF 中は LY が 0 で停止して VBlank が来ないため、「ON を VBlank 中に行う」待ちループは永久にハングする(../design/architecture.md §5.4)。
3. レーン・ノーツ
3.1 定数
| 定数 | NES | GBC | 根拠 |
|---|---|---|---|
NOTE_APPROACH_FRAMES | 96 | 96(維持) | 譜面ターゲット式の定数 96 とカウントイン長、song_frame == 96 で BGM 開始 のアサーション(NES issue #35 由来)をそのまま温存できる |
NOTE_SPEED_88 | 2 px/f | 1.5 px/f(8.8 固定小数 $0180) | 96 × 1.5 = 144 px。マーカー 16 から出現 X 160 まで |
LANE_MARKER_X | 32 | 16 | 左マージン 2 タイル |
NOTE_SPAWN_X | 224 | 160 | 画面右端ちょうど。ノーツが外から滑り込む |
LANE_Y | 56 | 24 | row 3-4(ビジョン内) |
MAX_LANE_NOTES | 6 | 6(維持) | §3.3 で 6 が必要十分と検算済み |
FRAMES_PER_BEAT(チュートリアル) | 32 | 32(維持) | 59.7275/32×60 = 111.99 BPM(NES 112.7 BPM)。体感差なし |
⚠ 定数は EQU で定義し、NOTE_APPROACH_FRAMES と NOTE_SPEED_88 の変更が定数 2 個 + export_charts_gb.py の再実行だけで済むようにする。 プレイテストで 96f/1.5px が否定された場合の退避先は 64f/2px(128 px)。
3.2 ノーツ X 座標(乗算なし)
remaining = target_frame - song_frame ; 0..96
x = LANE_MARKER_X + (remaining * 3) >> 1 ; = 16 + 1.5 * remaining
OAM X = x + 8 、OAM Y = LANE_Y + 16 = 40remaining*3 は (r<<1) + r(16bit 加算 2 回)、>>1 は srl h / rr l。LR35902 に乗算はないが 4 命令で済む。
| remaining | x | OAM X | 備考 |
|---|---|---|---|
| 96 | 160 | 168 | 出現。画面右端 ⚠ 表示されないが per-line 枠は消費する |
| 64 | 112 | 120 | |
| 32 | 64 | 72 | |
| 8 | 28 | 36 | GOOD 窓の端 |
| 4 | 22 | 30 | PERFECT 窓の端 |
| 0 | 16 | 24 | 判定マーカーと重なる |
| < 0 | 16 | 24 | 遅刻。マーカー位置にクランプ(NES と同じ) |
| > 96 | — | — | 非表示(OAM Y = 0) |
⚠ OAM X ≥ 168 の OBJ は画面に描かれないが、OAM スキャンの「1 ライン 10 枚」の枠は消費する。 出現直後のノーツ 1 枚がこれに当たる。per-line の見積り(§3.3)はこの 1 枚を含んでいる。
3.3 同時レーン上ノーツ数の検算
1 拍のフレーム数 = 4 * speed88 / 256(§7.2)。全拍にノーツが並ぶ最悪ケース:
| 曲 | BPM | speed88 | f/拍 | 96 / (f/拍) | レーン上 | 遅刻クランプ | 合計 |
|---|---|---|---|---|---|---|---|
| 0 サニーステップ | 120 | 1911 | 29.86 | 3.22 | 4 | 1 | 5 |
| 1 ネオン・ミッドナイト | 128 | 1792 | 28.00 | 3.43 | 4 | 1 | 5 |
| 2 グルーヴ・サーキット | 116 | 1977 | 30.89 | 3.11 | 4 | 1 | 5 |
| 3 クリムゾン・オーバードライブ | 152 | 1509 | 23.58 | 4.07 | 5 | 1 | 6 |
| 4 トワイライト・スターズ | 96 | 2389 | 37.33 | 2.57 | 3 | 1 | 4 |
⚠ 最悪 6 枚。MAX_LANE_NOTES = 6 はちょうど必要十分。 これは NES 版と同じ数字である(NES も 96 フレーム接近・同じ拍間隔だったため)。スラックがないのは NES 版と同条件なので、原作で破綻していない以上ここも破綻しない。
⚠ 実測(issue #6、PyBoy 2.7.0)は曲 3 で同時 5 枚であり、MAX_LANE_NOTES = 6 はスロット 1 枚ぶんの余裕を持つ。 上表の「レーン上 5 + 遅刻クランプ 1 = 6」は同時には成立しない: 遅刻クランプ中のノーツが 1 枚ある間、5 枚目の接近ノーツは必ず NOTE_APPROACH_FRAMES より遠く、まだレーンに出ていない。上限 6 は据え置く(余裕 1 枠として保持する)。詳細は ../design/architecture.md §4.4 の Issue #6 実測。
per-line OBJ 数 = マーカー 1 + ノーツ 6 = 7 枚 ≤ 10 ✓(キャラ帯は別 Y なので加算されない) ノーツ間隔(クリムゾン)= 23.58 × 1.5 = 35.4 px で視認可能。
3.4 レーンは 1 本のみ
複数レーンではない。振りの種類はノーツのタイル絵で区別する。
4. 振り(6 種)と入力
| 入力 | move id | 振り | アニメフレーム |
|---|---|---|---|
| ← | MOVE_L = 1 | サイドステップ左 | 2,3,4,5(groove→cross→roll→knee rebound) |
| → | MOVE_R = 2 | サイドステップ右 | 6,7,8,9 |
| ↑ | MOVE_U = 3 | ハンズアップ | 10,11,12,13(chest pop→wave→V→overhead clap) |
| ↓ | MOVE_D = 4 | しゃがみ/サイドストレッチ | 14,15,16,17(hip drop→lunge→twist→rising sweep) |
| A | MOVE_A = 5 | ジャンプ | 18,19,20,21(crouch→takeoff→peak→landing) |
| B | MOVE_B = 6 | スピン | 22,23,24,25(left profile→back→right→finish) |
| — | MOVE_REST = 0 | 休符 | idle 0/1 |
get_move_inputは pad_new を ← → ↑ ↓ A B の順に走査し、最初に見つかった 1 つだけを返す(同時押しは優先順で 1 つ)- アニメは全振り共通で 4 コマ ×
ANIM_STEP_LEN(5) = 20 フレーム固定・接地固定 - ジャンプのみ
pose_dyで0, -2, -5, 0の Y 浮上。pose_dxは 1〜2 px の横揺れ - アニメ第 1 ステップのみ
pose_dxを 2 倍にして「ポップ」させる(overshoot) - アイドルは半拍(16 フレーム)ごとにフレーム 0/1 を往復
4.1 コントローラマッピング
GB のジョイパッドは $FF00(rP1)の 2 行スキャン。ビット定義は NES と別なので、読み出し直後に NES 版と同じビット並びへ正規化する:
pad ビット(正規化後、NES 版 engine.s:359 と同一):
BTN_A = $80 BTN_B = $40 BTN_SELECT = $20 BTN_START = $10
BTN_UP = $08 BTN_DOWN = $04 BTN_LEFT = $02 BTN_RIGHT = $01
pad_new = ~pad_prev & pad_cur⚠ 起動直後に 1 回だけ読み捨てる(NES issue #69 の対策を継承。START+A+B タイトル復帰後、保持された START が pad_new 扱いになる事故を防ぐ)。
⚠ $FF00 の読み出しはスイッチのバウンス対策に同じ行を 2 回読んで 2 回目を採る。 GB の実機ではこれが定石。
5. 判定・スコア
5.1 判定窓
| 定数 | 値 | 判定 | 実時間 |
|---|---|---|---|
JUDGE_PERFECT | 4 | |目標拍 − 入力フレーム| ≤ 4 → PERFECT、+100 点 | ±66.97 ms |
JUDGE_GOOD | 8 | ≤ 8 → GOOD、+50 点 | ±133.94 ms |
| — | — | それ以外/誤ボタン/無入力 → MISS、0 点 |
NES との実時間差は ±0.9 ms 未満(NES ±66.56 / ±133.11 ms に対し GB ±66.97 / ±133.94 ms。差は PERFECT で 0.41 ms、GOOD で 0.83 ms)。1 フレーム 16.7 ms に対して 5% 未満なので体感差はない。判定窓を広く取るのは現代テレビ・液晶の表示遅延対策という原典の意図をそのまま引き継ぐ。
5.2 判定ロジック(4 つの規則、NES と同一)
- 入力を検出したら判定より先に必ず「押した振り」の 4 コマモーションを開始する。 タイミング外・誤ボタンでも踊り、判定だけ MISS
next_note_indexが未消化ノーツを指す。押下で判定したノーツ(MISS 含む)と、目標+8f を過ぎて無入力 MISS になったノーツは消化し、二重判定しない- 窓外の空振りはノーツを消費しない(NES issue #62)。MISS 文字を出すだけで、集計・ランク統計にも入らず、そのノーツは自分の窓でまだ判定できる
- 誤ボタン(窓内だが違う振り)は
register_miss→ ノーツ消化
5.3 スコア
- 6 桁 10 進、
score_digits[6](MSD 先頭)。score_add(加算値, 桁位置)で桁上がり伝播、999999 でクランプ - PERFECT は 100 の位に +1、GOOD は 10 の位に +5
- チュートリアル中はスコア加算しない(判定表示のみ)
- HUD 上は「視聴率」= SCORE。表示は
SC:+ 6 桁
5.4 判定文字の色(GBC 新規、タイルは差し替えない)
判定 7 文字は BG パレット 1 本(BGP2)の 8 バイト書換えだけで色を変える。VRAM キューを 1 バイトも消費しない。
| 判定 | BGP2 の色 | 15bit 値 |
|---|---|---|
| PERFECT | 金 | c0 $0000 / c1 $7FFF / c2 $135F / c3 $021F |
| GOOD | 水色 | c0 $0000 / c1 $7FFF / c2 $7FD0 / c3 $7F88 |
| MISS | 赤 | c0 $0000 / c1 $7FFF / c2 $189C / c3 $080C |
⚠ 全 BG パレットの c1 は $7FFF(白)を共有する。 これは NES 版の申し送り「全パレットの color1 = $30(白)共有がテキスト可読性の要」の継承であり、判定色の 3 パターンでも破らない。
6. 観客・視聴率ゲージ
6.1 観客定数(NES と同一)
| 定数 | 値 |
|---|---|
AUD_INIT | 8(初期) |
AUD_MAX | 16(上限) |
AUD_LV4_MIN | 13(Lv4 演出の下限) |
AUD_ANIM_PERIOD | 16(半拍ごとに 1 コマ) |
AUDIENCE_ANIM_FRAMES | 4 |
6.2 4 譜面拍ごとの集計(NES と同一)
| 条件 | 観客増減 |
|---|---|
| MISS 0 かつ 全 PERFECT | +2(フィーバー) |
| MISS 0 だが GOOD 混在 | +1 |
| MISS ちょうど 1 | +1 |
| MISS 2 以上 | −2 |
0〜16 でクランプ。0 でゲームオーバー。チュートリアル中は集計をスキップ。
6.3 観客の表示(GBC 版の再設計)
| 項目 | NES | GBC |
|---|---|---|
| 段数 | 3 | 2 |
| 1 段の席数 | 28 | 16 |
総席数 AUDIENCE_DISPLAY_MAX | 84 | 32 |
| 1 席のタイル | 1 列 × 2 行(8×16 px) | 1 列 × 2 行(8×16 px、同じ) |
| 表示席数 | (n*84 + 15)//16 | n * 2 |
| データ供給 | 17 状態 × 4 コマ × 168 B = 11,424 B のテーブル | ランタイム合成(テーブル 0 B) |
audience_display_count(n) = n * DISPLAY_MAX / AUD_MAX の式は変えず DISPLAY_MAX = 32 にするだけで = n*2 になる。
- 観客 8(初期)→ 16 席 = 前段が満席
- 観客 16(上限)→ 32 席 = 両段が満席
audience_level(n) = (n-1)//4 + 1と Lv4 ハイプ(AUD_LV4_MIN = 13)はそのまま
席の配置: 前段 = 席 0-15(row 16-17、col 2-17)、後段 = 席 16-31(row 14-15、col 2-17)。左右に 2 タイルずつマージンを取り、画面中央に対して対称。
⚠ 1 フレームに 1 BG 行だけ更新する。 4 行あるので全席の更新には 4 フレームかかる。この「1 フレーム 1 行」はキュー予算に余裕があっても維持する(../design/architecture.md §4.4)。
6.4 視聴率ゲージ(GBC 新規)
HUD 行 1、col 0-15 の 16 タイル。AUD_MAX = 16 と 1:1 で、観客 n に対して左から n タイルが点灯する。ゲームルールは 1 バイトも変えない、純粋な可読性の追加。
| 観客 n | 点灯タイル | 色(BGP7 の c2) |
|---|---|---|
| 1-4 | 1-4 | 赤 $080C |
| 5-9 | 5-9 | 橙 $021F |
| 10-12 | 10-12 | 黄 $135F |
| 13-16 | 13-16 | 白 $7FFF(Lv4 帯、点滅) |
更新は BG マップ行 1 の 32 バイトを GDMA 1 発(2 ブロック、64 dots)。観客値が変わったフレームだけ行う。
6.5 Lv4 ハイプ演出(NES と同一の発火条件)
観客 13 以上のときに A/B ノーツを PERFECT すると発火:
| 要素 | NES | GBC |
|---|---|---|
| 歓声 | 12 フレームのノイズ(CHEER_FRAMES) | 同じ(CH4、12 フレーム) |
| フラッシュ | バックドロップを白 $30 に 2 フレーム | BGP1 と BGP3 の 4 色を 2 フレームだけ $7FFF に置換(16 B、約 60 M-cycle) |
7. 楽曲と譜面
7.1 収録 5 曲
| # | ファイル | タイトル(逐語) | BPM | パターン | order 長 | 行数 | 譜面 |
|---|---|---|---|---|---|---|---|
| 0 | new_sunny_step_dpcm.json | サニーステップ [DPCM版] | 120 | 2 | 4 | 256 | あり |
| 1 | new_neon_midnight_dpcm.json | ネオン・ミッドナイト [DPCM版] | 128 | 6 | 9 | 576 | あり |
| 2 | new_groove_circuit_dpcm.json | グルーヴ・サーキット [DPCM版] | 116 | 4 | 8 | 512 | あり |
| 3 | new_crimson_overdrive_dpcm.json | クリムゾン・オーバードライブ [DPCM版] | 152 | 5 | 11 | 704 | あり |
| 4 | new_twilight_stars_dpcm.json | トワイライト・スターズ [DPCM版] | 96 | 5 | 7 | 448 | なし(エンディング専用) |
⚠ JSON ファイルは NES 版と同一のものをそのまま使う。 ファイル名の [DPCM版] はタイトル文字列の一部としてそのまま残す(曲名は ROM に出さないので表示上の影響はない。曲名を出すなら ../design/assets.md の英数フォントで別途 ASCII 短縮名を持つ)。
7.2 テンポ再計算(speed88)
GB_FPS = 4194304 / 70224 = 59.72750056960583 [Hz]
speed_gb = GB_FPS * 60 / (bpm * 4) # rowsPerBeat = 4 固定
speed88 = clamp(round(speed_gb * 256), 256, $7FFF)| # | 曲 | BPM | NES speed88 | GB speed88 | GB f/row | 行数 | 総フレーム | 実時間 | NES 実時間 |
|---|---|---|---|---|---|---|---|---|---|
| 0 | サニーステップ | 120 | 1920 | 1911 | 7.46484 | 256 | 1,911 | 32.00 s | 32.0 s |
| 1 | ネオン・ミッドナイト | 128 | 1800 | 1792 | 7.00000 | 576 | 4,032 | 67.51 s | 67.5 s |
| 2 | グルーヴ・サーキット | 116 | 1987 | 1977 | 7.72266 | 512 | 3,954 | 66.20 s | 66.2 s |
| 3 | クリムゾン・オーバードライブ | 152 | 1516 | 1509 | 5.89453 | 704 | 4,150 | 69.48 s | 69.5 s |
| 4 | トワイライト・スターズ | 96 | 2401 | 2389 | 9.33203 | 448 | 4,181 | 70.00 s | 70.0 s |
7.3 譜面
スキーマ(assets/charts/charts.json、NES と同一ファイル):
{ "version": 1,
"songs": { "<stem>": { "title": "…", "moves": [0,1,0,2,…] } } }moves は 1 拍 1 エントリの整数配列(0=REST, 1=L, 2=R, 3=U, 4=D, 5=A, 6=B)。
| # | 曲 | 拍数 | ノーツ数 | 内訳 REST/L/R/U/D/A/B |
|---|---|---|---|---|
| 0 | サニーステップ | 64 | 43 | 21/9/9/8/7/7/3 |
| 1 | ネオン・ミッドナイト | 144 | 102 | 42/21/20/16/20/14/11 |
| 2 | グルーヴ・サーキット | 128 | 91 | 37/30/18/9/11/13/10 |
| 3 | クリムゾン・オーバードライブ | 176 | 128 | 48/33/23/27/19/14/12 |
| 4 | トワイライト・スターズ | 0(未作成) | 0 | — |
⚠ moves 配列は 1 バイトも変更しない。 GBC 版で変わるのは speed88 のみ。
拍ターゲット(絶対フレーム、16bit)の事前計算式:
target[N] = NOTE_APPROACH_FRAMES + ceil(N * 4 * speed88 / 256) # N = 0 .. total_beats(+1 = コーチ先読み用)
NOTE_APPROACH_FRAMES = 96⚠ target[0] == 96。 NES 版と同じ値であることが ../design/e2e.md のシナリオ S3 の前提。
⚠ 拍間隔は一定ではない。 target[] は ceil を通した整数フレームなので、隣り合う拍の差は 4*speed88/256 の前後に揺れる(曲 3 は 4*1509/256 = 23.578 に対し実際の差が 24, 24, 23, 24, 23 …)。したがって**「拍間隔が 4*speed88/256 の整数倍である」という検査は 5 曲中 4 曲で成立しない**。同期の検証は「同じ ceil 式で期待値を再計算し、target[N] と一致するか」で行う(../design/e2e.md S7)。
チュートリアル譜面は ROM にハードコード: 4 拍に 1 ノーツ × 6 グループで L→R→U→D→A→B を 1 回ずつ、$FF 終端。
7.4 譜面と音楽の同期
- 本番はカウントイン 96 フレームの後、
update_rhythmがsong_frame == 96の瞬間にsound_play_songを呼ぶ - 拍ターゲットは BGM ドライバの 8.8 固定小数行タイマと同じ丸めで事前計算されるため、構造的にズレない
- チュートリアルのみ BGM を止めてメトロノーム(CH1 ビープ、A5 ≈ 880 Hz、
BEEP_FRAMES = 8)で 32 f/拍固定 - コーチは
coach_chart_posという独立した譜面カーソルを持ち、プレイヤーが早押しでノーツを消してもお手本は必ず踊る
8. ランク・CLEAR・エンディング
8.1 ランク算出(NES と同一)
| ランク | 条件 |
|---|---|
| S | total_miss == 0 かつ total_good == 0(全 PERFECT) |
| A | total_miss == 0(GOOD あり) |
| B | hits*5 >= total*3(正答率 60% 以上) |
| C | hits*5 >= total*2(正答率 40% 以上) |
| D | それ未満 |
hits = total_perfect + total_good、total = hits + total_miss。
⚠ 割り算を使わず 16bit の乗算 + 比較で実装する。 LR35902 にも除算命令がないので NES 版の実装方針がそのまま通る。曲ごとにノーツ数が違うため固定 16 ノーツ閾値は使わない。
8.2 CLEAR 画面
- 行 5 col 6 に
RANK:+DCBASの 1 文字 - 行 6 バナーに §2.4 の CLEAR 文字列
S ランク祝賀演出(NES と同一):
- アイ・コーチともジャンプ 4 コマ(フレーム 18-21)を永久ループ
- 8 枚の紙吹雪 OBJ(レーンの OAM 枠 8-15 を再利用、2 形状 × パレット/フリップの組合せ)が Galois LFSR
x^8 + x^6 + x^5 + x^4 + 1で左右に揺れながら落下 - ⚠ CLEAR 中の OAM 使用は 16 枠(アイ 4 + コーチ 4 + 紙吹雪 8)で、ゲームプレイ中の 15 枠より多い。紙吹雪は 8 枚に 16 px ずつ位相をずらした初期 Y を与え、同一の 16 px 帯に 2 枚を超えて入らないようにする(
../design/architecture.md§4.1)。この不変条件がないと、キャラ帯(画面 y 72-103)に紙吹雪が集まったとき 1 ライン 10 枚を超える - 60 フレーム周期のソフトウェア CH1 ピッチスライド(ホイッスル SE)
8.3 エンディング
全曲クリア後、CLEAR で START → FLOW_ENDING(12)。曲 4(トワイライト・スターズ)と共に 12 ページを再生。ビジョン内テキスト演出(一枚絵ではない)。12 ページ後にタイトルへ。
9. 一枚絵(GBC 版の新規要素)
⚠ NES 版には一枚絵が 1 枚も存在しない(原典 §1.3 に「重要: プロローグに一枚絵(画像)は存在しない」と明記)。GBC 版で一枚絵を入れるのはユーザーの明示的な要望による意図的な追加であり、原作の逸脱として自覚的に行う。テキスト演出そのものは原典どおり維持する。
9.1 対象 5 枚
| # | シーン | 内容 |
|---|---|---|
| 1 | タイトル | ロゴ + アイのポートレート |
| 2 | プロローグ p1 | スマホを見るアイ(自室) |
| 3 | プロローグ p2 | 光る広告 → 吸い込まれる |
| 4 | プロローグ p3 | 電脳世界とお手本ガールのシルエット |
| 5 | プロローグ p4 | 踊り出すアイ |
9.2 ART シーンのレイアウト
row 0-11 : 一枚絵 160×96 px(20×12 = 240 セル、ユニークタイル ≤ 240)
row 12 : 区切り(ネオンライン 1 行)
row 13-15 : かなテキスト 3 行(各 20 タイル、かなフォントは VRAM bank1)
row 16 : 空(タイトルだけが作品名 `AI DOPAGAKI` を置く。§9.3)
row 17 : `PUSH A`(col 6-11。タイトルは同じ行を `MODE:` が使う)⚠ 絵の高さを 96 px(12 行)に絞ったのは、原典のテキスト演出(下部 3 行)を維持するため。 全画面 160×144 の絵にすると 360 セルとなり、テキストを重ねる場所がなくなる。
9.3 タイトル画面のメニュー
タイトルは ART レイアウトを流用し、row 13-15 をメニューに使う:
row 13 : ▶ START
row 14 : CONTINUE
row 15 : PROLOGUE
row 16 : AI DOPAGAKI (col 4-14)
row 17 : MODE:NORMALカーソル ▶ は BG タイルの差し替えで表現する(ART は OBJ OFF のため)。
⚠ row 16 の AI DOPAGAKI は §9.2 の「row 16 は空」からの意図的な差分である(ユーザー要望、2026-09-03)。タイトルの一枚絵は 160×96(row 0-11)しかなく、作品名を絵の中に描くとユニークタイル 240 の枠を食う。row 16 は ART レイアウトでどのシーンも使っていない唯一の行なので、UI 英数 11 タイルを col 4-14 に置く(TITLE_LOGO_ADDR = $9A04)。プロローグ(flow 10)では row 16 は空のままである。
⚠ カーソルは巡回しない(NES 版との意図的な差分)。 START で ↑、PROLOGUE で ↓ を押してもカーソルはその行に留まる(title_move_cursor が端で止める)。NES 版は 3 項目を巡回するが、GB 版は 3 行が同時に見えているので端で止める方が誤選択が少ない。CONTINUE の遷移先も NES 版と異なる(進行が空でも常に演奏へ。§1.2)。
⚠ メニュー 4 行に必要なユニークグリフは 16 種(: A C D E G I L M N O P R S T U)で、カーソル ▶ と空白を加えて 18 種になる。 ⚠ H は 4 行のどこにも現れない(H を含むのは PUSH A)。 これらはすべて VRAM bank1 に置いた UI 英数フォント 47 グリフから索引する(BG 属性 bit3 = 1、../design/architecture.md §3.4)。ART 構成では LCDC.4 = 1 のため bank0 $9000 常駐の GAME 用フォントはアドレスできないので、ART 用のテキストタイルは bank0 ではなく bank1 に置くのが本設計の解である。bank0 は一枚絵 240 タイル専用。
9.4 送りと自動再生
| 操作 | 効果 |
|---|---|
| プロローグで A | 次ページへ(手動送り) |
| タイトルで 新規押下が無い 600 フレーム | プロローグへ(アトラクト、240 フレーム自動送り) |
| テキストの表示 | 8 フレームに 1 行(原典どおり。1 ページ 3 行 = 24 フレームで表示完了) |
⚠ アトラクトの 600 フレームは「新規押下が無い」フレーム数である。 title_attract_step が見るのは hPadNew(そのフレームで新しく押されたビット)だけなので、ボタンを押しっぱなしにしていてもカウンタは進みアトラクトへ落ちる。 これは NES 版 update_title と同一の挙動であり、意図した仕様である。
⚠ 「8 フレームに 1 文字」のタイプライタ演出は採らない。 1 行 18 文字なら 144 フレーム/行、3 行で 432 フレームとなり、アトラクトの 240 フレーム自動送りと直接矛盾する(1 ページを送り切る前に次ページへ行く)。原典は「8 フレームに 1 行」である。
ページ間の遷移: パレットフェードアウト(16 段)→ LCD OFF → GDMA で次の絵をロード → フェードイン。
10. テキスト全文(逐語)
10.1 表記ルール
⚠ 実在サービス名は使わず「SNS」で統一する(ユーザー確定 2026-07-28)。NES 版の tools/gen_prologue_font.py に残っていた TIKTOK は GBC 版では最初から SNS に置換する。置換はこの 1 箇所のみで、他の文字は 1 字も変えない。
⚠ 全 50 行(プロローグ 12 + 会話 8 + エンディング 30)が 20 タイル以内(1 文字 = 1 タイル。かな・カナ・英数・記号すべて 8×8)。最長 = 18 タイル(8ビットの せかいに すいこまれた! / でんしせかいから だっしゅつ せよ! / はじめての バイトだい うれしいなー)。この計測値は tools/test_text_pages.py が毎回再計算し、20 を超えたらビルドを落とす。表の数値と食い違ったらテストの出力を正とする。
10.2 プロローグ(4 ページ × 3 行)
| ページ | 行 | 本文 | タイル |
|---|---|---|---|
| 1 | 1 | じょしこうせいの アイは | 12 |
| 1 | 2 | いつものように SNSを | 12 |
| 1 | 3 | みつづけていた | 7 |
| 2 | 1 | ふしぎな こうこくを タップすると | 17 |
| 2 | 2 | スマホが ひかりだして | 11 |
| 2 | 3 | 8ビットの せかいに すいこまれた! | 18 |
| 3 | 1 | もとのせかいに もどるには | 13 |
| 3 | 2 | ダンスで しちょうりつを あげて | 16 |
| 3 | 3 | バズらなければ ならない | 12 |
| 4 | 1 | みるだけだった アイは | 11 |
| 4 | 2 | みようみまねで おどりだす | 13 |
| 4 | 3 | でんしせかいから だっしゅつ せよ! | 18 |
⚠ 原文(NES 版)は 1-2 が いつものように TIKTOK を。 TIKTOK → SNS の置換に伴い後続の半角スペースも詰めて SNSを とする。これは原典の別行 みんなも SNSを みるだけでなく(エンディング 11-1)と表記を揃えるため。改行位置・語順・句読点は一切変えない。
10.3 会話デモ(3 ページ)
| ページ | 行 | 本文 | タイル |
|---|---|---|---|
| 1 | 1 | あなたがしんじんのアイね | 12 |
| 1 | 2 | さっそくダンスのしかたをおしえるわ | 17 |
| 2 | 1 | リズムにあわせてわたしと | 12 |
| 2 | 2 | おなじうごきをしてちょうだい | 14 |
| 2 | 3 | じゅうじキーとA,Bボタンで | 14 |
| 2 | 4 | おどるのよ | 5 |
| 3 | 1 | うまくできてるわね | 9 |
| 3 | 2 | では、さっそくほんばんスタート! | 16 |
10.4 エンディング(12 ページ)
| ページ | 本文(行ごと) | 最長タイル |
|---|---|---|
| 1 | コーチ「きょうのライブ、 / さいごまで おどりきれたわね! / おつかれさま | 15 |
| 2 | コーチ「はい、これ きょうの / にっとうね。いいおどりだったから / また おねがいするわ | 16 |
| 3 | アイ「わー! / ありがとうございます! / はじめての バイトだい うれしいなー | 18 |
| 4 | アイ「なかみは いくらかなー? / うわっ まぶしい! | 15 |
| 5 | (ふうとうからの ひかりが / しゅういを つつみこむ) | 13 |
| 6 | (めをあけると / じぶんのへやに もどっていた) | 15 |
| 7 | アイ「あ!じぶんのへやに / もどってる!? / ゆめだったのかなー? | 12 |
| 8 | (すごい げんじつみのある / ゆめだった...) | 14 |
| 9 | アイ「ダンス たのしかったし、 / わたしも みるだけじゃなくて / ダンスかつどう してみようかな! | 16 |
| 10 | どうがしか みてなかった / ドパガキのアイは / ダンスに めざめた | 12 |
| 11 | みんなも SNSを みるだけでなく / じぶんで かつどう / してみましょう | 17 |
| 12 | THE END | 7 |
⚠ エンディング 12 ページの本文は unittest に逐語で書き込み、コピー承認ゲートにする(NES 版 test_ending_font.py の方式を踏襲)。テキストの変更はテストの変更を強制する。
10.5 ストーリー(説明書 LP 用、逐語)
キャッチコピー: 見るだけの毎日は、今日で終わり。 / 踊れ。バズれ。生きて帰れ。
女子高生の主人公 AI(アイ) は、いつものように SNS を見続けていた。 そこに流れてきた、見たことのない不思議な広告——。 タップした瞬間、スマホがまばゆく光り、アイは 8Bit の電脳世界に吸い込まれてしまった! 元の世界に戻る方法はただひとつ。ダンスで視聴率を稼ぎ、現実世界の SNS でバズること。 いつもは見るだけで満足していたドパガキのアイが、見様見真似のダンスで電子世界からの脱出に挑む!
- AI(アイ):
主人公。見る専のドパガキ女子高生。ダンス経験ゼロ。だが、毎日何千本も動画を見てきた目は、すべての振りを記憶している——あとは体が動けば。 - お手本ガール:
電脳世界の謎のダンサー。アイの前に現れ、無言で振りを実演する。彼女は何者なのか。なぜアイを導くのか。すべては踊りの先に。
11. ポーズ・裏技・モード
11.1 操作一覧(NES と同一)
| 操作 | 効果 |
|---|---|
本番中 START | ポーズ切替。バナー行に PAUSE 。NR51 = 0(ミキサ遮断) + sound_tick をゲートして BGM を行位置ごと凍結。解除で NR51 を復帰して全チャネル再トリガ |
ポーズ中 A+B+SELECT 同時押し | total_good / total_miss を 0 にして通常の CLEAR 経路を通す = 強制ランク S |
どこでも START+A+B 同時押し | タイトルへ復帰(title_return_request フラグで次フレームに遷移) |
タイトルで ← → ← → ↑ ↓ B B | 全曲クリア扱い → CONTINUE が選曲画面に。成功時は白フラッシュ + 歓声ノイズ |
タイトルで ← / → / SELECT | NORMAL ⇄ EASY 切替(HUD 行 1 col 17-19 の NRM / ESY。MODE_ADDR = $9830 は col 16 の空白から始まる 4 タイルを書く) |
会話中 SELECT | BGM をその場で切替。会話には譜面もカウントインも無いので restart_pending を経由せず即座に鳴らし直す。曲の選び方(曲 4 をスキップして 0 へ)は本番中の SELECT と song_next_charted を共有する |
本番中 SELECT | 次の曲で演奏やり直し(restart_pending で次フレームに遅延実行)。曲 4 はスキップして 0 へ |
チュートリアル中 SELECT | 残りを省略して最終セリフへ |
START(ゲームオーバー / クリア時) | リスタート |
⚠ ポーズで snd_silence を呼んではならない。 snd_silence は NR52 = 0 で APU 全体をリセットするため、チャンネルの周期・音量・duty 位相をすべて失い、「行位置ごと凍結して復帰する」という要件と両立しない。snd_silence はシーン遷移専用(flow_goto の exit から呼ぶ)である(../design/architecture.md §7.10)。
11.2 EASY モード
get_chart_entry で 偶数 index の拍(各小節の 1 拍目・3 拍目)を強制的に休符化する。
⚠ $FF 終端はフィルタ対象外(消すと譜面が終わらず暴走する)。
12. 定数一覧(src/constants.inc)
; --- タイミング ---
GB_FPS_NUM EQU 4194304
GB_FPS_DEN EQU 70224 ; 59.72750 Hz
NOTE_APPROACH_FRAMES EQU 96
NOTE_SPEED_88 EQU $0180 ; 1.5 px/frame(8.8 固定小数)
FRAMES_PER_BEAT EQU 32 ; チュートリアル専用 = 111.99 BPM
JUDGE_PERFECT EQU 4
JUDGE_GOOD EQU 8
BEEP_FRAMES EQU 8
CHEER_FRAMES EQU 12
ANIM_STEP_LEN EQU 5 ; 4 コマ × 5f = 20f
LETS_DANCE_FRAMES EQU 180
ATTRACT_IDLE_FRAMES EQU 600
ATTRACT_PAGE_FRAMES EQU 240
TEXT_LINE_FRAMES EQU 8 ; 8 フレームに 1 行
HYPE_FLASH_FRAMES EQU 2 ; Lv4 ハイプ: BGP1/BGP3 が白の 2 フレーム(§6.5)
; --- レーン ---
LANE_MARKER_X EQU 16
NOTE_SPAWN_X EQU 160
LANE_Y EQU 24
MAX_LANE_NOTES EQU 6
; --- キャラ ---
AI_X EQU 96
AI_Y EQU 72
COACH_X EQU 48
COACH_Y EQU 72
FLOOR_Y EQU 104
; --- 観客・ゲージ ---
AUD_INIT EQU 8
AUD_MAX EQU 16
AUD_LV4_MIN EQU 13
AUD_ANIM_PERIOD EQU 16
AUDIENCE_TIERS EQU 2
AUDIENCE_SEATS_PER_TIER EQU 16
AUDIENCE_DISPLAY_MAX EQU 32
AUDIENCE_ANIM_FRAMES EQU 4
AUDIENCE_COL EQU 2
GAUGE_TILES EQU 16
; --- BG マップアドレス(§2.3。BG マップは $9800 の 1 面のみ) ---
SCORE_LBL_ADDR EQU $9800 ; row 0 col 0 "SC:" 3
SCORE_ADDR EQU $9803 ; row 0 col 3 6 桁 6
AUD_LBL_ADDR EQU $980E ; row 0 col 14 "AUD:" 4
AUD_ADDR EQU $9812 ; row 0 col 18 2 桁 2
GAUGE_ADDR EQU $9820 ; row 1 col 0 視聴率ゲージ 16
MODE_ADDR EQU $9830 ; row 1 col 16 " NRM"/" ESY" 4
TEXT_LINE_0_ADDR EQU $9860 ; row 3 col 0 本文 1 行目 20
TEXT_LINE_1_ADDR EQU $9880 ; row 4 col 0 本文 2 行目 20
TEXT_LINE_2_ADDR EQU $98A0 ; row 5 col 0 本文 3 行目 20
JUDGE_ADDR EQU $98A6 ; row 5 col 6 判定 7 文字 7
RANK_ADDR EQU $98A6 ; row 5 col 6 "RANK: S" 7
TEXT_LINE_3_ADDR EQU $98C0 ; row 6 col 0 本文 4 行目 20
BANNER_ADDR EQU $98C0 ; row 6 col 0 バナー 20
AUDIENCE_R3_ADDR EQU $99C2 ; row 14 col 2 後段 上半 16
AUDIENCE_R2_ADDR EQU $99E2 ; row 15 col 2 後段 下半 16
AUDIENCE_R1_ADDR EQU $9A02 ; row 16 col 2 前段 上半 16
AUDIENCE_R0_ADDR EQU $9A22 ; row 17 col 2 前段 下半 16
ART_TEXT_0_ADDR EQU $99A0 ; row 13 col 0 かな 1 行目 20
ART_TEXT_1_ADDR EQU $99C0 ; row 14 col 0 かな 2 行目 20
ART_TEXT_2_ADDR EQU $99E0 ; row 15 col 0 かな 3 行目 20
ART_PROMPT_ADDR EQU $9A26 ; row 17 col 6 "PUSH A" 6
ART_DIVIDER_ADDR EQU $9980 ; row 12 col 0 区切り線 20
TITLE_MODE_ADDR EQU $9A22 ; row 17 col 2 "MODE:NORMAL" 11
TITLE_LOGO_ADDR EQU $9A04 ; row 16 col 4 "AI DOPAGAKI" 11
; --- 曲・譜面 ---
CHARTED_SONGS_MASK EQU %00001111 ; 譜面のある曲 0-3。曲 4 は最初からクリア済み扱い
ALL_SONGS_MASK EQU %00011111 ; 収録 5 曲ぶん(§1.2 / §7.1)
; --- クリアランク(§8.1。数値の大小がそのまま優劣) ---
RANK_D EQU 0
RANK_C EQU 1
RANK_B EQU 2
RANK_A EQU 3
RANK_S EQU 4
; --- バナー(§2.4。可視長は全て 20 タイル以内) ---
BANNER_LEN EQU 20 ; 行 6 の固定長。右は空白で埋める
BANNER_PAUSE_LEN EQU 10 ; " PAUSE "
BANNER_GAMEOVER_LEN EQU 16 ; "GAME OVER START"
BANNER_RETRY_LEN EQU 13 ; "RETRY - START"
BANNER_NEXT_LEN EQU 17 ; "NEXT SONG - START"
BANNER_ALL_CLEAR_LEN EQU 17 ; "ALL CLEAR - START"
; --- CLEAR 画面と S ランク祝賀(§8.2) ---
RANK_TEXT_LEN EQU 7 ; "RANK: " + ランク 1 文字
CONFETTI_COUNT EQU 8 ; 紙吹雪 8 枚(OAM 8-15)
CLEAR_SFX_PERIOD EQU 60 ; ホイッスル SE の周期(フレーム)
; --- 裏技(§11.1) ---
TITLE_CHEAT_LEN EQU 8 ; タイトルの ← → ← → ↑ ↓ B B
; --- タイトルメニュー(§9.3) ---
TITLE_ITEM_COUNT EQU 3 ; START / CONTINUE / PROLOGUE
TITLE_MODE_LEN EQU 11 ; "MODE:NORMAL" / "MODE:EASY " の固定幅
TITLE_LOGO_LEN EQU 11 ; row 16 の "AI DOPAGAKI"(UI 英数)
; --- プロローグ(§9.2 / §9.4 / §10.2) ---
PROLOGUE_PAGE_COUNT EQU 4 ; 4 ページ × 3 行
PROLOGUE_PROMPT_LEN EQU 6 ; row 17 col 6 の "PUSH A"
; --- 進行の結果(§1.2) ---
RESULT_RETRY EQU 0 ; RANK B 以下。同じ曲をやり直す
RESULT_NEXT EQU 1 ; RANK A 以上。次の未クリア曲へ
RESULT_ALL_CLEAR EQU 2 ; ALL_SONGS_MASK に到達した
; --- LCDC の 3 値(§2.5。bit3 は常に 0 = $9800) ---
LCDC_GAME EQU $87
LCDC_ART EQU $95
LCDC_BLANK EQU $05
; --- フロー(NES と同一の番号) ---
FLOW_DIALOG_1 EQU 0
FLOW_DIALOG_2 EQU 1
FLOW_TUTORIAL EQU 2
FLOW_DIALOG_3 EQU 3
FLOW_CLEAR_TO_TUTORIAL EQU 4
FLOW_CLEAR_TO_PERFORMANCE EQU 5
FLOW_PERFORMANCE EQU 6
FLOW_CLEAR EQU 7
FLOW_ENTRANCE EQU 8
FLOW_TITLE EQU 9
FLOW_PROLOGUE EQU 10
FLOW_SONG_SELECT EQU 11
FLOW_ENDING EQU 12
; --- 振り ---
MOVE_REST EQU 0
MOVE_L EQU 1
MOVE_R EQU 2
MOVE_U EQU 3
MOVE_D EQU 4
MOVE_A EQU 5
MOVE_B EQU 6
; --- ボタン(正規化後、NES と同一ビット並び) ---
BTN_A EQU $80
BTN_B EQU $40
BTN_SELECT EQU $20
BTN_START EQU $10
BTN_UP EQU $08
BTN_DOWN EQU $04
BTN_LEFT EQU $02
BTN_RIGHT EQU $01⚠ 本節と src/constants.inc は 1:1 対応でなければならない(../design/issues.md #1 受入条件 5 / #16 受入条件 6)。名前の揺れはそのまま静的解析テストの失敗になるので、AUDIENCE_ANIM_FRAMES(AUDIENCE_ANIMATION_FRAMES ではない)と NOTE_SPEED_88(NOTE_SPEED ではない)を全文書で使う。 BG マップアドレス 21 本は ../design/issues.md #4 受入条件 3 が ASSERT 検査を求めているので、.inc に載る形でここに揃えてある。 ⚠ TITLE_MODE_ADDR(row 17 col 2、11 タイル)は ART_PROMPT_ADDR(row 17 col 6、6 タイル)と走りが重なる。 row 17 は 20 列しかなく、MODE:NORMAL の 11 タイルを col 6-11 の外に置ける位置は存在しない。タイトル(flow 9)とプロローグ(flow 10)は同時に表示されないので、この行の所有者はシーンで決まる(§9.3)。 ⚠ **同じく TITLE_MODE_ADDR は AUDIENCE_R0_ADDR(観客前段の下半、row 17 col 0 から)とも同一の走りに乗る。**row 17 は GAME 構成では観客前段の下半、ART 構成ではタイトルの MODE: 行であり、所有者は wSceneIsArt で決まる(GAME なら観客、ART ならタイトル / プロローグ)。ART 構成では観客の描画そのものがゲートで止まっているので衝突しない(../design/architecture.md §3.4)。
13. 未決事項
| # | 論点 | 現状の扱い | 判断が必要な時期 |
|---|---|---|---|
| S1 | 一枚絵 5 枚の絵柄(元画の選定とトリミング) | assets/concept/*.png から起こす前提。減色後 4 倍プレビュー PNG をレビュー担当(Fable)がプレビューを目視承認するまで先へ進めない | 子 issue 11(タイトル)の前 |
| S2 | 曲 4(トワイライト・スターズ)の譜面新規作成 | NES 版どおり未作成。エンディング専用のまま出荷 | 出荷後 |
| S3 | 曲名の画面表示 | 確定: SONG SELECT 画面でのみ ASCII 短縮名を出す。 本番中は曲名を出さない(現レイアウトに曲名行がなく、HUD の 1 行目はスコアと観客で埋まっている)。選曲中の曲は wCurrentSong が正典で、本番中の切替は SELECT(§11.1)で行う。描画の実装は子 issue 11 | 決着済み(issue #8) |
| S4 | SRAM セーブの将来追加 | 本版はセーブなし($0147 = $19)で確定。追加する場合は $19 → $1B + $0149 = $02 のヘッダ 2 バイト変更 | 別 issue |
| S5 | Web トラッカー/譜面メーカーの GB APU 版 | 作らない。既存 5 曲の JSON をそのまま使うため作曲作業が発生しない | 曲を増やす段階 |
| S6 | NOTE_SPEED_88 の最終値(1.5 px/f) | 確定: 1.5 px/f・96f。 子 issue #6 のプレイテスト(PyBoy 出力で判断)で採用した。ノーツ間隔は最密の曲 3 でも 35.4 px あり、視認・判定とも成立する。64f × 2px への退避案は破棄 | 決着済み(issue #6) |