In-game HUD
The Glide build draws the in-game overlay (candle, active item, prompt text, compass, power-up icons and logbook icon) over the 3D scene on a virtual 640×480 screen, scaled to the output size.
Shared rules
- A HUD bitmap is drawn with its top-left corner at the configured anchor
plus the frame’s draw offsets (the signed fields at frame-record offsets
0x06and0x08, see GXA). Every frame of the HUD atlases has offsets(0, 0), so each INI coordinate is the bitmap’s top-left corner. The power-up slots and the logbook icon use the anchor alone. - Palette index 0 is transparent.
- HUD text uses
FONTS\REDSEL.FNTwith the[3dfx] font_selpair (shipped255,255), the same glyph, shadow and colour rules as an NPC subtitle. Text is drawn as stored, ignores[system] disable_text, and wraps only when a glyph would cross x = 640. - Timers below count game ticks, the unit of
rtx_pickup_override_timeand the subtitle timers; the tick advance per frame is the FPS-derived frame scale, so ticks run atnormal_frame_rateper 18 BIOS timer ticks (about 12.14 per second with the shipped value 12) while the frame rate stays at or abovemin_frame_rate. Animations marked per rendered frame advance once per drawn frame, independent of ticks. The 3dfx renderer opens Glide at 60 Hz with two colour buffers and swaps with a swap interval of 1, so each frame waits for one vertical retrace: these animations advance at most 60 times per second and otherwise at the frame rate the machine reaches.max_frame_ratelimits only the tick computation, not drawing. - Lockout retains the low 16 bits of the value passed to
LockoutPlayer; the engine also resets it to 0, for example after player death. Each element below tests it differently.LockoutPlayersets the selected player’sattr_no_node_combatto one only when the retained lockout value is exactly one. Other values clear that attribute on the selected player and every registered actor that currently has it set. EveryLockoutPlayercall clears the saved highlighted-choice text used by the SOUP topic menu. Values other than one also clear the HUD prompt-override timer. The function returns zero as its SOUP scalar result. attr_player_typeuses the SOUP386.DEF valuesply_cyrus0,ply_gremlin1,ply_crossbow2.
| Element | Bitmap | Frames | Size | Anchor keys | Shipped anchor |
|---|---|---|---|---|---|
| Candle | SYSTEM\SNUFF.GXA | 81 | 50×98 | candle_xco, candle_yco | 12, 8 |
| Active item | ITEM.INI bitmap_file (SYSTEM\PICKUPS.GXA) | 66 | 119×83 | pickup_xco, pickup_yco | 12, 386 |
| Compass | SYSTEM\COMPASS2.GXA | 32 | 73×65 | compass_xco, compass_yco | 546, 396 |
| Power-up slots | [system] game_bitmap (system\powerup.gxa) | 2 | 39×61, 54×61 | game_xco1/2, game_yco1/2 | 576, 6 and 576, 96 |
| Logbook icon | SYSTEM\SCROLL.GXA | 32 | 73×58 | logbook_xco, logbook_yco | 540, 20 |
All anchor keys are in SYSTEM.INI [system].
Candle
The candle shows the player’s health. Maximum health is a fixed 100 (no INI
key). Health is attr_health (attribute index 3 in SOUP386.DEF), always
clamped to 0..100.
Admission and lifetime
The common candle routine requires a loaded candle bitmap, a separate enabled flag, and rendering to be enabled. It checks these conditions before servicing F8, advancing the candle animation, or processing its show timer. A failed condition skips all of that work, independently of candle mode and lockout. The console’s render on/off control supplies the rendering condition.
Candle initialization releases any previous candle bitmap, loads the requested
bitmap, sets the enabled flag, and resets the shown level and animation step.
The HUD initializes it with SYSTEM\SNUFF.GXA. Candle teardown releases the
bitmap and clears both its reference and the enabled flag.
Level
With integer arithmetic and truncating division:
d = ((100 - min(health, 100)) * 256) / 100
L = (d * 11) >> 8
Level L | Health |
|---|---|
| 0 | 91–100 |
| 1 | 82–90 |
| 2 | 73–81 |
| 3 | 64–72 |
| 4 | 55–63 |
| 5 | 46–54 |
| 6 | 37–45 |
| 7 | 27–36 |
| 8 | 18–26 |
| 9 | 9–17 |
| 10 | 1–8 |
| 11 (snuffed) | 0 |
Frame layout
Levels 0–10 own six frames each: 6L to 6L + 4 are the flicker cycle and
6L + 5 is the transition frame between level L and L + 1 (frames 0–65).
Frames 66–80 are the snuff-out animation.
Animation
The candle keeps a shown level S (unset when a play session opens) and a
step counter C (initially 0). Every rendered frame of a running world,
whether or not the candle is visible:
- If
Sis unset,S = L. - If
L < 11:C = C + 1, wrapping to 0 above 4. Then- if
S < L: show6S + 5, thenS = S + 1,C = 4; - if
S > L:S = S - 1, show6S + 5,C = 4; - otherwise show
6S + C.
- if
- If
L = 11: ifS = 11,C = C + 1; otherwiseS = 11,C = 0(no stepping through levels).Cis clamped to 14 and frame66 + Cis shown, holding frame 80.
The candle therefore steps one level per rendered frame through transition frames and flickers in a five-frame loop. Frames on consecutive rendered frames:
| Situation | Frames |
|---|---|
| Spawn at health 100 | 1, 2, 3, 4, 0, 1, 2, … |
| Level 0, health drops to 50 | 5, 11, 17, 23, 29, 30, 31, 32, 33, 34, 30, … |
| Level 5, health restored to 100 | 29, 23, 17, 11, 5, 0, 1, 2, … |
| Level 5, health drops to 0 | 66, 67, …, 80, 80, … |
| Snuffed, health set to 100 | 65, 59, 53, …, 11, 5, 0, 1, … |
Quick sword
KEYS.INI quick_sword_key reads the held key on each world update. Lockout 1
or selected-player attr_player_type 1 disables this handler. Drawing or
sheathing a sword prevents another sword operation until that transition ends.
When the player is holding a weapon, the key requests sheathing. Otherwise it uses the first owned item in the order 1, 13, 75. Items 1 and 13 become the selected item before use; item 75 is used without changing the selection. An empty search leaves the selection unchanged.
The key remains held after handling. Holding it through a completed draw can therefore request sheathing without another press. Item use retains its normal eligibility and torch precedence: a burning torch is doused instead of drawing the requested sword, after the selection has changed to item 1 or 13.
Visibility
[screen] candle_mode selects the visibility mode (shipped 2):
| Mode | Candle |
|---|---|
| 0 | Shown only while the show timer runs. |
| 1 | Always shown. |
| 2 | Always shown, with the health number below it. |
In every mode the candle is drawn only while lockout is exactly 0. The show
timer T (ticks) is set:
- to 25 when the player’s health changes (damage, healing, health script
functions) while
Tis below 50 (some health script functions only whileTis at most 25); - to 25 when the player draws or sheathes the sword;
- to 50 on every frame while the sword is drawn and lockout is 0;
- to 50 while the inventory screen is open (which draws the candle itself), and to 0 when it closes.
After the candle is drawn, a timer with 0 < T < 50 loses the frame’s ticks,
stopping at 0. A value of 50 never decays; it is replaced by the events above.
T does not decay while lockout hides the candle, and it is stored in save
games. In mode 0 the candle is thus visible while the sword is drawn, for 25
ticks after a health change or a draw/sheathe, and in the inventory.
Health number
In mode 2 the player’s attr_health is drawn as a decimal integer. It is
centred, by the subtitle centring rule, on
x = candle_xco + width / 2 − 5 (integer half) with its top at
y = candle_yco + height; with shipped values, centred on x = 32 at y = 106.
After drawing the candle bitmap, the number pass saves the current text settings, selects the standard GUI font with text-colour indices 1 and 0, and enables text before formatting and painting the number. It reads the selected player’s current health attribute independently of the cached health used to advance the candle animation. It then restores the previous font, colour indices, shadow settings, text cursor, case flags, text viewport and text-disable flag. The selected renderer palette is outside this saved text state; pushing and restoring these text settings does not select a palette.
Mode key
F8 (scan code 66, not configurable in KEYS.INI) cycles candle_mode
0 → 1 → 2 → 0 and immediately writes the new value to SYSTEM.INI
[SCREEN] CANDLE_MODE. No notice is shown.
Active item
The icon is frame bitmap[x] of ITEM.INI bitmap_file for the selected item
x, drawn at (pickup_xco, pickup_yco). The HUD does not use
bitmap_selected_file. All 87 shipped items define bitmap[x].
pickbox.gxa and pickblob.gxa are not used: no box or blob is drawn behind
the icon.
Count
When the selected item’s count is greater than 1, the count is drawn as a
decimal integer, left-aligned with its top-left at
(icon_x + width − 32, icon_y + 8): (99, 394) with shipped values. At a new
game the gold count is 53 (player_total[2] = 52 plus one from
start_item_list), so the gold icon shows 53.
Inventory ownership
The item system initializes each count from player_total[x], then adds one
of each ID in start_item_list through the acquisition rules. Counts and the
selected item belong to the game session and survive ordinary world changes.
HaveItem returns the current player count. AddItem returns zero. A positive
addition is capped by both total[x] - count and player_max[x] - count.
It succeeds only when all recursively required items are owned. A successful
addition performs the configured drop/add lists before incrementing the count.
A nonpositive addition or an exhausted cap leaves the count unchanged.
SelectItem selects the requested defined item without checking ownership or
visibility; the next item-overlay update repairs an exhausted selection.
Selection
- The initial selection is ITEM.INI
start_item_select(shipped 1, the sabre), or the first item when absent. - KEYS.INI
[input] prev_key/next_keystep once per press through items in ID order, wrapping, and skipping items withhide[x] = 1or a count of 0. - Cycling is disabled while the sword is drawn or
attr_holding_weaponis set; the icon stays visible. Drawing or sheathing does not change the selection. Disabling rendering suppresses item artwork and text, but does not disable otherwise-eligible item cycling. - When the selected item’s count drops below 1, the selection moves to the next valid item; with none, nothing is selected and nothing is drawn.
Visibility
Evaluated each frame in this order:
- Lockout 1: neither icon nor prompt is drawn, and the timers below hold.
- Activation hold-off
A > 0:Aloses the frame’s ticks, stopping at 0, and nothing is drawn. - A pending activation prompt replaces the icon (see below).
attr_player_type1: nothing is drawn.- Otherwise the icon and count are drawn.
Lockout values other than 1 do not hide the icon.
Inventory screen
KEYS.INI inventory_key opens a modal screen over the last world frame. The
world update latches the key and opens the screen on its next update. The
screen pauses the game clock and captures the current frame before waiting
for the key to be released. It then installs the inventory text settings.
The saved frame contains tightly packed 16-bit pixels copied from the display
rows using the source stride; capturing it does not select a renderer palette.
The clock resumes on exit. Each screen update polls input once and then waits
for the next 18.2 Hz BIOS tick; the navigation and exit checks read that poll.
A mouse binding therefore reads the button release
between two successive updates.
Each admitted update draws the candle with its show timer pinned to 50 and advances its existing animation state once. Lockout can hide the candle without stopping that state update. The clock wait admits a drawing pass when the sampled tick changes; a jump of several ticks still produces one pass, without catching up the skipped ticks.
Inventory bitmap resources
Inventory bitmap initialization releases the previous mask, border and arrow
images, then loads SYSTEM\INVMASK.GXA, SYSTEM\INVBORDR.GXA and
SYSTEM\INVARROW.GXA in that order. All three loads use the same palette
output buffer. Each successful load overwrites that buffer, leaving the arrow
palette after the final load.
The bitmap loader expands the first decoded frame’s palette by shifting each of its 768 bytes left by two bits. With a palette output buffer, it copies those bytes to the buffer. With no output buffer, it instead calls the palette installation service for 256 entries. Inventory bitmap initialization uses the copy branch for all three resources.
Bitmap image preparation keeps six texture slots arranged as three columns and two rows. The column and row origins are spaced 256 pixels apart. Slots outside the image dimensions contain no texture. Each populated slot receives a separate buffer of indexed pixels, copied row by row using the full image width as the source stride and the region width as the destination stride. A 640 by 480 image therefore produces widths 256, 256 and 128 in each row, with heights 256 and 224.
Each region requests an indexed texture and supplies its tightly packed pixel buffer to the texture population service. The resulting texture identifiers are stored in the image’s six-slot array. The preparation routine assigns consecutive image identity values to populated regions in slot order.
Item list
The list holds every item with hide[x] = 0 and a nonzero count, in ID
order. It is empty while the selected player’s attr_player_type (attribute
105) is 1. The cursor starts on the selected item. When the selected item is
not in the list, the cursor keeps its value from the previous opening (zero at
the first opening). An empty list sets the cursor to −1.
Navigation
The list is a grid of four columns. Right adds 1 to the cursor, left subtracts 1, down adds 4 and up subtracts 4. The result is clamped to the list without wrapping. Left, right, up and down are the KEYS.INI turn and move bindings. A move that changes the cursor plays sound effect 13, and further moves are ignored for the next three ticks while any direction is held. Releasing every direction ends the delay.
Three rows are visible. The first visible row moves up when the cursor is above it and down until the cursor is within the three visible rows. The first visible row is retained between openings.
Exit
The screen begins closing when inventory_key, Activate (key_d), the
walk/run modifier (key_a), Escape or Enter is held. Closing first waits for
the inventory key to release, then for Escape to release. It then samples
Activate and Enter once. If either is held and the cursor is valid, the cursor
item becomes the HUD active item; otherwise selection is
unchanged. A confirmation released during those first waits does not select,
and a confirmation first pressed during them can select.
After that selection test, closing waits for the walk/run modifier, Activate and Enter to release, in that order. Each wait completes once; a key pressed again after its wait has completed does not restart that earlier wait.
Exit selects resource group 31 and releases group 28, then processes deferred texture residency changes. A texture also owned by another group retains that ownership. A texture whose last group is released is freed even when repeated lookups had raised its reference count.
The saved 16-bit frame is copied back using the destination display stride, then its buffer is freed. Exit calls display finalization, clears the candle show timer, frees the temporary item list, restores the saved text state and resumes the clock, in that order. Restoring the saved frame copies its pixel values directly; it does not select a renderer palette.
The optional rectangle drawn during display finalization uses black RGB vertex colour. Its colour setup and rectangle drawing do not select an indexed renderer palette.
The rectangle helper swaps both corner pairs when the second Y coordinate is smaller than the first. It does not independently reorder reversed X coordinates. It rejects rectangles wholly above or left of the viewport, or whose first corner lies beyond the right or bottom viewport limit. It clamps negative first coordinates to zero and second coordinates to the viewport limits, then adds one to the second X and Y coordinates before emitting two triangles. A point on a viewport limit therefore reaches one coordinate unit beyond that limit.
The rectangle’s graphics setup and post-draw reset send the following Glide API arguments in table order. The reset installs fixed values rather than restoring captured caller state. Setup sets chroma-key mode to zero; the reset leaves that setting unchanged.
| API | Rectangle setup arguments | Post-draw reset arguments |
|---|---|---|
grChromakeyMode | 0 | No call |
guColorCombineFunction | 2 | 6 |
grDepthMask | 0 | 1 |
grDepthBufferFunction | 7 | 1 |
guAlphaSource | 1 | 0 |
grAlphaBlendFunction | 1, 5, 4, 0 | 4, 0, 4, 0 |
The queued-text enable flag belongs to the queue’s buffer lifecycle. Initialization first tears down the previous queue, allocates and zeroes 66 buffers of 256 bytes each, then enables processing. Teardown frees and clears all buffer pointers when the enable flag is exactly one, then clears the flag.
For each buffer, initialization stores the allocation result and passes it directly to the 256-byte zero-fill operation. It does not test for a null result or retry the allocation before that operation.
The buffer-fill path repeats the low byte of the fill value across the requested byte range and returns the original destination. A zero byte count skips all destination access. For a positive count, the underlying fill routine reads the first destination byte before writing and has no null-pointer guard.
Startup initializes queued-text suppression from [system] disable_debug_text
in SYSTEM.INI, separately from the ordinary disable_text value. When the
startup q option query succeeds, it sets both values to one and skips those
configuration reads. These are initial values; suppression is mutable engine
state, separate from the queue’s allocation flag. Queue initialization and
display finalization do not restore suppression from saved text state.
Display finalization also processes queued text. When that processing is enabled,
it temporarily enables text output. Unless drawing is suppressed, it visits up
to 66 queued buffers in order and stops at the first empty buffer. The console
output route submits each buffer with %s; the ordinary text route uses %s\n.
It then clears all 66 buffers, 256 bytes each, even when drawing is suppressed
or stops at an empty buffer, and restores the incoming text-disable value.
The queue processor does not save and restore the remaining text state or select
a renderer palette itself; text-service calls are separate operations.
Ordinary queued text starts at the current text cursor and leaves that cursor updated after each message. Line feeds return X to the current line origin and advance Y by the font line height. This route uses the current font, temporarily selecting its linked small font when a configured text viewport is narrower than 640 pixels. It restores the incoming font after each message. The text sink does not install a separate text style or select a renderer palette; shared glyph services handle text colour.
The text viewport-mode setter retains its full 32-bit input value. The setter leaves the display width, display height and selected renderer palette unchanged. Ordinary text treats zero as the fixed 640-pixel viewport mode and any nonzero value as the configured-width mode.
The ordinary route treats message contents as string data: percent characters remain literal. It copies bytes up to the first NUL, appends one line feed, and NUL-terminates the formatted text. An existing trailing line feed is retained, so an additional one follows it.
TAB in ordinary text changes only the horizontal cursor. For a positive text
viewport width W of at least eight pixels, let s = trunc(W / 8). Without
signed arithmetic overflow, the new X is X + rem(X + s, s), where rem is the
signed remainder from division truncated toward zero. This calculation uses
absolute X, without subtracting the line origin, and does not wrap immediately.
At a width of 640 pixels, X values 1, 79 and 80 become 2, 158 and 80 respectively.
Repeated tabs apply the calculation to the updated X. TAB bypasses character
lookup and glyph drawing. NUL ends the formatted text; apart from LF and TAB,
nonzero bytes, including CR, pass unchanged to character lookup.
The ordinary text sink reuses shared formatting storage supplied by the executable data image. It does not allocate or free formatting storage per message. The formatting call receives no destination-size argument.
The buffer-clear wrapper can run optional memory validation before filling. Outside a reentrant validation call, a nonzero byte count first checks the destination address, then the final byte address if the first check returns without an error. These numeric checks enter a diagnostic path for addresses below 16; they do not read the destination bytes. A zero count skips these address checks. If validation returns normally, its return value does not suppress the subsequent fill.
View (key_c) opens the cursor item’s examine view with its
inventory_object_file model. When that view returns a nonzero result, the
inventory screen closes without changing the selection.
Each loop latches View before waiting for the next tick. It updates the cursor and draws the inventory before servicing the pending View request. With a valid cursor, that request is handled before ordinary exit keys are tested, including when View and an exit key are sampled together. After the view returns, the exit test uses the controls remaining after the view’s release wait.
On the screen update that opens View, the inventory drawing pass omits the cursor item’s bitmap, quantity and description. Other visible cells keep their ordinary drawing behavior. The inventory background and health candle are still drawn before Examine opens.
Examine view
The view shows the item’s inventory_object_file model (loaded from INVENTRY.ROB) over the
frozen screen. The inventory screen opens it with mode 1 and the cursor cell’s screen position. The world loop
also contains a mode 0 entry gated by a request flag, item and position words; no code in the
executable writes those words, so no gameplay path reaches it. It pauses the game
clock for its duration, plays sound effect 61 when it opens and 60 when it begins to close, and
waits for the next BIOS tick each update. It returns 0 when the item is not in the list.
Three Euler angles (0..0x7FF, wrapping) turn the model: yaw about Y, pitch about X and roll
about Z, applied to an identity matrix in that order. Offsets are single-precision values. The
model’s extents a, b and c are the values at model offsets 1, 5 and 9 and M is the largest.
The model starts at offset (0, 0, c + 180) and unrotated.
While the view is open, each tick with any direction, A, B or C held applies one step:
| Held | Left / right | Up / down |
|---|---|---|
| none | yaw +0x50 / −0x50 | pitch −0x50 / +0x50 |
B (key_b) | roll −0x50 / +0x50 | depth +80·s / −80·s |
A (key_a) | X offset −40·s / +40·s | Y offset −40·s / +40·s |
The scale s is (depth − c) / (8·M + 180) + 0.1. Depth is clamped to [c, c + 8·M + 180],
and then each offset is clamped to ±2·M·s (the Y offset’s range is scaled by the viewport
height over its width).
The view opens by interpolating a factor from 1 to 0 in steps of 0.15 per tick: the model’s
offset is the target offset plus the fly-in offset times that factor. The screen position that
mode 1 supplies is the cell the item sits in, so the model flies from its cell to the centre. The view
ignores close requests until Activate, View, Enter and Escape are all released after opening.
Pressing View, Activate, Enter or Escape then closes it: the factor rises by 0.15 per tick back
to 1 with the angles interpolating to zero by the shortest way round, and the view returns once it
exceeds 1. The return value is 1 when Activate or Enter was held at the close request and 0
otherwise. The caller waits for View, Activate, Enter and Escape to release before continuing.
A cursor item without bitmap[x] draws no artwork.
Layout
Coordinates are in the 640 × 480 screen. The screen draws, in order:
- A quad from (90, 49) to (550, 433) in colour (10, 10, 10) with alpha 180, blended as source alpha over one minus source alpha, without depth test.
SYSTEM\INVBORDR.GXAframe 0 at (90, 49).SYSTEM\INVARROW.GXAframe 0 at (313, 54) when the first visible row is not the first row.- For each visible item, column
cand visible rowr, withx = 100 + 105candy = 87 + 102r:- the cursor item draws frame
bitmap[x]ofbitmap_selected_fileat(x − 2, y − 2); other items draw framebitmap[x]ofbitmap_fileat(x, y); - a count greater than 1 draws the count in decimal, centred on
x + 84, with its top aty + 10.
- the cursor item draws frame
SYSTEM\INVARROW.GXAframe 1 at (313, 419) when rows follow the visible rows.
Text uses the REDSEL font with a (2, 2) shadow. The cursor item’s name[x]
RTX text is split into lines like the
logbook wrap with a 13-byte step,
replacing a space with a line break. Line n is centred on
x + w / 2, where w is the selected frame’s width (integer half), with its
top at y + 92 − b + trunc(0.9 × n × line_height). b is the
bottom of the font’s a glyph (top offset plus height). An item without name
text, or whose text starts with byte 0x9E, draws INDEX <id> centred on
x + 52 at y + 92 − b.
The candle is drawn with its timer at 50 while the screen is open and the timer
is 0 after it closes. SYSTEM\INVMASK.GXA is loaded with the screen artwork
but not drawn.
Prompt text
A single text line is drawn left-aligned from x = pickup_xco with its top at
y = pickup_text_yco (shipped 12, 436).
Activation prompt
The script function Activate(label) (also used by TorchActivate) offers
RTX entry label as the prompt when lockout is not 1, no dialogue topic menu
is open, the sword is not drawn, no other object holds the activation, the
hold-off A is 0, and no prompt is already pending. An existing
activation owner may offer its own prompt again when the other gates permit it.
When the player’s attr_player_type is 1, the receiving object’s
attr_special_activate must be nonzero. A receiver attached to a rope or in an
IsOnEdge state cannot offer a prompt. IsOnEdge is true for actor states
10 through 19 inclusive. These two tests apply to the receiving object.
The selected player must not be holding a weapon, drawing a sword, or sheathing
a sword. The shared player-lineup result must be zero. Standalone player
initialization clears this result. Each PlayerLineUp invocation stores its
selected alignment helper’s result in the shared value before returning it. Activate requires the
player’s torch-burning counter to be zero; TorchActivate requires it to be
nonzero. The prompt text is the RTX entry’s text; a missing RTX entry does not
activate the object. RTX lookup folds ASCII uppercase letters in the four-byte
key to lowercase. An existing entry with empty text still supplies a prompt and
can activate the object.
The weapon predicates use the item system and the actor’s current animation.
Outside shell-script execution, with the item system initialized and a current
animation present, drawing is actor state 20 or animation group 91; sheathing
is actor state 21 or animation group 129. Combat capability additionally
requires an assigned weapon. A nonzero attr_holding_weapon makes the actor
combat-capable and holding a weapon directly. Otherwise holding a weapon
requires combat capability, the weapon-drawn flag, and no sheathing state.
- If the use key is held when
Activateis called, the activation fires: the function returns true, the receiving object becomes the activation owner, andAis set to 22 ticks, blanking the icon and prompt area. The resolved prompt remains pending even when activation fires.Deactivatealways clearsA; it clears the activation owner only when called on that owning object. - Otherwise the prompt is drawn in place of the icon.
- While a prompt is pending, holding the previous/next item key sets an
override timer to
rtx_pickup_override_time(shipped 24 ticks). While it runs, the prompt is suppressed, the icon is drawn and items can be cycled. It decays by ticks and is cleared when no prompt is pending. - While the dialogue topic menu is open, and in some player states, a pending prompt draws nothing and still hides the icon.
A pending prompt is consumed when its text is drawn, not at the start or end of
every frame. Lockout 1, activation hold-off, prompt override, and early exits
that suppress prompt drawing retain it. The drawing checks use the selected
player’s rope attachment, IsOnEdge, and IsInDeepWater state, as well as the
dialogue topic menu. IsInDeepWater is true for water states 2 and 4.
Disabling rendering with the render off console command also suppresses prompt
drawing without consuming the pending text. render on removes that suppression.
Lockout 1 freezes the activation hold-off. Otherwise a nonzero hold-off is reduced by supplied ticks, saturating at zero, and that update still draws neither prompt nor item. With no hold-off, a nonzero prompt override similarly decays and selects the item path for that update, even when it reaches zero. An absent pending prompt clears the override before these decisions. Drawing a prompt while a previous/next item key is held sets the configured override after consuming the prompt; item cycling is deferred to a later item-path update.
Item name
The selected item’s name[x] text is drawn here only when the item has no
bitmap[x], which no shipped item does. Cycling items does not show the item
name.
Compass
The compass is drawn at (compass_xco, compass_yco) when lockout is not 1,
attr_player_type is not 1, the player owns item 0, and the compass display
flag is on.
The flag starts off, toggles each time item 0 is used, and is stored in save games. Receiving item 0 does not set it. This differs from the ITEM.INI comment that giving the player item 0 “will automatically turn on the compass display”.
Item use retains a consumed-use latch. The item overlay clears it when the
Activate/Attack input is released. An item-use call leaves the latch untouched
when lockout is 1 or the selected player is on an edge, or when the latch is
already set. Otherwise it sets the latch before testing item-system readiness
and the remaining actor gates. Item use requires the selected player, ordinary
rather than shell execution, water state other than 2 or 4, no held weapon,
no torch lighting or dousing transition, and no burning torch. The transition
checks require an initialized item system and an animation state; they recognize
actor states 33/34 or animation groups 149/150. A rope attachment does not by
itself block an explicit UseItem call, and the call does not remove the
attachment. An owned item 0 then toggles the compass flag. An unowned item cannot toggle it. Holding the use input does not
repeat the accepted toggle.
The Activate/Attack key (key_d) uses the selected item for an unarmed selected player: one press
starts one use of the item (a selected sabre is drawn, a selected drinkable item runs its use script
once) and holding the key does not repeat it. Without the key press nothing is used. Item use itself
keeps the refusals above, so a player holding a weapon uses nothing and the same key attacks instead.
A successful Activate or TorchActivate also marks the held action input as consumed.
The selected player skips the quick-key and action-key handlers while that mark and the
held action input are both set. Releasing the action input clears the mark before those
handlers resume. Releasing the activation owner, including at script End, does not
clear this input mark.
The compass flag lives outside the world and the session initializer. Only the item 0 toggle and a startup command-line switch write it, so it survives world swaps and a New Game.
The frame is
frame = ((-(heading + world_compass[N])) mod 2048) >> 6
where heading is the player heading
and world_compass[N] the current world’s WORLD.INI
value, in the same 2048-unit circle. Shipped worlds 3 (2000), 7 (1024),
13 (1536), 15 (1536), 20 (512), 22 (1024) and 23 (1024) set it; it is 0
elsewhere. Turning right increases the frame index.
| Heading | world_compass | Frame |
|---|---|---|
| 0 | 0 | 0 |
| 1985–2047 | 0 | 0 |
| 1984 | 0 | 1 |
| 1024 | 0 | 16 |
| 65 | 0 | 30 |
| 1–64 | 0 | 31 |
| 0 | 2000 | 0 |
| 1000 | 2000 | 17 |
Power-up slots
Two slots show frames of [system] game_bitmap (system\powerup.gxa: frame 0
gauntlet, frame 1 shield) at (game_xco1, game_yco1) and
(game_xco2, game_yco2), slot 1 then slot 2, while lockout is exactly 0.
ShowBitmap(id, frame)requiresid0 or 1 and a valid frame; other values are a fatal error. Ifframeis already shown in either slot, nothing changes. Otherwise it uses slot 1 if empty, else slot 2 if empty, else the slot after the one last chosen (choosing either slot, including an empty one, makes the other slot next). The slot stores(id, frame).UnShowBitmap(id)clears slot 1 if its id matches, otherwise slot 2 if its id matches.- Slot contents are stored in save games.
- The two slot frame indices live outside the world, with
-1meaning empty, together with the alternation state (which slot a new frame takes when both are full; it starts at slot 1). Loading a world releases the slot bitmaps and resets the alternation to slot 1. World entry then rebuilds each slot’s bitmap from its retained frame index, so a power-up survives a world swap. The session initializer (first start and New Game) sets both indices to-1, so New Game empties both slots.
Script logbook entries
AddLog(tag) accepts a four-byte RTX label packed into an integer in
little-endian byte order and returns 0. It checks a 400-slot tag table before
looking up the RTX entry. A matching tag suppresses the call. Otherwise the
first zero slot receives the tag, even when lookup or file opening later
fails. Zero is the empty-slot sentinel. When all slots are occupied, an
unmatched tag is not retained and the call continues.
A found RTX entry appends its text to LOGBOOK.TXT. A missing entry writes
nothing. Once the log file is opened, the engine writes the text, closes the
file and starts the logbook icon without checking the write result. A suppressed
tag or failed file open leaves the icon unchanged.
Logbook icon
When a new logbook entry is written and the icon is not already playing,
SCROLL.GXA plays frames 0–31 once at (logbook_xco, logbook_yco), one
frame per rendered frame, then disappears. It is not hidden by lockout.
Opening the logbook screen stops it.
Manual logbook entries
The installed C action opens a modal Log Entry dialog from the logbook
reader. The default keyboard binding is Alt, configured by key_c[0] in
KEYS.INI; this is the action binding, not the letter C.
Each dialog starts with empty text and accepts at most 64 bytes. Further input bytes are ignored at the limit; deleting a byte permits another byte to be entered. Inserted control bytes count toward this limit. It converts lowercase ASCII letters to uppercase before processing them, with Caps Lock either on or off and with or without Shift. Unshifted Backspace removes the last byte when the text is nonempty. Unshifted Return accepts nonempty text; an empty submission leaves the dialog open. Escape cancels the dialog, including while Shift is held. Tab produces no character.
Shift+Backspace appends a carriage-return byte (0x0D), and Shift+Return appends
a vertical-tab byte (0x0B), subject to the same byte limit. These combinations
do not delete or submit. Caps Lock does not change these control mappings.
Neither inserted control byte draws a visible symbol or starts a new line in the entry dialog. In the reader, vertical tab remains invisible within its record. Carriage return separates physical records when the accepted text is reloaded, so text after it appears on a new entry line.
Acceptance appends 9,, the entered text and CRLF to LOGBOOK.TXT in binary
append mode. It uses the shared logbook writer and its notification behavior,
bypassing the script tag-deduplication table. The writer’s current style is then
reset to 7. The reader reloads the file and selects its maximum scroll offset,
showing the newest text. Cancellation does not invoke the writer.
This operation adds a new record. It does not select, replace or delete an existing entry.
The dialog restores its captured reader background on each redraw and draws
SYSTEM/MINIMENU.GXA at (175, 140) in the canonical 640-by-480 viewport.
Its backing rectangle uses RGB (10, 10, 10) and alpha 125, including the
right and bottom image boundaries. The Log Entry title is centered at
(320, 170) with REDSEL and the normal font style from SYSTEM.INI.
The edited text starts at (200, 200) with REDSEL and the selected font style.
Both use the shared font shadow and colour rules.
In the dialog, an entered carriage return has no visible glyph and advances
the text pen by one pixel without starting a new line.
An underscore cursor is appended to the display copy while its phase is on. The phase starts off. Each newly sampled BIOS tick toggles it only if that tick is divisible by six; missed ticks do not cause extra toggles. The display copy, including any cursor, replaces spaces with newlines by scanning from byte offset 13, excluding the final byte. After a replacement, scanning resumes fourteen bytes later. The stored edit buffer is unchanged. The glyph painter also wraps before a glyph whose width would extend past x = 450, returning to the text’s starting x one font line lower. This pixel wrap applies to unbroken runs as well as the prepared display lines.
Logbook reading layout and navigation
The logbook reads physical lines from its stored text. CR and LF are line separators; after each record, consecutive separators are skipped. A leading character followed by a comma selects the entry style and is excluded from the entry text.
Text wrapping searches for a space starting at byte offset 65. It replaces the first matching space with a line separator, then resumes searching 66 bytes after that replacement. The final byte is never replaced. Long words remain intact. Every entry occupies 12 pixels per wrapped line plus an 8-pixel gap. The first line starts at y = 70 minus the scroll offset.
The reading area is 340 pixels high. The maximum scroll offset is the sum of entry heights minus 340, clamped to zero. Opening the screen initializes the offset to this maximum, showing the newest text.
Home selects offset zero; End selects the maximum. Ordinary up/down scrolling moves 10 pixels per screen update. Page Up and Page Down request the corresponding direction and select a shared 50-pixel step for that update. Home is processed before End, then upward scrolling before downward scrolling. Each direction clamps independently to the available range. Exit is tested after these changes.
Logbook reading screen
The installed log key opens the reading screen and clears the opening press.
The screen uses SYSTEM/LOGPAPER.GXA, tiled vertically with its first copy at
-(scroll_offset % 480) and the next at that position plus 480. Text starts at
x 30, y 70 minus the scroll offset. Negative line origins are skipped; remaining
glyph pixels clip to the viewport. Text retains its case.
The reader suspends world updates. It saves the normal simulation timing state before entry and restores it with a fresh BIOS-clock baseline on return. Its rendering loop does not advance the simulation clock. Existing audio playback is not stopped by the reader.
Home selects the top, End the bottom. Installed up/down bindings scroll ten pixels per BIOS-timer update. Either Page key or the installed A binding selects the shared fifty-pixel step. Page Up also supplies up; Page Down supplies down. The installed log key, Escape or installed D binding closes the reader. Closing waits for those controls to be released before gameplay resumes.
The reader selects the paper file’s own palette (the LOGPAPER.GXA palette, installed
as the file stores it) for both the paper and the scrolls, so the scroll textures take
their colours from that palette and not from REDGUARD.COL. Apart from the colour-key
entries it overrides, that palette is REDGUARD.COL with each channel truncated to six
bits (4 * floor(channel / 4)).
MENU.ROB supplies two instances of SCROLL.3D. Their positions are
(-324,-212,362) and (-324,200,362) with initial rotations (0,512,0).
The rotations apply the second axis, then the first, then the third. Each admitted
up operation subtracts 20 from the third angle, or 100 for the large step; down
adds the corresponding amount. Angles wrap modulo 2048. Home and End do not
rotate the models. The screen uses the menu viewport projection and a zero camera.
Logbook scroll lighting
The reader lights its two SCROLL.3D instances with a light state of its own,
so the loaded world’s ambient, sun and lights have no effect on them. On entry the
reader sets the ambient RGB to (64, 64, 64) and the directional light to white,
(255, 255, 255), along the vector whose components are 16000 times the sine, cosine
and sine of the table angles 256, 256 and 512, that is (11313, 11313, 16000),
normalised. Every drawn frame also registers two white local lights of strength
(255, 255, 255) and radius 6400 at (-390, 230, 600) and (390, 230, 600), in the zero-camera
space the scrolls are placed in. The lights apply to the scrolls only; the paper
is a flat picture.
The scroll model’s face brightness byte is zero and every corner takes its vertex
normal through the model’s indirection table (an authored radial normal in the XY
plane). Corner lighting follows the model lighting
and local-light rules, with the
instance rotation applied to positions and normals. The textured faces then use
Glide’s texture-times-iterated-colour combination, so a channel T lit to level
L becomes floor(T * (L + 1) / 256). The lit level reaches 255, which leaves the
texture unchanged, over most of the lower scroll and darkens the upper scroll to
about half its brightness where its surface turns away from both light positions.
The single byte before a comma is converted to an integer font-table index;
missing prefixes use index 7. The reader follows the font-table entry’s small-font link directly. Index 7 therefore uses FONTNORS.FNT with main alpha
200; index 9 uses FONTSELS.FNT with main alpha 255. Text shadows are disabled here.
Glyph colors come from the font palette’s 6-bit components. Index 9 text shifts them left by two bits. Index 7 text shifts them left by one bit, so its glyphs are half as bright before the alpha blend with the paper.
Reader style table
The style index selects an entry of the engine’s font table. Each entry is a font
with a value pair A,B; B is the main alpha. Entries 0, 3, 4 and 6 are copies of
FONTS\REDSEL.FNT, entry 7 is a copy of FONTS\FONTNORM.FNT and entry 8 a second
copy of it. Entry 9 is FONTS\FONTSEL.FNT. Entries 1 and 2 use the font named by
SYSTEM.INI [system] gui_low_font.
| Index | Font | A | B |
|---|---|---|---|
| 0 | REDSEL | [3dfx] font_sel A | font_sel B |
| 1, 2 | gui_low_font; glyph indices select page-palette colours | 255 | 255 |
| 3 | REDSEL | font_norm A | font_norm B |
| 4 | REDSEL | font_used A | font_used B |
| 6 | REDSEL | 255 | 180 |
| 7 | FONTNORM; small font FONTNORS with font_norm A,B | font_norm A | 140 |
| 8 | FONTNORM; no separate small font | font_used A | 50 |
| 9 | FONTSEL; small font FONTSELS with font_sel A,B | font_sel A | font_sel B |
Entries without a separate small font draw with the entry itself in the reader, so
styles 0, 3, 4, 6 and 8 use the large fonts and styles 1 and 2 the gui_low_font. Style 7 and 9 text
uses the small-font link, whose alpha the reader sets to 200 and 255. Glyph colours are the font
palette colours multiplied by A / 255, except for styles 1 and 2. Index 5 has no initialised entry;
indices above 9 are not described.
The reader draws text glyph by glyph. A glyph whose right edge would pass x = 640 moves the pen to the entry’s starting x and down by the font’s line height first, so long lines of the large styles wrap inside words. A line feed does the same. Drawing stops when the pen reaches y = 480. Wrapping by 65 bytes above applies before drawing and is independent of this pixel wrap.
Toggle notices
Tab (scan code 15) toggles walk mode and F7 (scan code 65) toggles
auto-defend; neither key is configurable in KEYS.INI. Each writes SYSTEM.INI
[CYRUS] WALK_MODE or [CYRUS] AUTO_DEFEND and shows an RTX string for 24
ticks, centred on x = 320: walk mode (#WON / #WOF) with its top at
y = 10, auto-defend (#DON / #DOF) at y = 30.
Draw order
After the 3D scene, later entries over earlier ones:
- Compass
- Candle, then its health number
- Logbook icon
- Walk-mode and auto-defend notices
- Power-up slot 1, slot 2
- Activation prompt, or item icon then count (or item name)
- Two additional bitmaps
- Subtitles, the dialogue topic menu and later layers
Torches
LightTorch begins lighting and UnlightTorch begins dousing; both do nothing
without the item system, an animation state with the transition’s group, or outside
the ordinary execution context. Lighting also requires that the actor is not in
water (surface 1 through 4), is not in motion state 27 through 32, 15 or 10 through
19, is not already lighting or dousing, has no burning torch, holds no weapon and is
not drawing or sheathing one, and has no falling or queued motion; dousing refuses
only in motion state 27 through 32, while lighting or dousing, and while drawing or
sheathing. Lighting requests group 149 and sets motion state 33; dousing requests
group 150 and sets motion state 34.
While the held-item byte is set, each update finishes the transition. A held item
in deep water (surface 2 or 4) or with a weapon held is dropped. Motion state 33
requests group 149 and, once that group is current and its frame-progress byte
reaches attribute byte 103, sets the burning counter from the saved counter (or,
when that is zero, from the start value), clears the motion state and leaves the
held item set. Motion state 34 requests group 150 and, at attribute byte 104,
saves the burning counter, clears it and the motion state and the held item, and
drops one unit of the held item when the counter had reached zero. The start value
is torch_time from ITEM.INI times one and a half, rounded to even, at least
torch_time one and at most 255.
Every eighth world-loop iteration, an actor whose counter is nonzero and that is not dousing is updated: in deep water the counter is cleared; in shallow water the torch is doused; otherwise the counter loses the nominal ticks (not below zero) and dousing begins when it reaches zero. When a global torch flag is set, the selected player’s counter is set to 100 instead. Using any item while a torch burns puts the torch out; using the torch item lights it and holds it.