対象: Tile Texture Reuse Codec(TTRC)
期間: 2026-07-26
最終main: encoder e139 / player p97 / eed9d704262820d2ef940b57a87487aaab193659
全編認定録画: 30fps profileはe139/p96、15fps profileはe139/p97
H40の代表4プロファイルを、解像度、fps、cold cap、フィルタ、palette、ditherなどの目標画質設定を下げずに、DEBUG HUDでFAILしない状態へ戻した。
最終的な主原因は、単純なPrgBuf枯渇でも、Main-CPUのrun数でも、ADPCM decode単体の遅さでもなかった。旧encoderは、ある配送slotがその時点までにCD 1xで物理的に読める量を超えても、後続の軽いslotでpadを減らし、映画全体では配送量の帳尻を合わせられるscheduleを許していた。しかし、一度余分に費やした実時間は後から取り戻せない。その表示遅延でcontinuous CD producerがplayback consumerより先行し、有限のAPPLY control queueがguardへ達し、control sectorをpumpできなくなった後にMSF gap/reseekが発生していた。
修正は場当たり的にcold capや画質を下げるものではない。controlとPrg payloadの全prefixを、routing上限だけでなく、fpsから決まる累積CD 1x時間にも収め、有限PrgBufのexact scheduleでrate_lead_peak = 0を必須にした。これにより、全4プロファイルが元の品質設定とcold capのままPASSした。
| Profile | 認定版 | 出力 | cadence | cold cap | 実測最大cold | 最大coldのframe数 | HUD最終結果 |
|---|---|---|---|---|---|---|---|
| H40 Bad Apple | e139/p96 | 320x224 | 30fps / N=2 | 180 | 180 | 1,330 | PASS: S00 D00 R00 M01 J04 |
| H40 Sonic Jam OP | e139/p96 | 288x200 | 30fps / N=2 | 190 | 190 | 142 | PASS: S00 D00 R00 M01 J00 |
| H40 Machi OP | e139/p97 | 320x152 | 15fps / N=4 | 480 | 480 | 8 | PASS: S00 D00 R00 M03 J00 |
| H40 Machi ED | e139/p97 | 320x224 | 15fps / N=4 | 380 | 380 | 399 | PASS: S00 D00 R00 M03 J02 |
各認定版で、full sim/pack、startup込みのlossless DEBUG recording、first-loop全frame HUD OCR、timeline、hudline、mixlineを確認した。under=0、rate_lead peak/end=0/0であり、目標品質設定は変更していない。最終main p97では4 profileすべてのDEBUG discを再pack/buildし、player memory contractも再確認した。
今回許容したのはencoder内部の判断やsim結果の多少の変化であり、目標品質の低下ではない。
変更しなかったもの:
- 解像度とfps
- cold cap
- crop、fit、resize filter
- palette方式、segmented palette、dither
- active tile数やcodecの品質目標
- 各profileが意図するprefetch方式
この区別が重要だった。「sim結果を一切変えない」とすると正しいschedule修正まで妨げる。一方、「再生できればよい」としてcold capや解像度を下げると、原因を隠すだけになる。
初期のBad Apple e135/p92では、coldを下げてもFAILは消えなかった。
| cold cap | HUD |
|---|---|
| 190 | FAIL: S=0x31, R=0x10 |
| 180 | FAIL: S=0x1D, R=0x08 |
| 170 | FAIL: S=0x0B, R=0x03 |
負荷を下げるほど症状は減ったが、production baselineの180でもFAILし、170でも残った。この時点でcap探索を止めた判断は正しかった。症状が連続的に減ることは、capが根本原因である証拠ではない。壊れた容量・期限モデルでも、入力負荷を下げれば発症頻度は減る。
一般化すると、既知の品質点で以前通っていたものが通らなくなった場合、まず回帰として扱う。品質ノブを動かすのは原因修正後の上限調査であり、回帰修正の代用ではない。
途中でslot-locality、Hamiltonian path探索、run数削減も候補に上がったが、runを適用するのはMain-CPUである。今回疑っていたのはSub-CPU側のCD pump、audio、control/payload copy、handoffだったため、run数最適化は直接の対策ではない。
この切り分けは重要である。
- Main-CPU: VRAM更新、run適用、表示側の処理
- Sub-CPU: CD sector pump、stream分配、ADPCM decode/wave write、Word RAM handoff
- Encoder: 将来の配送slot、control/payload量、有限bufferを事前に計画
見かけ上「CPU負荷」と呼べても、どのCPUがどのmemory domainで行う仕事かを確定しない限り、最適化案の評価はできない。
PCM_DEC_BUF 1,536 bytesをWord RAMから安全なSub PRG-RAMへ移し、同一streamでplayer-only A/Bを行った。
Sonicでは非デグレを確認し、J最大値が20 KiBから19 KiB、Mainから見たSub handoff待ちWの平均が約0.37 scanline減った。一方、ADPCM phase AはHUD分解能内で不変だった。Bad AppleのFAILは解消せず、Sは減らなかった。
したがって、この移動は共有Word RAM trafficを減らす正しい最適化だが、Bad AppleのFAIL原因を直接解いたものではない。
next-index table 2,848 bytesとnibble LUT 256 bytesをSub PRG-RAMへ移した。Sonicは全2,714 frameを完走し、HUD gateはPASSした。しかしAの改善は約0.123 ms単位のHUDでは測定できなかったため、5,696-byte signed-delta tableを動かすStage 2には進まなかった。
ここから得た原則:
- 理論上access数が減っても、実測可能な効果がなければ次段を自動で積み重ねない。
- 正しさ、非デグレ、効果量は別々に判定する。
- PrgBufを減らす施策は、その価値が測定できてから行う。
関連issue:
HUDのCはCD待ちを示すdiagnosticであり、画像破綻やdeadline missそのものではない。処理が早く終われば、次のdataを待つ時間が増えてCが大きくなることもある。
そのためCをgate判定から外し、代わりにmin / mean / median / maxを常に報告するようにした。Aも同じ4統計を出す。
今回の判定原則:
- S/D/R/M/Jなど、再生破綻や期限違反へ直接つながる値をgateに使う。
- Cは原因分析用に保存するが、単独でFAILにしない。
- C増加を「改善」とも「悪化」とも即断せず、S/D/R、映像、cadence、J、A、Wと合わせて読む。
whole-movie timelineのPrgBufは固定scaleなので、数KiB残っていても視覚上ゼロに見える。そこでplayerのlive signed balance QをHUDへ追加した。
Bad Apple p95/p96で確認した値:
- player live Q最小: 64 patterns、2 KiB
- packer model最小: 32 patterns、1 KiB
- literal zero、logical underflow: なし
PrgBuf低水位とSには時間的な相関があったが、最初のSが発生した時点ではmodel PrgBufは約77.5 KiBまで回復していた。したがって、「PrgBufがゼロになったからSが起きた」という説明は否定された。
低水位を起点に調査したこと自体は有効だったが、低水位は直接原因ではなく、同じ重い区間で生じたcadence遅延を見つける入口だった。
追加した主なdiagnostic:
- Q: live signed PrgBuf balance
- G: SubがCDC pump opportunity外にいた最長interval
- B: APPLY 30 KiB guardによりcontrol pumpを拒否したframe
- K: MSF-gap recoveryに由来するSの累積数
Bad Apple診断では:
- G min/mean/median/max = 253 / 275.896 / 276 / 284 tick
- S付近にGの異常spikeなし
- Bは12 frame
B at f2172 -> S at f2173を含め、B直後のS transitionを10件確認- K最終値とS最終値が一致
- CDC_TRN retry exhaustionは0
これにより、「Subがpump path外で長時間止まった」という仮説は否定された。一方で、APPLY guardがcontrol sectorを拒否し、その後MSF gap/reseekへ進む因果関係が見えた。
HUDは最初から完璧な設計でなくてよい。ただし、仮説を一つずつ否定できるfieldを追加し、追加した値が再生制御を変えないことが重要である。
SEGA-CDのCD 1xは75 sectors/sである。
- 30fps、N=2では、1 game frameあたり約2.5 sector
- 15fps、N=4では、1 game frameあたり約5 sector
旧plannerはrouting上の最大slot量を主なcapacityとして使い、あるslotがそのframeまでのCD 1x時間を超えても、後続slotのpadを減らして最終的なrate debtを0に戻せればfeasibleとしていた。
これはbyte accountingとしては合っていても、deadline accountingとして誤りである。例えば30fpsで5 sector読むslotは約2 frame分のCD時間を使う。次のslotを軽くして映画全体の平均を1xへ戻しても、先に失った表示時間は戻らない。
実際の連鎖:
- 重いslotが、その時点のCD 1x cadence allowanceを超える。
- fixed-cadence表示が遅れ、同じgame frameが余分なscanoutで表示される。
- CD pumpはcontinuousなので、producerがplayback control consumerより先行する。
- APPLY queueが30 KiB guardへ達する。
- 次のcontrol sectorをpumpできない。
- MSF gapを検出し、reseekがS/Rとして現れる。
最初のmodel低水位区間f1789..2142では、通常N=2より77 scanout余分に表示されていた。これはbufferの見た目ではなく、失われた実時間の直接証拠だった。
全prefixについて、control sector数とPrg payload sector数の合計を次の小さい方へ収める。
- routing tableが許す累積sector数
- fps/cadenceから、その時点までにCD 1xで物理的に読める累積sector数
さらに、有限PrgBufのexact scheduleについて:
under = 0- control/payload deadlineを満たす
- ring peakが物理容量以下
rate_lead_peak = 0
を必須にした。以前のrate_lead_end = 0だけでは不十分である。
この修正はCPU作業量を推測するmodelではない。物理CDがdeadlineまでに配送できるsector数という、直接検証可能で保守しやすい制約である。CPU作業量を初期scheduleへ組み込む案は、正確なmodelが必要なので別課題として保留した。
修正commit:
診断途中では、APPLYを増やしてPrgBufを減らす案も合理的に見えた。実際、有限queueのheadroomを増やせば発症を遅らせられる。
しかし今回は、producer leadの起点が不正な配送scheduleだった。ここでAPPLYだけを増やすと、誤ったscheduleをより大きなbufferで吸収することになる。必要容量の計算漏れを後付けbufferで隠してはいけない。
判断順序:
- そのslot/prefixは物理deadlineまでに配送可能か。
- producer/consumerの正しい最大leadはいくつか。
- 正しいscheduleでも有限queueに必要なheadroomが足りないか。
- 3が証明された場合だけ、PrgBufとの再配分を検討する。
fps別に必要なresource量は異なる。
| cadence | control block上限 | PCM samples/frame | PrgBuf delivery cap | live jitter |
|---|---|---|---|---|
| 30fps / N=2 | 372 B | 736 | 398 KiB | 20 KiB |
| 15fps / N=4 | 740 B | 1,472 | 378 KiB | 40 KiB |
15fpsは1 frameあたりのaudio/control量が大きく、low-rate CDC poll pathとrouting条件も異なる。30fps buildが通ったから15fpsも通るとは限らない。
一方、実装を「15fpsだけの例外」にしてはいけない。resource量、jitter、Supply、routing、buffer ceilingはfpsから導出し、15/24/30fpsを同じsource of truthで構築する。
4 KiBはMega-CD BIOSのSub-CPU命令全体に固有の絶対上限ではない。現行boot imageがdata track先頭16 sector、32 KiB内でSP sourceを0x7000..0x7FFFへ置く構成のため、BIOS boot moduleとして4 KiBになっている。
最初にboot moduleへextensionを単純連結した方式は、Sonicのframe 499で確定的に停止した。また0x08600..0x097FFはstartup BIOS read後も保持される領域ではなかった。
採用した方式:
- boot時だけ必要な追加命令をHEADER preload内の既存paddingへ格納する。
- streaming開始前に安全なSub PRG-RAMへcopyする。
- size、hash、load address、exec address、entry point、memory overlapをbuild-time検証する。
- BIOS boot module自体は4,096 bytes以内に保つ。
p94では88-byteのADPCM table copy extensionをこの方式へ移した。p97では同じ基盤へboot-onlyのrouting/ring/APPLY初期化を追加し、15fps diagnostic buildを次のように収めた。
- resident Sub image: 4,128 Bから4,080 B
- preload extension: 176 Bから216 B
- HEADER sector数: 増加なし
- PrgBuf、APPLY、WordBuf、sim判断: 変更なし
修正commit:
HUDだけでは「playerで何が起きたか」は見えるが、「encoderがそのframeに何を計画したか」が見えない。timelineだけでは、実再生がどこで遅れたかが見えない。
今回からfull DEBUG recordingごとに次を恒久化した。
- timelineを生成・公開
- hudlineを生成・公開
- 同じwhole-movie frame axisへ合わせたmixlineを生成・公開
- layout JSONとGist receiptを録画の隣へ保存
mixlineにより、model PrgBuf低水位、実Q、extra scanout、B、Sの順序を一枚で追えるようになった。原因調査では、単一meterの最大値より時間軸上の前後関係が重要である。
- 6,576 frames、cold max 180、該当1,330 frames
- S00 / D00 / R00 / M01 / J04
- C: 0 / 0.178 / 0 / 2
- A: 62 / 65.991 / 66 / 66
- G: 253 / 275.945 / 276 / 284
- timeline
- hudline
- mixline
- analysis video
- playback video
- 2,714 frames、cold max 190、該当142 frames
- S00 / D00 / R00 / M01 / J00
- C: 0 / 0.058 / 0 / 2
- A: 62 / 65.994 / 66 / 66
- G: 257 / 275.946 / 276 / 283
- timeline
- hudline
- mixline
- analysis video
- playback video
- 2,293 frames、cold max 480、該当8 frames
- S00 / D00 / R00 / M03 / J00
- C: 0 / 0.000 / 0 / 0
- A: 182 / 207.196 / 188 / 242
- G: 361 / 377.244 / 377 / 378
- timeline
- hudline
- mixline
- 3,998 frames、cold max 380、該当399 frames
- S00 / D00 / R00 / M03 / J02
- C: 0 / 0.001 / 0 / 1
- A: 182 / 206.065 / 188 / 242
- G: 361 / 377.225 / 377 / 378
- timeline
- hudline
- mixline
完了issue:
同種の再生破綻では、次の順序を崩さない。
-
Data layer
実際のHEADER.DATとBODY.DATを全frame走査し、writer/reader契約をbyte単位で確認する。 -
Logic layer
Python referenceで表示状態、pattern source、control/payload順、ring推移を照合する。 -
Physical deadline
最終平均だけでなく、全prefixがその時点のCD 1x時間、DMA時間、queue容量へ収まるか確認する。 -
CPU ownership
Main、Sub、encoderのどこが仕事を所有しているかを確定する。別CPUの最適化を混ぜない。 -
Assembly and memory
build binaryを逆アセンブルし、address、size、hash、bank ownership、overlapを確認する。 -
HUD evidence
仮説を否定できるfieldを追加し、full first-loop TSV、timeline、hudline、mixlineで前後関係を見る。 -
Cross-cadence regression
30fpsだけでなく15fps、必要なら24fpsも、各々の実resource量でbuild・full playbackする。 -
Modelを疑う
simが成立するのに実再生だけが繰り返し壊れる場合、hardwareを品質ノブでsimへ合わせるのではなく、sim/packerが物理deadlineを正しく表しているか問い直す。
今回最も大きかった教訓は、「容量が足りる」と「期限までに届く」は別であることだった。映画全体でCD 1x平均に収まり、PrgBufがunderflowせず、最終rate debtが0でも、途中の1 slotが使った実時間は取り戻せない。
また、HUDで見えた相関をすぐ原因と決めず、Q、G、B、Kを追加して仮説を否定したことで、PrgBuf枯渇という見かけから、APPLY back-pressure、さらにその起点であるfixed-cadence配送制約の欠落まで辿れた。
品質を下げずに直すには、playerを速くすることだけでなく、encoderが最初からhardwareへ配送可能なscheduleだけを作ることが必要である。