SINGE 3.00
==========
Unreleased
API Changes
-----------
- --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 "SINGE Is Not a Game Emulator", in the banner,
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 grey, --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 analogue 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
colour 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 centred 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. 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 centre,
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). Colours 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