# Формат ресурсов Prince of Persia (Apple II, оригинальные исходники 1989)

Источник — официально опубликованные Джорданом Мехнером исходники
(`Prince-of-Persia-Apple-II/`, 6502-ассемблер). В отличие от DOS-версии, здесь
формат восстановлен **напрямую по коду**, а не по догадкам о байтах —
уверенность высокая везде, где указана ссылка на конкретный файл/строки.

---

## 1. Формат уровня (`01 POP Source/Levels/LEVEL0`…`LEVEL14`, 2304 байта)

Файлы уровня — это побайтовый дамп структуры `blueprnt`, которая грузится по
фиксированному адресу `$b700` (`EQ.S:28`) и объявлена как `dum blueprnt` в
`EQ.S:258-266`. Никакого отдельного заголовка файла нет — это чистый образ
структуры в памяти:

| Поле | Размер | Смещение в файле | Описание |
|---|---|---|---|
| `BLUETYPE` | 720 Б | 0–719 | 24 экрана × 30 тайлов: тип объекта/тайла |
| `BLUESPEC` | 720 Б | 720–1439 | 24 экрана × 30 тайлов: доп. байт состояния объекта |
| `LINKLOC`  | 256 Б | 1440–1695 | Таблица связей нажимных плит/дверей, часть 1 |
| `LINKMAP`  | 256 Б | 1696–1951 | Таблица связей, часть 2 |
| `MAP`      | 96 Б  | 1952–2047 | 24 экрана × 4 байта: граф соседних экранов |
| `INFO`     | 256 Б | 2048–2303 | Метаданные уровня: старт Кида, стражников и т.д. |

Сумма: 720+720+256+256+96+256 = **2304** — точно совпадает с размером файла,
что подтверждает: это чистый дамп структуры, без обёртки.

### 1.1 Сетка тайлов (`BLUETYPE` / `BLUESPEC`)

Каждый экран — ровно **30 тайлов** (10 столбцов × 3 ряда): подтверждено
таблицами `BlockTable`/`BlockEdge` (`TABLES.S:74-154`) и логикой перехода
между экранами в `CTRLSUBS.S:218-234` (при переходе через край экрана
`tempblockx` меняется на ±10, `tempblocky` — на ±3).

Функция `CALCBLUE` (`GRAFIX.S:1757-1784`) вычисляет для экрана 1–24:
`BlueType = blueprnt + (screen-1)*30`, `BlueSpec = BlueType + 24*30`,
используя таблицу `Mult30` (`TABLES.S:131-140`).

Байт `BLUETYPE` упакован битовыми полями (`EQ.S:484-486`):

```
бит 7-6: secmask  (%11000000) — назначение не установлено по доступному коду
                                (возможно, служебное поле редактора)
бит 5:   reqmask  (%00100000) — флаг "необходимая опорная плитка"
                                (проверяется в BREAKLOOSE, MOVER.S:395-397)
бит 4-0: idmask   (%00011111) — тип тайла/объекта, 0-29
```

Перечень 30 типов объектов (`MOVEDATA.S:8-37`):

```
0  space          8  pillarbottom   16 exit            24 window2
1  floor          9  pillartop      17 exit2           25 archbot
2  spikes        10  flask          18 slicer          26 archtop1
3  posts         11  loose          19 torch           27 archtop2
4  gate          12  panelwof       20 block           28 archtop3
5  dpressplate   13  mirror         21 bones           29 archtop4
6  pressplate    14  rubble         22 sword
7  panelwif      15  upressplate    23 window
```

Проверено вручную на дампе начала `LEVEL1` (`00 00 00 21 01 21 21 21 34 34
33 33 21 23 00 34 14 14 14 34 14 34 34 2e 23 0b 01 21 34`) — например,
`0x33 → id=0x13=19 (torch)`+reqmask, `0x34 → id=20 (block)`+reqmask —
декодирование по таблице сходится чисто.

`BLUESPEC` — доп. байт, чья семантика зависит от типа тайла (единой схемы
нет, разбирается объект-специфичным кодом):

- **gate** (дверь, `FRAMEADV.S:2222-2234`): на диске — маленький enum (1 =
  начинает открытой сверху, 2 = снизу, …), который через `initsettings`
  (`FRAMEADV.S:22-23`, диапазон `gminval=0`..`gmaxval=188`, из
  `MOVEDATA.S:56-57`) при инициализации уровня превращается в живой счётчик
  "высоты двери" 0–188.
- **loose** (шаткая плитка, `FRAMEADV.S:2224-2237`): при инициализации всегда
  принудительно обнуляется, независимо от значения на диске.
- **flask** (зелье, `FRAMEADV.S:2226,2239-2246`): значение×32 выбирает
  цвет/тип зелья.
- **spikes** (шипы, `MOVER.S:365-382`, константы `spikeExt=5, spikeRet=9` в
  `MOVEDATA.S:45-46`): 0 = безопасно/убраны, 1–8 — кадр анимации
  выдвижения/втягивания, `$FF` = навсегда заклинило (тело наколото).
- **pressplate/upressplate** (нажимные плиты, `MOVER.S:425-464`,
  `FRAMEADV.S:2059-2098`): значение — это **индекс в цепочке связей**
  `LINKLOC`/`LINKMAP` (см. ниже); младшие 5 бит `LINKMAP` по этому индексу
  одновременно служат счётчиком таймера плиты (0–31), определяющим
  состояние "поднято/опущено".

### 1.2 `LINKLOC` / `LINKMAP` — граф триггеров (нажимные плиты → двери и т.п.)

Два параллельных массива по 256 байт кодируют цепочки "нажатие плиты X →
сработать объект на экране S, блок B". Восстановлено из `MOVER.S:506-537`
(цикл `trigger`) и `MOVER.S:1549-1581` (`gettimer/chgtimer/getloc/
getlastflag/getscrn`):

```
LINKLOC[i]:  бит 7      = флаг "последнее звено цепочки"
             биты 6-5   = младшие 2 бита номера целевого экрана
             биты 4-0   = номер целевого блока (0-29); $FF = "никуда не привязано"

LINKMAP[i]:  биты 7-5   = старшие 3 бита номера целевого экрана
                          (вместе с LINKLOC биты 6-5 → полный номер экрана 0-31)
             биты 4-0   = таймер обратного отсчёта плиты (0-31, значим только
                          по индексу самой плиты)
```

`BLUESPEC` плиты хранит индекс `i` её *первого* звена; `getlastflag` идёт
вперёд (`inc linkindex`), пока не встретит бит 7 в `LINKLOC`. Сверено на
`LEVEL1`: байт по смещению 1440 (`0x89 = 10001001` → флаг конца цепочки,
целевой блок 9) и параллельно байт по смещению 1696 (`0x60 = 01100000` →
старшие биты номера экрана) — согласуется с этой раскладкой. Заполнены
реально используемые уровнем звенья, остальное — "мусорные" повторяющиеся
байты-заполнители.

### 1.3 `MAP` — граф соседних экранов

24 записи × 4 байта = 96 байт: для каждого экрана (1–24)
`MAP[(scrn-1)*4 + 0..3] = левый, правый, верхний, нижний соседние экраны`,
читается через `GETLEFT/GETRIGHT/GETUP/GETDOWN` (`CTRLSUBS.S:244-274`,
индексация `MAP-4..MAP-1,x` при `x = scrn*4`). Экран `0` зарезервирован как
"нет экрана" (проверка `beq ]rts` в этих же процедурах).

### 1.4 `INFO` — метаданные уровня (256 байт, база = смещение файла 2048)

Объявлено как `dum INFO` в `EQ.S:272-288`:

| Смещение (от начала INFO) | Поле | Размер |
|---|---|---|
| 0 | "число экранов + 1" (используется в `SETINITIALS`, `SUBS.S:1441-1445`) | 1 |
| 1–63 | резерв/не используется | 63 |
| 64 | `KidStartScrn` | 1 |
| 65 | `KidStartBlock` | 1 |
| 66 | `KidStartFace` (направление; при загрузке инвертируется XOR `$ff`, `SUBS.S:1516-1518`) | 1 |
| 67 | заполнитель | 1 |
| 68 | `SwStartScrn` (стартовый экран меча) | 1 |
| 69 | `SwStartBlock` | 1 |
| 70 | заполнитель | 1 |
| 71–94  | `GdStartBlock[1..24]` — стартовый блок стражника на экране; **≥30 = "стражника нет"** (`AUTO.S:1832-1834`, `SUBS.S:1677-1679`) | 24 |
| 95–118 | `GdStartFace[1..24]` (86 = "стражника нет", см. `ShadFace cmp #86` по всему `AUTO.S`) | 24 |
| 119–142 | `GdStartX[1..24]` — пересчитывается заново из блока при старте уровня, значение на диске почти не используется (`SUBS.S:1674-1690`) | 24 |
| 143–166 | `GdStartSeqL[1..24]` | 24 |
| 167–190 | `GdStartProg[1..24]` — "программа"/поведение ИИ стражника | 24 |
| 191–214 | `GdStartSeqH[1..24]` — обнуляется при старте (`SUBS.S:1685-1686`) | 24 |
| 215–255 | резерв/не используется | 41 |

Проверено на `LEVEL1`: байт по смещению файла 0x800 = `0x18`=24 (число
активных экранов = 23+1); по смещению 0x840 — `01 00 ff 00 00 00 ff 1e 1e
11 1e 1e ...` → `KidStartScrn=1, KidStartBlock=0, KidStartFace=$FF,
SwStartScrn=0, SwStartBlock=0`, далее 24 байта `GdStartBlock`, в основном
`0x1e`(30, "нет стражника"), с реальной расстановкой только на экране 3
(`0x11`=17) и экране 23 (`0x06`) — согласуется с уровнем, где всего два
стражника.

### 1.5 Как уровень попадает с диска (важно: имя файла — не игровой механизм)

В рантайме нет чтения "по имени файла LEVELn" — это чисто утилита для
экспорта в этом репозитории. Реально `LOADLEVELX` (`MISC.S:795-809`)
использует фиксированные таблицы по номеру уровня `bluepTRKlst`/
`bluepREGlst` (`MISC.S:776-787`), дающие физическую **дорожку (1-33)** и
**регион (0/1)**, затем `rdbluep` (`MASTER.S:598-616`) вызывает
низкоуровневое чтение `rw18` (`RdGrpErr`) 9 физических групп по 256 байт
(`$b7-$bf`) — 9×256=2304 байта — прямо в буфер blueprint; регион 0/1 выбирает
половину 18-секторной дорожки (два уровня делят одну дорожку). Файлы
`LEVELn` в этом репозитории — реконструкция этого сырого блока для удобства
работы с инструментами.

---

## 2. Формат изображений/спрайтов (`IMG.CHTAB1-7`, `IMG.BGTAB1/2.DUN/.PAL`)

Каждый такой файл грузится целиком по **фиксированному адресу**, заданному
константами `chtableN`/`bgtableN` (`GAMEEQ.S:9-18`):

```
chtable1=$6000  chtable2=$8400  chtable3=$0800  chtable4=$9600
chtable5=$a800  chtable6=$6000  chtable7=$9f00
bgtable1=$6000  bgtable2=$8400
```
— то есть смещения внутри файла один-в-один совпадают с адресами в памяти
после загрузки.

### 2.1 Раскладка контейнера

Восстановлено из заголовка-комментария "Image table format" в `HIRES.S:181-186`,
процедуры разрешения указателя `setimage` (`HIRES.S:263-277`) и
`GETWIDTH`/`PREPREP` (`HIRES.S:283-339`):

```
Смещение 0        : 1 байт  — число изображений в таблице (максимум 127,
                     в образцах встречается 0x7f)
Смещение 1..254    : 127 × 2-байтных little-endian указателей
                     (указатель на изображение N — по смещению 1+(N-1)*2,
                     N=1..127) — АБСОЛЮТНЫЕ адреса в адресном пространстве
                     фиксированной загрузки этой таблицы, указывающие на
                     запись данных этого изображения
Смещение 255       : заполнитель (таблица указателей занимает ровно 256 байт)
Смещение 256 (база+0x100) и далее:
                     последовательно идущие записи данных изображений:
                       байт 0: ширина (в байтах на строку)
                       байт 1: высота (число строк)
                       байты 2..(2+ширина*высота-1): сырые байты пикселей,
                            слева направо, сверху вниз, БЕЗ сжатия
```

Проверено вручную на `IMG.BGTAB1.DUN`: с точной арифметикой индексов из
`setimage` (`Y = image*2 - 1`, `HIRES.S:264-267`) первые ~30 записей дают
строго возрастающую последовательность указателей `0x6101, 0x6133, 0x6159,
0x618b, 0x61c9, 0x61fb, 0x6221, 0x6313, 0x63c9, ...` — указатель
изображения #1 приходится ровно на `bgtable1 ($6000) + 0x100`, то есть точно
на конец 256-байтной таблицы указателей. Это независимо подтверждает и
размер таблицы, и семантику указателей.

**Важно: сжатия в этом формате нет.** RLE/дельта-упаковка (`SngExpand`/
`DblExpand`/`DeltaExpPop`/`DeltaExpWipe` в `01 POP Source/Source/UNPACK.S`)
применяется только к полноэкранным изображениям (титры/пролог/катсцены), но
не к CHTAB/BGTAB — спрайты и фоновые тайлы хранятся как чистые упакованные
байты hi-res/double-hi-res экрана Apple II, без какого-либо RLE или дельты.

### 2.2 Параметры отрисовки (не часть файла ресурса)

При выводе спрайта (`LAY`/`FASTLAY`/`PEEL` и т.д., `HIRES.S:658-1740`)
используются zero-page параметры `PAGE/XCO/YCO/OFFSET/IMAGE/OPACITY/TABLE/
BANK` (описаны в `HIRES.S:155-178`): `OFFSET` (0–6) — горизontальный сдвиг на
под-байтовый пиксель, `OPACITY` выбирает режим совмещения (AND/OR/STA/XOR/
маска-OR) плюс отдельный бит горизонтального зеркалирования (бит 7). Это
чисто рантайм-параметры отрисовки, не хранящиеся в файле ресурса. Точный
механизм барабанного сдвига для `OFFSET` (таблицы `HRTABLES.S`/`YLO`/`YHI`)
не прослежен до конца — при необходимости требует отдельного анализа.

### 2.3 Инструмент DRAZ (авторская утилита создания спрайтов)

В `04 Support/DRAZ` нет исходников самой утилиты DRAZ — только файлы данных
(`PAC.*` — позы персонажей, и уже скомпилированные `IMG.*`), поэтому
внутренний пайплайн DRAZ (как позы превращаются в CHTAB) напрямую не виден.
Формат контейнера выше выведен полностью из кода движка-потребителя, что
является надёжным, но косвенным источником.

Отдельно: в игровой логике списков объектов (`ADDBACK`, `GRAFIX.S:191-214`)
встречается **рантайм-упаковка ссылки на фоновую картинку** в один байт: бит
7 выбирает `bgtable1` или `bgtable2`, биты 0-6 — номер картинки в таблице
(0-63). Это соглашение для внутриигровых списков объектов (`bgIMG` и т.п.), а
не свойство самих файлов CHTAB/BGTAB на диске.

---

## 3. "Главного индекса ресурсов" не существует

В отличие от DOS-версии (см. `docs/MSDOS_RESOURCE_FORMAT.md`), в рантайм-коде
Apple II **нет обобщённого справочника "имя ресурса → расположение на
диске"**. Расположение каждого ресурса зашито напрямую как таблицы
дорожка/группа-секторов прямо в коде загрузчика:

- Уровни: `bluepTRKlst`/`bluepREGlst` (`MISC.S:776-787`), используются
  `LOADLEVELX`/`LOADLEVEL` (`MISC.S:795-809`, `MASTER.S:467-481`).
- Альтернативные наборы фонов/персонажей: `bg1trk`/`bg2trk`/`ch4trk`/`ch4off`
  (`MASTER.S:522-528`).
- Массовая загрузка при старте (chtable1-7, bgtable1-2, seqtable и т.д.):
  прямые вызовы `rw18`/`RdGrp`/`RdSeq` с литеральными hex-списками
  групп-секторов в `MASTER.S:1250-1360` и `BOOT.S:100-118`.

Весь дисковый ввод-вывод идёт через нестандартный низкоуровневый драйвер
`rw18` (`rw18 = $d000`, `EQ.S:11-12`; папка `02 POP Disk Routines/RW1835`),
реализующий нестандартный формат **18 секторов/дорожку** (вместо 16 у
стандартного DOS 3.3) — этим объясняется, почему регионы уровня (9×256Б)
идут парами на одной физической дорожке. Символические имена
`chtableN`/`bgtableN` в `GAMEEQ.S` — ближайший аналог "индекса ресурсов", но
они связывают ресурс с **фиксированным адресом в ОЗУ**, а не с положением на
диске; связь с диском — отдельная, вручную сопровождаемая таблица,
сопоставленная с ресурсом лишь порядком вызовов загрузчика.

---

## 4. Что ещё не восстановлено (открытые вопросы)

- Точное назначение бит `secmask` (%11000000) в `BLUETYPE` — не встречено
  использование в доступном игровом коде (возможно, поле только для
  редактора уровней, не читается движком).
- Механизм барабанного сдвига `OFFSET` для суб-байтового позиционирования
  спрайта по X (`HRTABLES.S`) — не прослежен в деталях.
- Внутренний формат авторских файлов `PAC.*` инструмента DRAZ (как позы
  скелетной анимации превращаются в растровые кадры CHTAB) — исходники DRAZ
  отсутствуют в репозитории, можно только косвенно восстановить по
  результату (уже скомпилированным `IMG.*`).
