Skip to content

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 Hz4194304 / 70224
判定±4 フレーム = PERFECT(+100) / ±8 フレーム = GOOD(+50) / それ以外 = MISS
収録曲5 曲(うち 4 曲に譜面。曲 4 はエンディング専用)
進行の永続化なし。電源が入っている間だけ(NES 版と同じ思想)

NES 版から変えたもの(全リスト):

#項目NESGBC理由
1画面32×30 タイル20×18 タイルハード
2NOTE_SPEED_882 px/f1.5 px/f(8.8 固定小数 $018096f × 1.5px = 144px でレーンが 160px 幅に収まる
3LANE_MARKER_X3216左マージン 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 バンク
10DPCM ドラムサンプル 2,307 BCH4+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_initflow_state ではない。 どちらも該当状態の enter ルーチンの中で呼ばれる内部サブルーチンであり、状態番号を持たない。NES 版の ENTRY_OPENING / ENTRY_COUNTIN に相当するが、GBC 版では entry request 機構ごと廃止したので、タイトルからの START / CONTINUE はどちらも flow_goto() を 1 回呼ぶだけである。flow_state の値域は 0-12 のまま増えない。

サブルーチン呼ぶ enter内容
entrance_opening_initflow_enter_entrance(8)コーチを画面外 COACH_X = 0 に置き、歩行 4 コマ(フレーム 26-29)で COACH_X = 48 まで移動させる状態を初期化
performance_countin_initflow_enter_performance(6)song_frame = 0next_note_index = 0coach_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)一枚絵
0FLOW_DIALOG_1GAME
1FLOW_DIALOG_2GAME
2FLOW_TUTORIALGAME
3FLOW_DIALOG_3GAME
4FLOW_CLEAR_TO_TUTORIALGAME
5FLOW_CLEAR_TO_PERFORMANCEGAME
6FLOW_PERFORMANCEGAME
7FLOW_CLEARGAME
8FLOW_ENTRANCEGAME
9FLOW_TITLEARTタイトル 1 枚
10FLOW_PROLOGUEARTプロローグ 4 枚(ページごとに差し替え)
11FLOW_SONG_SELECTGAME
12FLOW_ENDINGGAME(ビジョン内テキスト)

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
  • 全曲クリアで CONTINUESONG 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画面 YOAM XOAM Y占有行
アイ AI_X / AI_Y967210488row 9-12
お手本ガール COACH_X / COACH_Y48725688row 9-12
判定マーカー ◎16242440row 3-4
ノーツ 0-516 … 1602424 … 16840row 3-4
床ライン FLOOR_Y104row 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_ADDRRANK_ADDRBANNER_ADDRTEXT_LINE_3_ADDR は同一アドレスを共有する。 どちらも同時には出ないため。NES の MODE: / JUDGE: ラベルは 20 タイル幅に入らないので廃止し、判定文字だけを中央 7 タイルで出す。

2.4 バナー文字列(全て 20 タイル以内)

NES の GAME OVER - PUSH START(22 文字)は入らない。確定文字列:

状況文字列長さ
本番開始LETS DANCE10
ポーズ PAUSE 10
ゲームオーバーGAME OVER START16
CLEAR ランク B 以下RETRY - START13
CLEAR ランク A 以上NEXT SONG - START17
全曲クリアALL CLEAR - START17
消去空白 × 2020

2.5 シーン構成 3 種

構成LCDCBG タイル索引OBJ使用シーン
GAME$87$8800 符号付き(LCDC.4=0)。index $00-$7F$9000-$97FFONflow 0-8, 11, 12
ART$95$8000 符号なし(LCDC.4=1)。index $00-$FF$8000-$8FFFOFFflow 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_0101LCD 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 定数

定数NESGBC根拠
NOTE_APPROACH_FRAMES9696(維持)譜面ターゲット式の定数 96 とカウントイン長、song_frame == 96 で BGM 開始 のアサーション(NES issue #35 由来)をそのまま温存できる
NOTE_SPEED_882 px/f1.5 px/f(8.8 固定小数 $018096 × 1.5 = 144 px。マーカー 16 から出現 X 160 まで
LANE_MARKER_X3216左マージン 2 タイル
NOTE_SPAWN_X224160画面右端ちょうど。ノーツが外から滑り込む
LANE_Y5624row 3-4(ビジョン内)
MAX_LANE_NOTES66(維持)§3.3 で 6 が必要十分と検算済み
FRAMES_PER_BEAT(チュートリアル)3232(維持)59.7275/32×60 = 111.99 BPM(NES 112.7 BPM)。体感差なし

定数は EQU で定義し、NOTE_APPROACH_FRAMESNOTE_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 = 40

remaining*3(r<<1) + r(16bit 加算 2 回)、>>1srl h / rr lLR35902 に乗算はないが 4 命令で済む。

remainingxOAM X備考
96160168出現。画面右端 ⚠ 表示されないが per-line 枠は消費する
64112120
326472
82836GOOD 窓の端
42230PERFECT 窓の端
01624判定マーカーと重なる
< 01624遅刻。マーカー位置にクランプ(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)。全拍にノーツが並ぶ最悪ケース:

BPMspeed88f/拍96 / (f/拍)レーン上遅刻クランプ合計
0 サニーステップ120191129.863.22415
1 ネオン・ミッドナイト128179228.003.43415
2 グルーヴ・サーキット116197730.893.11415
3 クリムゾン・オーバードライブ152150923.584.07516
4 トワイライト・スターズ96238937.332.57314

最悪 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)
AMOVE_A = 5ジャンプ18,19,20,21(crouch→takeoff→peak→landing)
BMOVE_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_dy0, -2, -5, 0 の Y 浮上。pose_dx は 1〜2 px の横揺れ
  • アニメ第 1 ステップのみ pose_dx を 2 倍にして「ポップ」させる(overshoot)
  • アイドルは半拍(16 フレーム)ごとにフレーム 0/1 を往復

4.1 コントローラマッピング

GB のジョイパッドは $FF00rP1)の 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_PERFECT4|目標拍 − 入力フレーム| ≤ 4 → PERFECT、+100 点±66.97 ms
JUDGE_GOOD8≤ 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 と同一)

  1. 入力を検出したら判定より先に必ず「押した振り」の 4 コマモーションを開始する。 タイミング外・誤ボタンでも踊り、判定だけ MISS
  2. next_note_index が未消化ノーツを指す。押下で判定したノーツ(MISS 含む)と、目標+8f を過ぎて無入力 MISS になったノーツは消化し、二重判定しない
  3. 窓外の空振りはノーツを消費しない(NES issue #62)。MISS 文字を出すだけで、集計・ランク統計にも入らず、そのノーツは自分の窓でまだ判定できる
  4. 誤ボタン(窓内だが違う振り)は 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 値
PERFECTc0 $0000 / c1 $7FFF / c2 $135F / c3 $021F
GOOD水色c0 $0000 / c1 $7FFF / c2 $7FD0 / c3 $7F88
MISSc0 $0000 / c1 $7FFF / c2 $189C / c3 $080C

全 BG パレットの c1 は $7FFF(白)を共有する。 これは NES 版の申し送り「全パレットの color1 = $30(白)共有がテキスト可読性の要」の継承であり、判定色の 3 パターンでも破らない。


6. 観客・視聴率ゲージ

6.1 観客定数(NES と同一)

定数
AUD_INIT8(初期)
AUD_MAX16(上限)
AUD_LV4_MIN13(Lv4 演出の下限)
AUD_ANIM_PERIOD16(半拍ごとに 1 コマ)
AUDIENCE_ANIM_FRAMES4

6.2 4 譜面拍ごとの集計(NES と同一)

条件観客増減
MISS 0 かつ 全 PERFECT+2(フィーバー)
MISS 0 だが GOOD 混在+1
MISS ちょうど 1+1
MISS 2 以上−2

0〜16 でクランプ。0 でゲームオーバー。チュートリアル中は集計をスキップ。

6.3 観客の表示(GBC 版の再設計)

項目NESGBC
段数32
1 段の席数2816
総席数 AUDIENCE_DISPLAY_MAX8432
1 席のタイル1 列 × 2 行(8×16 px)1 列 × 2 行(8×16 px、同じ)
表示席数(n*84 + 15)//16n * 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-41-4$080C
5-95-9$021F
10-1210-12$135F
13-1613-16$7FFF(Lv4 帯、点滅)

更新は BG マップ行 1 の 32 バイトを GDMA 1 発(2 ブロック、64 dots)。観客値が変わったフレームだけ行う。

6.5 Lv4 ハイプ演出(NES と同一の発火条件)

観客 13 以上のときに A/B ノーツを PERFECT すると発火:

要素NESGBC
歓声12 フレームのノイズ(CHEER_FRAMES同じ(CH4、12 フレーム)
フラッシュバックドロップを白 $30 に 2 フレームBGP1 と BGP3 の 4 色を 2 フレームだけ $7FFF に置換(16 B、約 60 M-cycle)

7. 楽曲と譜面

7.1 収録 5 曲

#ファイルタイトル(逐語)BPMパターンorder 長行数譜面
0new_sunny_step_dpcm.jsonサニーステップ [DPCM版]12024256あり
1new_neon_midnight_dpcm.jsonネオン・ミッドナイト [DPCM版]12869576あり
2new_groove_circuit_dpcm.jsonグルーヴ・サーキット [DPCM版]11648512あり
3new_crimson_overdrive_dpcm.jsonクリムゾン・オーバードライブ [DPCM版]152511704あり
4new_twilight_stars_dpcm.jsonトワイライト・スターズ [DPCM版]9657448なし(エンディング専用)

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)
#BPMNES speed88GB speed88GB f/row行数総フレーム実時間NES 実時間
0サニーステップ120192019117.464842561,91132.00 s32.0 s
1ネオン・ミッドナイト128180017927.000005764,03267.51 s67.5 s
2グルーヴ・サーキット116198719777.722665123,95466.20 s66.2 s
3クリムゾン・オーバードライブ152151615095.894537044,15069.48 s69.5 s
4トワイライト・スターズ96240123899.332034484,18170.00 s70.0 s

7.3 譜面

スキーマ(assets/charts/charts.jsonNES と同一ファイル):

jsonc
{ "version": 1,
  "songs": { "<stem>": { "title": "…", "moves": [0,1,0,2,] } } }

moves1 拍 1 エントリの整数配列(0=REST, 1=L, 2=R, 3=U, 4=D, 5=A, 6=B)。

#拍数ノーツ数内訳 REST/L/R/U/D/A/B
0サニーステップ644321/9/9/8/7/7/3
1ネオン・ミッドナイト14410242/21/20/16/20/14/11
2グルーヴ・サーキット1289137/30/18/9/11/13/10
3クリムゾン・オーバードライブ17612848/33/23/27/19/14/12
4トワイライト・スターズ0(未作成)0

moves 配列は 1 バイトも変更しない。 GBC 版で変わるのは speed88 のみ。

拍ターゲット(絶対フレーム、16bit)の事前計算式:

python
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_rhythmsong_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 と同一)

ランク条件
Stotal_miss == 0 かつ total_good == 0(全 PERFECT)
Atotal_miss == 0(GOOD あり)
Bhits*5 >= total*3(正答率 60% 以上)
Chits*5 >= total*2(正答率 40% 以上)
Dそれ未満

hits = total_perfect + total_goodtotal = 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 に残っていた TIKTOKGBC 版では最初から SNS に置換する。置換はこの 1 箇所のみで、他の文字は 1 字も変えない。

全 50 行(プロローグ 12 + 会話 8 + エンディング 30)が 20 タイル以内(1 文字 = 1 タイル。かな・カナ・英数・記号すべて 8×8)。最長 = 18 タイル8ビットの せかいに すいこまれた! / でんしせかいから だっしゅつ せよ! / はじめての バイトだい うれしいなー)。この計測値は tools/test_text_pages.py が毎回再計算し、20 を超えたらビルドを落とす。表の数値と食い違ったらテストの出力を正とする。

10.2 プロローグ(4 ページ × 3 行)

ページ本文タイル
11じょしこうせいの アイは12
12いつものように SNSを12
13みつづけていた7
21ふしぎな こうこくを タップすると17
22スマホが ひかりだして11
238ビットの せかいに すいこまれた!18
31もとのせかいに もどるには13
32ダンスで しちょうりつを あげて16
33バズらなければ ならない12
41みるだけだった アイは11
42みようみまねで おどりだす13
43でんしせかいから だっしゅつ せよ!18

原文(NES 版)は 1-2 が いつものように TIKTOK を TIKTOKSNS の置換に伴い後続の半角スペースも詰めて SNSを とする。これは原典の別行 みんなも SNSを みるだけでなく(エンディング 11-1)と表記を揃えるため。改行位置・語順・句読点は一切変えない。

10.3 会話デモ(3 ページ)

ページ本文タイル
11あなたがしんじんのアイね12
12さっそくダンスのしかたをおしえるわ17
21リズムにあわせてわたしと12
22おなじうごきをしてちょうだい14
23じゅうじキーとA,Bボタンで14
24おどるのよ5
31うまくできてるわね9
32では、さっそくほんばんスタート!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
12THE END7

エンディング 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 が選曲画面に。成功時は白フラッシュ + 歓声ノイズ
タイトルで ← / → / SELECTNORMAL ⇄ EASY 切替(HUD 行 1 col 17-19NRM / ESYMODE_ADDR = $9830 は col 16 の空白から始まる 4 タイルを書く)
会話中 SELECTBGM をその場で切替。会話には譜面もカウントインも無いので restart_pending を経由せず即座に鳴らし直す。曲の選び方(曲 4 をスキップして 0 へ)は本番中の SELECTsong_next_charted を共有する
本番中 SELECT次の曲で演奏やり直し(restart_pending で次フレームに遅延実行)。曲 4 はスキップして 0 へ
チュートリアル中 SELECT残りを省略して最終セリフへ
START(ゲームオーバー / クリア時)リスタート

ポーズで snd_silence を呼んではならない。 snd_silenceNR52 = 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_FRAMESAUDIENCE_ANIMATION_FRAMES ではない)と NOTE_SPEED_88NOTE_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_ADDRAUDIENCE_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)
S4SRAM セーブの将来追加本版はセーブなし$0147 = $19)で確定。追加する場合は $19 → $1B + $0149 = $02 のヘッダ 2 バイト変更別 issue
S5Web トラッカー/譜面メーカーの GB APU 版作らない。既存 5 曲の JSON をそのまま使うため作曲作業が発生しない曲を増やす段階
S6NOTE_SPEED_88 の最終値(1.5 px/f)確定: 1.5 px/f・96f。 子 issue #6 のプレイテスト(PyBoy 出力で判断)で採用した。ノーツ間隔は最密の曲 3 でも 35.4 px あり、視認・判定とも成立する。64f × 2px への退避案は破棄決着済み(issue #6)