Skip to content

STG — stage files & the UI ​

Prerequisite: The DFile container format and The image codec.

A STG ("stage") file holds full-screen screens and the on-screen UI — anything that isn't a walkable room. The deck-plan map, the inventory screen, the mini-game boards, and the bottom UI band all come from STG files, each paired with its scripts.

Reference implementation: engine/src/df/stg.ts (decoding) and engine/src/runtime/stage.ts (StageController — the runtime). This page covers the file; the runtime behaviour built on it — lifecycle conventions, the overlay stack behind the inventory, the exact click order — is in Stage & UI at runtime.

Flats: full-screen background images ​

A STG's screens are called flats — a flat is a 512×384 full-screen image plus its scripts and click regions. MAIN.STG is the always-present stage: its flat image is the background the UI band sits on, and its main script defines gotospecial — a core travel routine used game-wide.

What's in the file ​

WhereContents
Palette @ 56the colour table for this file
Main script @ 44i32 container ref: the stage's main script, the one that carries openstage
Stage name @ 2104the stage's own name, which is not always its filename — currentstage()'s answer
Flat table @ 2124one 46-byte record per flat — pointers to that flat's script, image, and click-logic containers
Image containersflat images, in the common image codec

Each flat, when shown, fires openflat / closeflat events on its script, mirroring the openscene/closescene pattern for rooms.

One flat record, field by field:

A flat record (STG, DreamFactory 4)
46 bytes in container 0's flat table, one per full-screen UI layer
16 B per row · 46 B total
Hover a block to see what it is for; click to pin it.
Mapped from engine/src/df/stg.ts (readStgFile)

And a whole stage — LANG.STG, the language chooser this project wrote from nothing, which is also the one DF file that ships in this repository. Two flats, and the picture each one draws is nearly all of it:

Loading the map of lang.stg…

The main script, and where it is named ​

Container 0 names it, at +44 on a v4 .stg and +32 on a v1 .flt — right after the screen size, which sits at 0x28/0x1c for the same reason (v1's header runs 12 bytes earlier, and its palette 20). SET and SHP name their main scripts the same way (#325).

The corpus alone cannot show this — container 1 IS the main script in all 388 shipped .stg/.flt — so it is read from both engines. TI.EXE's stage parser 0x4451b0:

0x4452b0: add  eax, 0x838          ; = c0 + 2104, the stage name
0x4452c0: mov  edx, [eax - 0x80c]  ; = c0 + 44, the main script
0x4452c8: mov  [edi + 0x18], edx
0x4452ea: call 0x4385f0            ; ...and open that container

and DF.EXE's 0x4010a0 takes v1's the same way, between the two screen-size checks it makes either side of it:

0x401107: mov  eax, [eax + 0x20]   ; = c0 + 32, the main script
0x40110d: cmp  word [ebx + 0x1c], 0x200   ; 512 wide
0x401124: cmp  word [ebx + 0x1e], 0x180   ; 384 high
0x40113e: add  ebx, 0x824          ; = c0 + 2084, the stage name

That also locates the v1 stage name: 20 bytes earlier than v4's 2104 (reading at 2104 lands on the first flat record and returns two NULs). All 20 of Dust's .flt files carry a real one, and six do not match their filename — CREDITS.FLT is "cred.flt", INVEN/HIST.FLT is "DBhist.flt", SALGAMES.FLT is "cardflats", UNDER/SNAKE.FLT is "puzzle", NEW.FLT is "new". Three of Dust's own currentstage() tests ask for those names, and are unreachable without them: HOUSE.PRP's avatar forwards a click to the flat's handleit() on "scorp" and "yunnibox", and NEW.FLT arms its cash-update loop on "new".

How STG fits the render stack ​

From engine architecture, the screen is built back-to-front, and STG is the bottom layer:

┌────────────────────────────────┐
│  SET view (top 512×264)         │  ← composited in when "set visible"
├────────────────────────────────┤
│  UI band: menu · held item ·    │  ← STG flat image + house.shp props
│  watch                          │
└────────────────────────────────┘
  • The flat image is the background for the whole 512×384 frame.
  • When a set is visible, the room view is composited into the top 512×264.
  • Props (from SHP) are drawn on top, z-ordered by depth (propdist — more negative = nearer the front; inventory items sit in front of the band).

A full-screen flat (the map, the inventory) calls setvisible(false) so the room view is hidden and the flat fills the screen.

The UI band and inventory ​

The bottom band's furniture — the lifesaver menu button, the currently held item, the watch — are house.shp props drawn over MAIN.STG's flat, at fixed screen positions.

The inventory works by swapping the stage: the boot routine transtoflat("inven1.stg") fades out, saves the current stage on a stack, switches to the inventory flat, and shows every owned item via that item's moveyoself handler. transfromflat reverses it. Adding an item (addinven, from inven.shp) puts it in Frank's hand (owner "frank", shown in the band). The stack — and everything else that has to happen for a mid-puzzle overlay to come back to the exact screen it covered — is the runtime's job: the overlay stack.

Stage builtins and the click order ​

Scripts drive the stage layer through the openstagefile / closestagefile, gotoflat, currentstage / currentflat, setvisible family — the full list is in the builtin reference. Who gets a click first on a stage (props vs regions vs scripts) has real subtleties; see the click order.

Writing a stage ​

Every format here has a builder (the write path); a stage needs two of them:

  • engine/src/df/stg-build.ts — buildStgFile({ palette, main, flats }) lays out container 0 (palette at 56, flat table at 2124), the main script named at 44, and per flat a script, an encodeFrame image and a click-logic container of 32-byte region records.
  • encodeScript in engine/src/df/script.ts, with the assembler in script-asm.ts on top of it — source text in, the 8-byte segment stream plus its string pool out (see the script container).

So a flat's buttons can be authored as script:

code mousedown()
	global taootlang
	taootlang = "de"
	gotoflat("wait")
endcode

The port's own language chooser (public/lang.stg, built by npm run mklang -w taoot) is exactly this and nothing more — two flats, six click regions, a compiled handler each — and the engine opens it with openstagefile like any CyberFlix stage. What it does with the choice, and why a script global rather than a builtin, is in Languages & the chooser.

The same builder makes the fixture the stage editor's tests are checked against, so read → edit → write is verified against a file the library itself produced.

The same file in Dust — .FLT ​

Dust calls a stage a flat file and its boot says openstagefile("new.flt") — the same builtin, a renamed extension — and the model underneath is the same one: flats, each with a script, a picture and a table of clickable regions. So one reader takes both, and NEW.FLT maps with the same labels a .STG does:

Loading the map of new.flt…

What differs is where the fields sit (palette at 0x24 rather than 0x38, a 28-byte flat record rather than 46) and what v4 added: a v1 flat record carries no condition, width or height, so a v1 flat is reported at the stage's own screen size — which is what it is, 512×384, for every flat on the disc.

Not just UI — mini-games too ​

Mini-game boards live in STG files as well. The blackjack game's real winner() logic, for example, runs out of the original BLKJACK.STG script, which also serves as an interpreter check (8/8 rule checks against the shipped binary).

  • the stage editor (/editors/stages.html) — every flat of a STG in a browser page, its art with the click regions drawn over it, and the flat/region names and rectangles editable.
  • taoot/tools/mapjumps.ts — reads MAP.STG's regions to recover where each deck-plan hotspot jumps to.

Next: sound — Audio (TRK / SFX / 11K / SND).

Docs licensed GPL-3.0 (the decoder is ported from DFET). Game data © CyberFlix — not included.