, palette: int, ground: list, walls: list, ceiling: list, objects: list>}> */ public array $rooms; /** @var list */ public array $sprites; /** @var list, end: string, next?: int}> */ public array $anims; /** @var list> 4 bytes per object type */ public array $types; /** @var list>, end: string}> */ public array $programs; /** @var list lines per tick while falling */ public array $fall; /** @var list> (room, x, y) of the 4 suit modules */ public array $modules; /** @var list> (room, x, y) of the recharge cells */ public array $cells; /** @var list (room, x, y) of the suit at the start */ public array $armour; /** @var list> (room, x, y) start places */ public array $start; /** @var list the original's messages (Spanish edition) */ public array $messages; /** @var list> 14 palettes: 16 pens -> hardware colour */ public array $palettes; /** @var list> hardware colour -> RGB */ public array $hw; /** @var array{glyphs: list>, chars: array} */ public array $font; public static function load(string $path): self { if (self::$instance === null) { $data = json_decode((string)file_get_contents($path), true); if (!is_array($data)) { throw new \RuntimeException("$path: not found or not JSON (run tools/extract/extract.sh)"); } $w = new self(); foreach (['rooms', 'sprites', 'anims', 'types', 'programs', 'fall', 'modules', 'cells', 'armour', 'start', 'messages', 'palettes', 'hw', 'font'] as $key) { $w->$key = $data[$key]; } self::$instance = $w; } return self::$instance; } public function exit(int $room, string $side): ?int { return $this->rooms[$room]['exits'][$side] ?? null; } /** Pen 1 or 5 on a line of the playfield's first 8 (x0..x1 in mode 0 pixels): the ceiling test of #6714. */ public function ceilingAt(int $room, int $line, int $x0, int $x1): bool { $screen = $line - self::TOP; if ($screen < 0 || $screen >= 8) { return false; } $row = $this->rooms[$room]['ceiling'][$screen]; for ($x = max(0, $x0); $x <= min(159, $x1); $x++) { if ($row[$x] === '1') { return true; } } return false; } /** Pen 3 or 7 on the line under (x0..x1 in mode 0 pixels, 0-159): the ground test of #68B4. */ public function groundAt(int $room, int $line, int $x0, int $x1): bool { $screen = $line - self::TOP; if ($screen < 0 || $screen >= 128 || ($screen & 7) !== 0) { return false; } $row = $this->rooms[$room]['ground'][$screen >> 3]; for ($x = max(0, $x0); $x <= min(159, $x1); $x++) { if ($row[$x] === '1') { return true; } } return false; } /** * A pixel of pen 7 in screen byte $byte (0-79) between lines $from and $to: a wall (#67B6 scans the * column Tal walks into). The rooms' walls are hex strings of 80 bits per line. */ public function wallAt(int $room, int $byte, int $from, int $to): bool { if ($byte < 0 || $byte > 79) { return false; } $rows = $this->rooms[$room]['walls']; $digit = $byte >> 2; $mask = 8 >> ($byte & 3); for ($y = max(0, $from - self::TOP), $last = min(127, $to - self::TOP); $y <= $last; $y++) { if (hexdec($rows[$y][$digit]) & $mask) { return true; } } return false; } /** RGB of a pen in a room palette. */ public function rgb(int $palette, int $pen): array { return $this->hw[$this->palettes[$palette][$pen]]; } /** The type's 4 bytes (byte 0: bit 7 object, bit 6 enemy, bit 5 + low bits a handler; byte 1 bits 7-5 hits). */ public function type(int $type): array { return $this->types[$type] ?? [0, 0, 0, 0]; } }