SINGE 3.00
==========
Unreleased
API Changes
-----------
- On Windows, Singe started with its output already going somewhere -- an
IDE, a launcher that keeps a log, Singe.exe > log.txt -- leaves it there
instead of opening a console window of its own that took it away, and
only waits for a key before closing a console it opened itself.
- A game restyles its subtitles, its banners, and the score panel with its
own copies of subtitle.rml and scoreBezel.rml beside its script, each
linking a style sheet of its own; Singe draws from the game's copy when
there is one, and the trace says so. Editing the files in the Singe
folder never worked for long: every start writes that folder afresh.
- scoreBoard() works on a cabinet that is not signed in, for a game the
service offers to everyone, so a game can show its online board and ask
the player to sign in; the answer has no standing. scoreSignedIn() says
whether the cabinet is signed in. Posting still needs an account.
- Packed games: the data directory is named for the script's directory as
the game holds it. A games.dat names a game's script from the folder
above the game's own (MyGame/MyGame.singe), so a MyGame.game packed from
MyGame/ wrote to /MyGame/MyGame/; it now writes to /MyGame/,
as the Reference always said and where the loose game writes, so the two
share their saves and settings. Move what an earlier build left in
/MyGame/MyGame/ up one level by hand.
- scoreLowestWins(board) says a board ranks the lowest score first: fewest
strokes, deaths, or seconds. Call it before the board's first score, and
scoreSubmit and scoreBoard go that way for that board; scoreBoard's result
says which way a board ranks (result.order, "lowest" or "highest"). A
board's first score fixes its direction for good, so a score sent the
other way afterwards is refused, and dropped from the queue rather than
tried again. Zero is a score on such a board, and so is a number below
zero, a round under par.
- physicsSetGravity wakes every body when gravity changes. A body the
physics had put to sleep at rest stayed asleep under the new gravity, so a
crate left on a shelf hung in the air when gravity turned sideways, and
cloth that had hung still for a while ignored a wind made of gravity.
Setting the same gravity again wakes nothing.
- onOverlayUpdate is due 15 ms after the previous call began, not 15 ms after
it returned. A script that took 5 ms a frame used to run every 20 ms, and
one whose work varied from frame to frame ran at an uneven pace; now the
script's own time falls inside the 15 ms, and a script that takes less than
that runs on a steady 66 times a second. After a stall it is due a frame
from then, so it never runs several times in a row to catch up.
- controls.cfg, settings.cfg and gamecontrollerdb.txt are read game first.
The four places are the same, but the file beside the game's script --
what its author shipped -- is now read first, then the machine's own in
the directory Singe was started in, then the one above the data directory,
then the game's data directory, and a later file still wins. Before, the
game's file came last and beat the machine's, so a handheld could not keep
a game from putting its test mode on a face button. A machine file only
replaces what it names: INPUT_TEST_MODE = { SCANCODE.F2 } takes test mode
off whatever button a game gave it.
- gamecontrollerdb.txt now beats SDL_GAMECONTROLLERCONFIG. Systems that set
that variable for their built-in pad -- ArkOS does, with a mapping for the
R36S that leaves Select and Start out -- used to win over any file, so no
game ever saw those buttons. The files the first game finds are added to
the variable before SDL starts, where a later line for a pad replaces an
earlier one. With --program, a pad button its mapping leaves out is traced
with its raw number, so a gamecontrollerdb.txt line can name it.
- A controls.cfg input can be a chord: { GAMEPAD_0.BUTTON_BACK,
GAMEPAD_0.BUTTON_START } as one entry of a mapping fires when the buttons
are held together. Each button keeps its own meaning as well.
- fontPrint keeps the strings it renders, keyed by font, quality, colors and
text, so printing the same text every frame costs about what drawing a
sprite of it does. Rendering text with fontToSprite for performance is
deprecated; it still works, and is still the way to get text that can be
measured, scaled or turned. Strings not printed for a second are dropped,
and a font's go when it is unloaded.
- GUIs are redrawn only when their picture changes. When a frame's document
draws exactly what the last frame drew and nothing new was made in between,
the texture already holds the picture and is kept; changes through RML,
RCSS, the RmlUi Lua interface and the gui* calls are all seen.
guiRefresh(gui) forces a redraw, as a way out should a change ever be
missed; a game should not need it. guiGetStats reports zeros on a frame
where nothing was redrawn.
- videoDraw has the GPU draw a video that is not scaled or turned by
videoScale or videoRotate, when it lands on the overlay where nothing has
been drawn since an overlayClear to a transparent color: under the
overlay, where it looks the same as the frame copied in and costs next to
nothing. Such a frame is scaled to the screen the way the overlay is,
rather than nearest-neighbor into the overlay first. Loaded videos now
decode the way the disc does, including zero-copy, and are converted to
RGB on the CPU only when a CPU draw needs the pixels, so draw a video
before anything that goes over it rather than after.
- Singe can update itself. Once a day the menu asks the master service
whether a newer release exists for this machine's platform, and says so in
a line under the game's details; nothing is fetched until a person opens
the new Engine Update service tool and presses the button. The tool
downloads the release, checks it against the digest the service published,
puts it beside the running Singe under the release's own name, sets the old
one aside as old-, and restarts into the new one with --refresh, so
the support folder is rewritten by the version that now owns it and the
folder it replaces is kept as old-Singe-. no_update_check = true
in settings.cfg turns the daily look off; the tool still works by hand.
Behind it: singeGetSystemInfo() reports platform and executable,
SINGE_UPDATE_CHECK says whether the look is on, and singeRelaunch(path
[, argument]) starts another executable when this one ends. Neither the
question nor the download needs an account.
- A game can say what it needs of a machine. REQUIRES in its games.dat lists
the capabilities it uses -- SCENE, GUI, SHADOWS, POST, SKIN, MORPH -- and no
REQUIRES means 2D; TIER (0 to 3) says how much machine it wants. You rarely
write either by hand: the engine notices what a game uses as it runs, says in
the trace when the game uses something its REQUIRES leaves out, and --pack
writes REQUIRES into an entry that has none. The menu measures this
machine's tier from its own backdrop the first time it runs, keeps it in
machine.cfg, and marks a game this machine cannot run (and says why) or may
run slowly. Nothing is hidden. Behind it: singeCapabilities() reports what
the machine can do, singeSaveTier(tier) keeps the tier, and sceneMeasure(on)
times the 3D scene's real work rather than the display's pacing.
- Connections to the master service trust two lists of certificate
authorities at once: the one Singe ships and the one the machine keeps
(the distribution's file on Linux, the root store on Windows, the system
anchors on macOS). Either one vouching for the server is enough, so a
cabinet whose copy of Singe has aged can still fetch the update that
replaces it. A self-hosted service with its own authority puts that
certificate in cacerts.local.pem in the data folder, which Singe never
writes. A refused connection says which lists it checked and what the
machine's clock reads when a certificate is not valid yet, and says the
clock is wrong outright when it reads earlier than the date of the list
Singe ships -- the usual trouble on a board with no clock battery that
has not had network time. Behind it:
netTrustSystem(context) adds the machine's roots to a LuaSec context, and
every request shares one context instead of building one each time.
- Beta builds have a version of their own. CMake's project version has to be
numbers, so the four it takes are used as major.minor.beta.which: 3.00.0.0
is v3.00 and 3.00.1.2 is v3.00b2, which is what the binary is named, what
the engine reports, and what the books carry as their revision. A beta
ranks below the release of the same number, so a cabinet on v3.00b2 is
offered v3.00 the day it appears, and the minor is compared as a number, so
3.09 precedes 3.10. The launcher ranks the binaries beside it the same way.
- A game can be sent to the master service for review. An upload is not in
the catalogue until it has been reviewed. Behind it: singePack(directory,
database) packs a game from a script exactly as --pack does, and the
network layer can send a request body straight from a file, so a game is
never held in memory whole.
- The menu knows which of its games are out of date, and updates them from the
game's own page. A packed game now carries VERSION in its games.dat, so the
file itself says what it is wherever it ends up. The menu reads that out of
every games.dat it loads and asks which of them are out of date, no sign-in
needed; a game with a newer version wears an "update" mark in the list, the
line under its details names the version, and button 3 fetches it through
the same checked download the Get Games tool uses. Nothing about this
machine is kept anywhere but the question: the menu says which games and
versions it holds and is told which are newer, because a record of what a
cabinet has installed could never have stayed true.
- overlayGetFontWidth and overlayGetFontHeight report the size in pixels of one
character cell of the built-in console font. overlayPrint counts in cells
while every other drawing call counts in pixels, and until now nothing said
how big a cell is, so a script converting between the two had to measure the
font by eye and hard-code the answer.
- onInputPressed and onInputReleased receive a second argument when the
switch came from a mouse or a light gun: the device's index, as
Hypseus passes it, so a two-gun game can tell its players apart. A
switch from the keyboard or a pad passes nothing there.
- singeScreenshot takes an optional file name, so a script that makes
pictures on purpose can name them.
- guiDraw's place in a script's drawing now decides what is over the GUI:
what is drawn on the overlay after the call goes on a layer composited
over that document, so a gun's sight or a pointer drawn after the HUD
shows over it. Before, every GUI was drawn over the whole overlay.
- onTextInput, in MODE_FULL: the text a key press produced, shifted and
laid out as typed, since a keysym is the unshifted key and a script
reading keysyms could never type a capital or a quote.
- In MODE_FULL a pad or mouse button reaches the script as the switch it
is mapped to, as the manual always said; it arrived as a keysym of 0.
- A sprite scaled to nothing (a bullet shrinking to zero) draws as a
pixel instead of ending the engine with "Unable to transform sprite".
- --deterministic stepped the disc one video frame per engine frame,
whatever the step, and the overlay throttle's strict comparison ran
the script every other iteration of the virtual clock, so a game that
reads the same frame on two consecutive updates (as a game paced for
a faster overlay does) never saw it twice; the disc now follows the
virtual clock like everything else, each frame lasting the time it is
worth, and the script runs every step. A bare os.time() answers
with the virtual clock too, so a game that seeds its generator from
the wall clock when a game starts (a one-player game does)
repeats; os.time with a date table is Lua's own.
- fontSelect given no font (nil, or an index that was never loaded)
keeps the current font and warns instead of ending the script, as
Hypseus lets it pass; The Last Bounty Hunter names a font it never
loaded at its first board.
- guiSetHandler set again after a document's elements were rebuilt (a
list through inner_rml) now attaches to the new element with that id.
It kept the old listener, on an element that no longer existed, so a
rebuilt list's rows stopped answering clicks.
- TAB reaches a script even when a GUI document is up: RmlUi took it for
moving focus and never handed it back, so a game whose keys include
TAB lost it. It goes to the GUI only while a text field is being typed
in.
- spriteFlip mirrors a sprite left to right, top to bottom, or both, for
every draw of it, its frames included, so art that faces one way is
enough for a character that walks both ways.
- soundPlay takes a volume as its third argument, and
soundSetChannelVolume changes a playing channel's own volume, both under
the master from soundSetVolume: a quiet footstep and a loud shot can
share one master. The master used to be applied through the mixer's
tag gain, which overwrote the distance fade of positioned channels
whenever it changed; it is applied per track now.
- materialSetOccluder makes a material write depth and no color: an
invisible shape that hides what walks behind it while what was drawn
behind it -- a painted room, the disc -- shows through, which is how a
3D character walks behind a pillar that is only in the picture.
- --gamedir names the directory holding the games, the .game files and
the Singe support folder, so the executable can live anywhere. It
becomes the working directory; relative names count from it. The
Menu.sh the engine writes there runs the executable by its full path.
- Without --datadir the data directory is now data/ in the game
directory (2.x wrote beside the game, which may be read only). The
menu's Menu.sh no longer needs to pass -d data.
- math.randomseed accepts fractional seeds (floored), so 2.x games that
seed with os.clock() products run under Lua 5.4.
- A script launched by name (or by its directory) runs with its games.dat
entry, as the menu would run it: Singe finds the games.dat in the
script's directory or up to three above, and applies the entry whose
SCRIPT names the script, LEGACY_SPRITE_ARGS included. Options typed on
the command line keep their values. Every game in a library therefore
behaves the same whether it was started from the menu or from a shell.
- The name now expands to "Scott's Increasingly Nifty Game Engine", in the
banner, the menu, the manual, INSTALL, and the Windows file description.
- Framework.singe applies the legacy sprite argument order once even when
a game loads it more than once.
- Hypseus Singe extensions, so games written for it run unchanged:
spriteLoadFrames, spriteDrawFrame, spriteFrameWidth, spriteFrameHeight,
spriteResetColorKey, discAudioSuffix, singeSetQuitKeyEnabled (with the
Framework alias keyboardCatchQuit), vldpGetScale, vldpFocusArea,
vldpResetFocus, vldpSetMonochrome, overlaySetMonochrome,
vldpGetYUVPixel, controllerIsValid and controllerDoRumble. Sprite calls
take the handle first like the rest of 3.00; the LEGACY_SPRITE_ARGS
shim covers spriteDrawFrame and spriteResetColorKey.
- More Hypseus Singe names, almost all of them supplied by
Framework.singe over calls Singe already had: setOverlaySize and
setOverlayResolution over overlaySetResolution, rewriteStatus as a stub,
and the six music calls over the sound family. setOverlayLinearScale is
a real engine call: it smooths the overlay as the window scales it,
which is the default and what Singe has always done, or takes the
nearest pixel for crisp pixel art.
- The picture's place on screen is now one rectangle built from four
things in order: the window, --scalefactor (range widened from 50-100
to 25-100), --shiftx and --shifty (new; they move the picture inside the
room the scale left, -100 hard against one side and 100 against the
other, which is what Hypseus's own mean -- so at full scale there is no
room and a shift does nothing), and last the Sinden border, which is
therefore still a border whatever the shift and the scale did --
--scalefactor and --sindengun may now be used together. --rotate (new;
0, 90, 180 or 270 degrees)
turns the whole presentation, video, overlay, GUIs, 3D scene and
particles together, filling a portrait window on a quarter turn. Mouse
and light gun positions follow all of it, so a click lands where it is
drawn. Scripts move the scale and the rotation while the game runs with
the Hypseus extensions vldpSetScale, vldpSetRotate and vldpGetRotate.
- Bezels. --bezel names cabinet artwork in a bezels folder (--bezeldir
names another folder, --bezelflip draws the artwork in front of the
picture instead of behind it), and a Lua sidecar named after the
artwork declares the cutout the picture is drawn in, in the artwork's
own pixels: CUTOUT = { x = 200, y = 120, width = 800, height = 500 }.
With no cutout the picture keeps the window and --scalefactor,
--shiftx and --shifty place it, as before. A Sinden border goes to the
edge of the window when artwork is loaded, because the gun's camera
sees the whole screen; --sindenedge video|window overrides that.
Scripts ask mainBezelLoaded() which artwork they got and choose with
setOverlayOnTop whether the overlay sits above it.
- The arcade score panel behind the seven scoreBezel calls: credits, and
a score and a life count for two players, in a shipped RmlUi document
(Singe/scoreBezel.rml and Singe/scoreBezel.rcss) with a seven segment
look that a theme or a game may restyle. It is off until a game asks
for it with scoreBezelEnable, exactly as in Hypseus, and it sits in the
room a bezel's cutout leaves above or below the picture, or along the
bottom of the picture when there is none. Hypseus's second argument,
which chooses among its five scoreboards, is accepted and ignored.
guiDrawScreen puts a game's own GUIs out there beside it: it is
guiDraw with the rectangle measured across the whole window.
- A settings file. settings.cfg holds the options you would otherwise
type on every command line, in Lua like controls.cfg and found the same
four ways: the directory Singe was started in, above and inside the data
directory, and beside the game's script, with a later file winning key by
key. Its keys are the long option names without the dashes, so there is
no second vocabulary, and a switch takes true or false. A settings file
is a set of defaults: a games.dat entry for the game still overrides it
and the command line always does. Anything naming the game, naming a
directory, or acting before the file could be found (--framefile,
--gamedir, --datadir, --entry, --help, --apiversion, --noconsole, the
packing tools) belongs on the command line, and a key that is not an
option, is one of those, or carries a value the option will not take is
an error naming the file and the key. Singe/settings.cfg.example lists
every key with its default.
- Options a cabinet owner has been missing, none of them with a short
letter since the alphabet ran out: --screen picks the display (counting
from 1; a number too large lists the displays that are there),
--idleexit quits after so many seconds with nothing touched,
--startsilent starts muted until the first input, --monochrome starts
the disc picture in gray, --linearscale chooses the overlay's filter,
--nogamepad ignores the pads as --nomouse ignores the mice, --haptic
caps the rumble a game may ask for (0 turns it off), --keymapfile names
one controls.cfg instead of searching four places, --altaudio (and the
AUDIO_SUFFIX games.dat key) plays .ogg beside the disc's
video for another language, --xratio and --yratio carry the gun
coordinate scales a Hypseus light gun game reads with ratioGetX and
ratioGetY, --fvalue carries one number to the game through getFValue,
and --apiversion prints one machine readable line for a front end and
exits. --nosound, which had been accepted and quietly ignored, now
actually mutes.
- Every controller Singe opens is now a gamepad, whatever it started life
as. SDL calls a device a gamepad only when it has a mapping for it, and
an arcade encoder board, a spinner, a wheel or a flight stick usually has
none, so such a device used to be invisible. Singe now writes a
straight-through mapping for it -- axes in order to the two sticks and
then the triggers, buttons in order to the standard names, the first hat
to the D-pad -- and opens it as an ordinary pad, so it reaches
controls.cfg, GAMEPAD_0 to GAMEPAD_3 and every controller call like any
other. A device with more controls than the gamepad model can name loses
the extras, and one whose buttons are not wired in that order will have
them in the wrong places: --program traces the mapping in SDL's own
format so it can be pasted into a gamecontrollerdb.txt and rearranged.
Singe now reads that file, from the four places it reads controls.cfg,
and it and SDL's own database both win over the mapping Singe would
write. --mapjoysticks=false turns the writing off.
- Input options for a cabinet: --gamepad_reorder says which physical pad
fills which slot (enumeration positions from 0, as Hypseus writes them),
--trigger_threshold says how far an analog trigger must travel before
it counts as a button (a per cent of full travel, Hypseus's unit),
--joymouse lets the first pad's left stick drive the mouse cursor at
--js_range speed so a light gun game is playable on a stick-only
cabinet, --manymouse turns the many mouse mode on whatever the game asks
for so a two gun cabinet can be fixed from outside, and
--absolutes_only keeps only the devices that report an absolute
position, which is what a real gun does. controls.cfg gains DEAD_ZONES,
a dead zone for one axis at a time, and TRIGGER_THRESHOLD; both leave
every axis on DEAD_ZONE unless set, so nothing changes for anyone who
does not ask.
- SWITCH_MOUSE_DISCONNECT reaches onInputPressed when a mouse or light gun
is unplugged, so a gun game can say so instead of appearing to have
died, and joyMouseEnable, joyMouseIsEnabled, controllerGetName and
mouseGetName let a script drive and describe all of it.
- Alt-Enter switches between full screen and a window while the game runs,
as it does in Hypseus. Singe could not change the mode at all once it
had started.
- Subtitles. srtLoad reads a SubRip .srt and turns its timestamps into
disc frames at the disc's own frame rate, so a line is found again after
any search or skip; srtEnable shows them, srtPosition puts them where a
game wants (1 to 95 per cent down the picture) and srtClear takes one
off early. They are drawn through a shipped RmlUi document,
Singe/subtitle.rml with Singe/subtitle.rcss, both of which a game or a
theme may replace, over the picture and following it wherever the scale,
a shift, a bezel cutout or a rotation put it. A malformed file answers
false rather than dying. overlayBanner puts a message of its own up the
same way for about thirty frames.
- The disc picture gains three controls beside vldpSetMonochrome:
vldpFlash puts one white picture up the way a real player flashes when
it is searched, vldpSetBlend smooths the picture down its rows to take
the edges off an interlaced transfer, and vldpSetLuma brightens or
darkens it in eighths from half to one and a half without washing the
color out. All three work on the decoded frame, cost nothing while
they are off, and follow a frame file from one segment to the next --
which vldpSetMonochrome now does too, and did not before.
- Sprites. spriteRotateFrame turns one frame of a sheet and
spriteDrawRotatedFrame draws it centered on a point, which is what
spriteDrawFrame will not do since it ignores rotation and scale by
design; spriteDrawGrid draws any rectangle of a sprite at its own size,
which Singe had no way to do at all; spriteGetFrames counts a sheet's
frames; and spriteLoadData and soundLoadData build a sprite or a sound
from bytes held in a Lua string, for a game that unpacks its own assets.
setOverlayOpacity fades the whole overlay and controllerHowMany counts
the open gamepads.
- Music has its own volume. The music family (musicLoad, musicPlay,
musicStop with a real fade, musicPause, musicResume, musicIsPlaying,
musicSetVolume, musicUnload) is now in the engine rather than shimmed
over the sound family, and carries a gain of its own on Hypseus's 0 to
128 scale: musicSetVolume moves no sound effect and soundSetVolume
moves no music. Music starts as loud as the sound effects, at whatever
--volume_nonvldp set. musicPlay answers the handle it was given, as
Hypseus's does, so a game can keep it to stop the piece, and musicStop
takes nil as none. Ten more Hypseus names -- hypseusGetWidth,
hypseusGetHeight, takeScreenshot, mouseHowManyReal and the six
spriteAnim ones -- are aliases in Framework.singe.
- trace.txt now opens with a header a bug report can be built on: the
version, the whole command line, the operating system, the processor,
the renderer and GPU backend that were actually chosen, how video will
be decoded, which audio decoders the build carries, the SDL versions
built and linked against, the settings that were applied and the game. It sits between two rules so it can be
pasted whole.
- lfs.dir, lfs.attributes and lfs.symlinkattributes see inside a packed
game (the union of its loose directory, its data overlay and the
database), and lfs.mkdir and lfs.rmdir act on its data overlay, so
frameworks that list or create their own directories work packed.
- Physics. Any scene node can carry a rigid body on Jolt Physics:
static, kinematic (follows its node and pushes) or dynamic (drives its
node), shaped as a box, sphere, capsule, cylinder, a convex hull of the
node's mesh or the mesh's triangles; mass, friction, bounce,
velocities, forces and impulses; triggers; hinge, ball and slider
joints with limits; a raycast; onCollision and onTrigger callbacks.
The world steps at a fixed 60 Hz between animation and rendering and
pauses with the game. physicsSet2D keeps bodies in a plane for 2D
games, which need no GPU (nodes now exist on every machine, so a
Raspberry Pi 3 gets 2D physics without 3D). New calls: body*,
joint*, physics*, and the
BODY_*, SHAPE_* and JOINT_* constants; see the Physics chapter. Needs
SSE4.1 on x86 (2008 and later); the engine's first C++ (one wrapper
file and the library), statically linked, no runtime to install.
- Soft bodies. softNew makes cloth or a pressure body from a node's
mesh (particles from its welded vertices, Jolt's stretch, shear and
bend constraints, pressure for balloons) or a rope from the node to a
point with a tube mesh the engine makes; they collide with everything
rigid and the mesh is rewritten every frame. softPin holds particles
in place or to nodes; stiffness, mass, damping and pressure are
tunable. meshPlane takes subdivisions; nodeSetMaterial changes a
material without the mesh. New calls: soft*, and the SOFT_*
constants; see Soft bodies in the Physics chapter.
- Ragdolls. ragdollNew derives capsules and swing-twist joints from a
skinned model's skeleton; ragdollActivate hands the skeleton to
physics from its current pose and the mesh follows the bones as it
falls; motors (ragdollSetStrength) pull it back toward that pose;
ragdollApplyImpulse shoves a bone; ragdollDeactivate returns it to
animation. New calls: ragdoll*; see Ragdolls in the Physics chapter.
- Water. bodySetWater fills a static body with water: dynamic bodies
inside float, sink and drift by Jolt's buoyancy against its surface,
with drag and a current (bodySetCurrent, bodySetBuoyancy per body);
players swim in it (playerIsSwimming, playerSetSwim, three-axis
playerMove); a buoyant hull with vehicleNew(node, VEHICLE_BOAT) gets
a propeller and a rudder (vehicleSetThrust, vehicleSetRudder).
- Vehicles. A dynamic body becomes a car, a motorcycle or a tank on
Jolt's vehicle constraint: wheels are nodes the engine poses (spin,
steer, suspension), the script drives with throttle, steering, brake
and hand brake, and engine, gears, suspension, steering, brakes and
anti-roll bars are tunable. Motorcycles lean and balance with a spring
scaled to the chassis; tanks pivot on the spot. New calls: vehicle*,
and the VEHICLE_* constants; see Vehicles in the Physics chapter.
- Point lights inside geometry. A bulb in a closed room, under a shade
or inside a cabinet now shadows correctly: cube faces render depth
two-sided so closed meshes cast from inside, every shadow tap picks its
own cube face so the seams along the cube edges are gone, the depth
range fits the casters round the light instead of the scene center,
the bias grows with distance and grazing angle, and each face draws
only what it can see. nodeSetShadow excuses a mesh from casting.
- Character controller. playerNew puts Jolt's CharacterVirtual on a
node, as a capsule or any convex bodyNew shape: it walks where
playerMove says, climbs steps, slides along walls, refuses steep
slopes, falls, jumps from the ground, rides kinematic platforms and
shoves light dynamic bodies; onCollision and onTrigger report it like
a body. Works in 2D worlds as a platformer character, with tolerances
scaled to the shape. New calls: player*; see Players in the Physics
chapter.
- Linear lighting. The scene is lit in linear light on a 16-bit float
target and brought to the screen by a post pass with an exposure
control (sceneSetExposure, in stops) and a tone curve (sceneSetTonemap:
TONEMAP_NEUTRAL, TONEMAP_ACES, TONEMAP_NONE). Colors given as 0-255
are sRGB and decoded on the way in; textures are sampled as sRGB;
bright light compresses instead of clipping. Specular highlights are
GGX with Fresnel, so roughness and metallic behave as they do in glTF
viewers. Existing scenes look a little different: the same numbers
now mean what they mean everywhere else.
- GUI. Menus, HUDs and option pages can be RmlUi documents: HTML-like
RML styled by CSS-like RCSS, with layout, text wrapping, nine-slice
panels from the game's own artwork, transitions, animations, form
controls and a focus model a gamepad can drive. guiNew(width, height)
makes a GUI rendered to its own texture every frame; guiLoad puts a
document in it (names through the vfs like spriteLoad, so packed games
work), with guiShow, guiHide and guiClose; guiDraw composites it over
the overlay in overlay coordinates (through the Sinden border and the
overscan scale like the overlay itself), and materialSetGui shows it
on a material in the scene, where sceneProbeGui gives the GUI and
pixel under an overlay point. guiSetInput routes the mouse and light
gun, keys, typed text (SDL text input runs while a field has focus)
and a pad's switches (directions as arrows, ACTION_1 as Return,
ACTION_2 as Escape) to a GUI; what an element uses never reaches
onInputPressed or onKeyPressed, and the rest falls through as before.
guiSetHandler calls a Lua function for an element's click, change or
submit; guiGetValue and guiSetValue read and write a control's value
or an element's inner RML; guiLoadFont adds a face for every GUI.
RmlUi's own Lua API runs on the game's state: rmlui.contexts["gui0"]
and its documents, a document's