Research 05 / Data architecture

一套 loader,不能只服務第一關。

若每遇到錯圖就建立一份 GBA-only 對照表,Episode 越多,維護成本越接近無限。資料層必須把原始檔當作真實來源。

為 C 移植碼提供熟悉的 I/O

GBA ROM 是 memory-mapped,但 OpenTyrian 原始碼習慣以檔名、offset、 record 與 loader 讀資料。專案的 ROMFS 在 build 階段把 stock data 連同 path metadata 打包;runtime stat 取得直接指向 ROM 的 span,避免把整個檔案搬到 RAM。

30current ROMFS entries
4.285 MiBROMFS image
0 copieswhole-file staging

原始格式各自解讀

  • MUS:曲目 offset、LDS pattern 與 patch 資訊。
  • SHP/Sprite2:壓縮圖形 bank 與 frame/component layout。
  • PIC:前端背景、palette 與 source presentation。
  • HDT:武器、port、ship、option、shield 與 enemy 定義。
  • LVL:事件、enemy pool、map shapes 與三層 map。
  • levelsN.dat:加密 episode script、跳轉、難度、路線與選項。

邏輯先問 loader「原版資料是什麼」,再由 GBA adapter 決定如何顯示。 這也是修正 Episode 2/4 錯素材時的重要界線:問題應在通用 bank/index 規則解決,不在關卡中加入一個特殊 if。

允許的 build-time 轉換

「讀原始資料」不等於所有工作都必須在 16.78 MHz runtime 做。 可接受的轉換以資料不變性與完整覆蓋為原則:

  • 完整 Sprite2 bank 的無損 RLE 展開,而非只列第一關看過的 frames;
  • 前端完整 source stamp 集合,而非每個選單狀態手繪替代圖;
  • 字型由 stock glyph 無損轉為 GBA tile layout;
  • 音樂轉成 Maxmod soundbank,但曲目與流程仍由原始 song ID 驅動。
Build-time 資產是原始資料的通用表示;關卡事件仍決定「用什麼」, 不會被預轉換工具接管 gameplay 規格。

可驗證的 payload 去重

ROMFS 不以「目前沒有呼叫」為刪除條件。只有另一份 cartridge payload 已完整承接,且 build 能做無損 round-trip 時,才排除重複 原始流。目前 SND、Christmas SND/SHP、TYREND.ANM 與 34 份 newsh*.shp 共省下 5,420,450 bytes 的重複 payload;原始檔仍保留在 vendor/ 作為可重建來源。

Halloween Ramble 隨完整 41 首 PC 曲目保留; tyrianc.shpvoicesc.snd 則承接 可驗證的 Christmas 模式。這些資料都不能在 payload 精簡時誤刪。

加密腳本的 O(1) section seek

levelsN.dat 是 encrypted Pascal-string stream。舊 seek 每次從開頭逐行解密到第 N 個 *。新版直接掃 record length, 只解 encrypted first character 來辨識 section marker,保存 ROM offset。

四個 stock scripts 實際有 24–51 sections;快取約 272 bytes, 不需要 generated route catalog。跳轉、Full Game/Arcade 與 difficulty 判定仍由原 parser 執行。

完整性契約

ROMFS build 產生 entry count、metadata CRC、payload CRC、manifest CRC 與 image SHA-256。ROM 啟動時做 probe/self-test;完整 matrix 再走過四個 Episode 共 62 個 playable LVL sections,確保資料架構不是只對示範關卡成立。