Skip to content

Latest commit

 

History

History
193 lines (147 loc) · 7.22 KB

File metadata and controls

193 lines (147 loc) · 7.22 KB

07 — Playfields

Channel 4 of ldata.rtr carries the whole visible world of this game: 71 CLUT7 backgrounds, each with its own palette and its own code.

python tools/cdipf.py list           # geometry and stream offsets
python tools/cdipf.py png OUT/       # render all 71

The full table is in notes/playfield-inventory.md.

Two parallel streams on one channel

Channel 4 is tagged two different ways. Its VIDEO sectors — 5,890 of them — are one continuous record of raw pixels. Its DATA sectors — 438 of them — are 71 records, one per picture, carrying everything that is not pixels.

The pixels never stop and the descriptors never mix with them. A single channel number, two submodes, two streams.

The descriptor record

Seventy of the 71 records are 12,288 bytes (six Form 1 sectors); one is 36,864. The layout is the same in all of them:

0      u32     number of palette entries -- 128, on all 71
4      384     the CLUT: 128 entries of R, G, B
388    'IDAT'
392    u32     picture size in bytes
2048   'CPL0'
2052   u32     9,260 -- the size of what follows
2056   u32     156  -- header size, giving 38 offset slots
2060   38*u32  offsets, of which exactly 24 are used
2216   9,104   the entries those offsets point into
11464  824     zero

IDAT and CPL0 are the only two four-character tags on the disc that survive a check; everything else that looks like a tag is pixel data that happens to be uppercase ASCII.

Geometry

The IDAT value takes exactly three values across the 71 pictures:

IDAT Geometry Count
92,160 384 × 240 24
184,320 768 × 240 17
276,480 1,152 × 240 30

Every playfield is 240 lines tall and one, two or three CD-i screens wide. 384 × 240 is the CD-i normal-resolution NTSC frame; the wide ones are what scroll_world scrolls across.

Confirm the width independently by autocorrelating the pixel stream: the line pitch shows as a sharp peak at 384 for the narrow pictures and 1,152 for the wide ones, with weaker harmonics at 768 and 1,152.

Pictures follow one another in record order and each starts on a sector boundary, so the three widths cost 40, 80 and 119 sectors:

24 x 40 + 17 x 80 + 30 x 119 = 5,890 sectors = 13,688,360 bytes

which is the video stream exactly, to the byte. That arithmetic is the proof — render on any other assumption and the pictures shear apart a little more with each one.

The 71 TRIGGER sectors in the video record are near these boundaries but not on them; the deltas between triggers run 33–40, 77–83 and 119–125 sectors rather than a clean 40, 80, 119. They are cue points for the application, not structure.

The pixel format

Raw CLUT7. One byte per pixel, no header, no compression, no line padding.

The proof is negative and complete: across 13.6 MB of pixel data, not one byte has bit 7 set. The maximum value anywhere is 127. RL7, the run-length coding used elsewhere on this disc, signals a run by setting bit 7, so this cannot be RL7; and a compressed format that never uses half its byte values is not a compressed format.

A playfield is a framebuffer. The file is the picture, and the game's job is to get it into video memory.

Palettes

384 bytes, 128 entries of R, G, B, at offset 4 of each descriptor.

  • All 71 palettes are distinct. No two playfields share one.
  • Each uses 112 to 127 distinct colours out of 128, averaging 115.
  • Entry 0 is #FFFFFF on all 71 — white is the transparency key on this disc, where the soccer disc used #00FF00.
  • Index 0 appears in the pixel data of only 6 of the 71 pictures, which is what you would expect of a reserved index.

convert_palette and install_palette in the symbol table are what consume these.

CPL0 — 24 subroutines per playfield

The second half of every descriptor record is not data.

Every one of the 71 records carries a CPL0 block of exactly 9,260 bytes: a 156-byte header giving 38 offset slots, of which exactly 24 are used, in all 71 records without exception. Follow any of those 24 offsets and the first two bytes are 48 e7MOVEM.L with a register mask, the standard 68000 subroutine prologue emitted by a C compiler:

48e7 3fc0        MOVEM.L D2-D7/A0-A1,-(SP)
242f 002c        MOVE.L  (44,A7),D2
262f 0030        MOVE.L  (48,A7),D3
2042             MOVEA.L D2,A0
2243             MOVEA.L D3,A1
...

24 of 24 entries, in every one of the 71 playfields, begin with 48e7. Entry sizes run from 126 to 494 bytes.

So each playfield ships with roughly two dozen compiled 68000 subroutines, loaded off the disc alongside its background and its palette. This is a level script compiled to machine code rather than interpreted — which is why the symbol table contains check_world_hotspots, go_find_action and go_find_hero_action but no bytecode interpreter, and why 71 levels of behaviour fit in a game whose executable is 133 KB.

The first thirty-two bytes of the first four entries are byte-identical and they diverge at offset 32, so the entries are instances of one function shape with different bodies — the output of a compiler run over 24 similar source functions, not 24 hand-written ones.

What the 24 slots mean — whether they are a fixed set of hooks in a fixed order — is open.

The other channels of ldata.rtr

Channel Contents Bytes
0 one data record: a 128-entry palette, IDAT = 309,760, CPL0 = 31,442 3,317,760
0 134 CLUT7 video sectors 311,416
1 71 data records of 2 to 5 sectors 501,760
2 two chained PAC0 archives, then 300 KB more 313,344
3 one MAC0 archive 2,220,032
4 the 71 playfields 13,688,360

Channel 0's single record has the same shape as a playfield descriptor — the palette, the IDAT tag, the CPL0 tag — but with IDAT = 309,760 and a CPL0 block three times the size. This is the permanent set: the HUD, the subscreen, the fonts, the things that never unload.

Channel 1's 71 records line up one-to-one with the 71 playfields.

PAC0 on channel 2 is a chained archive format and it verifies cleanly:

0   'PAC0'
4   u32   total payload size
8   u32   header size, 108 -- giving 24 offset slots
12  24*u32 offsets, monotonically increasing, all below the total

Two archives sit back to back at offsets 0 and 7,686 — 7,578 + 108 and then 4,751 + 108 — and both carry exactly 24 entries, the same count as CPL0. The 18-sector playfield descriptor opens with a third PAC0 of 23,423 bytes, also 24 entries.

MAC0 on channel 3 has the same first eight bytes but a 144-byte header, and its offset table is not monotone. It has not been read.

Rendering one yourself

from PIL import Image
import cdilib, cdipf
d = cdilib.Disc()
vid, recs = cdipf.load(d)
m = cdipf.describe(recs[0])
im = Image.frombytes('P', (m['width'], 240), vid[0:m['size']])
p = [0]*768; p[:384] = list(m['pal'])
im.putpalette(p); im.convert('RGB').save('pf00.png')

Playfield 0 is 1,152 × 240: a shoreline with a stilt hut on the left, a rope bridge, a cave mouth, and an erupting volcano on the right. Playfield 70 is 384 × 240 and is the throne room at the end of the game.