1728 lines
89 KiB
Text
1728 lines
89 KiB
Text
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.
|
|
|
|
- A game can be described rather than written. Singe/Author.singe and
|
|
Singe/AuthorCompile.singe take a table of layers, entities, behaviours
|
|
and rules and compile it into an ordinary Singe game: the rules become
|
|
real Lua, so nothing walks a table every frame and the result can be
|
|
read and edited by hand. There is no notion of genre in it -- a game
|
|
declares which of the engine's layers it uses, and that is the whole
|
|
difference between a platformer and a quick-time event over video. The
|
|
vocabulary of conditions and actions is declared in a table rather than
|
|
built into the compiler, so new kinds of game are entries rather than
|
|
releases, and the "lua" action is the deliberate way out when a rule
|
|
needs something the vocabulary cannot say. See the manual.
|
|
|
|
- An editor for those descriptions, Forge, which is itself
|
|
a Singe game: the canvas is the same overlay at the same coordinates the
|
|
game will be played in, so what is placed is what is seen. Entity list
|
|
and details are an RmlUi document, the canvas beside them is drawn into
|
|
the overlay and picked the way a light gun game picks a target, and the
|
|
two compose because a button is offered to the GUI first while pointer
|
|
motion is never consumed. A description survives a round trip through
|
|
it: load, save, load again, and it compiles to the same game. Forge is
|
|
distributed on its own and no part of it ships inside Singe -- not the
|
|
editor, not the compiler, not the runtime. A game Forge builds carries
|
|
its own copy of that runtime, so it runs on a machine that has never had
|
|
Forge on it and cannot change behaviour because the engine moved on.
|
|
|
|
- A game released from Forge is standalone. forgeExport writes the
|
|
compiled script, a games.dat, the description it came from and a copy of
|
|
the runtime into a directory of its own, taken out of Forge. --pack turns
|
|
that directory into a .game. The game locates its own directory with
|
|
debug.getinfo rather than trusting DIR, which names the directory of the
|
|
script the engine was launched with -- not this one when a game is reached
|
|
by dofile.
|
|
|
|
- The editor edits rules, not only entity positions: Tab swaps the panel
|
|
between the entities and the event sheet, the selected rule opens in
|
|
place with its conditions and actions under it, and conditions and
|
|
actions are added from the same manifest the compiler reads, so the rule
|
|
editor needs no change when the vocabulary grows. It is driven by keys
|
|
as well as the pointer, which is how the bundled menu has always worked
|
|
and what a cabinet needs. ENTER types a value into the selected
|
|
condition or action and moves to its next one, ESC puts it back; a
|
|
number typed in comes back a number.
|
|
|
|
- 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 <base><suffix>.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 <script> blocks and on* attributes.
|
|
Singe/gui.rcss is the shipped theme (.panel, .list, .muted and the
|
|
styled form controls) and Singe/FreeSansBold.ttf the fallback face
|
|
(family FreeSans, bold). RmlUi (MIT) is added to LICENSES. Test
|
|
scenes scene36 to scene39 cover a document over the disc, a GUI on a
|
|
material, input and handlers, and the pointer on a 3D surface. See
|
|
the GUI chapter.
|
|
|
|
- GUI effects. Every RCSS effect now draws: rounded and transformed
|
|
elements clip their children to their shape (stencil clip masks),
|
|
filter: opacity() fades a group as one, mask-image cuts by an image's
|
|
alpha, box-shadow casts hard and soft shadows, filter and
|
|
backdrop-filter take blur, drop-shadow, brightness, contrast, invert,
|
|
grayscale, sepia, hue-rotate and saturate, and the linear-gradient,
|
|
radial-gradient and conic-gradient decorators draw (the theme's
|
|
panels, buttons and progress bars and the menu's list use them).
|
|
The custom shader decorator is refused, gradients carry at most
|
|
sixteen stops, effects nest at most eight layers deep. guiGetStats()
|
|
returns what the GUIs cost to render in the last frame (draw calls,
|
|
layers, filter passes, mask writes, layer bytes, passes). Test scenes
|
|
scene40 to scene43 cover clip masks, layers, filters and gradients.
|
|
|
|
- The Pi decodes video through its V4L2 memory-to-memory device (the
|
|
h264_v4l2m2m family of decoders) when the codec has one, falling back
|
|
to software when the device is missing, as on a Pi 5 for H.264. The
|
|
trace reports which decoder is in use and the format the device hands
|
|
back; --softwarevideo turns it off like the other hardware decoders.
|
|
|
|
- The Menu.sh and Menu.bat the engine writes now look beside themselves
|
|
for a file whose name starts with Singe and run the first one, the way
|
|
the launchers BatMaker writes already did, instead of naming the
|
|
executable. The executable's name carries its version, so a launcher
|
|
that named it stopped working at the next release, and the thing that
|
|
would have rewritten it was the launcher itself. With --gamedir the
|
|
executable is elsewhere and its full path is still written.
|
|
|
|
- Third-party libraries brought up to their current releases: ffmpeg
|
|
9.0.1 (from a 2023 development snapshot; libpostproc is gone with it),
|
|
OpenSSL 3.5.8 LTS (from a 2023 development snapshot), Lua 5.4.9, zlib
|
|
1.3.2, zstd 1.5.7, uthash 2.4.0, lsqlite3 0.9.7, luafilesystem 1.9.0,
|
|
copas 4.12.0 (its new copas.future module is embedded) and arg_parser
|
|
1.21. sqlite3.lversion() reports the real lsqlite3 version instead
|
|
of "unknown", and os.tmpname() returns a usable name again (the local
|
|
mkstemp edit never filled in its template). LICENSES now records the
|
|
version of every vendored library.
|
|
|
|
- The bundled menu is an RmlUi document (Singe/Menu.rml styled by
|
|
Singe/menu.rcss, filled and driven by Singe/Menu.singe): every game in
|
|
a scrolling list on the left, up and down to select, left and right to
|
|
page, Start or any button to play, and a mouse works too (click a row
|
|
to select it, click it again or the Start button to play, wheel to
|
|
scroll the description). The service tools are pages of the same
|
|
document. A machine without a GPU device, where guiNew fails, gets the
|
|
same menu drawn into the overlay instead; see "One menu, two renderers"
|
|
below.
|
|
|
|
- Physics debug drawing. physicsSetDebug(DEBUG_*) draws the bodies in
|
|
wireframe (coloured by motion state), joints and their limits, contact
|
|
points and velocities as lines over the scene each frame, static shapes
|
|
too with DEBUG_STATIC; lineDraw draws a script's own world-space lines
|
|
for a frame, navDraw the baked navigation mesh.
|
|
|
|
- Compressed textures. KTX2 files (Basis Universal, from
|
|
util/compressTextures.py) stay compressed on the GPU in the block
|
|
format the machine has (BC7, ASTC, BC3, or plain RGBA where nothing
|
|
else exists), a quarter of the memory of PNGs or less. A model whose
|
|
file uses KHR_texture_basisu loads them on its own; materialSetTexture
|
|
and the map calls take a .ktx2 name in place of a sprite; sceneGetStats
|
|
reports the texture memory in use. The Sponza test scene went from
|
|
393 MB of textures to 99 MB.
|
|
|
|
- Hot reload. singe --reload (-R) watches the game's loose script files
|
|
and runs the game again from its script when one changes; F5 does the
|
|
same, and singeReload() from a script. The engine (window, GPU, disc,
|
|
controllers) stays; everything the script made is remade by the script.
|
|
|
|
- Terrain. meshHeightmap makes rolling ground from a greyscale image,
|
|
materialSetTiling repeats a texture across it, a SHAPE_MESH body on it
|
|
becomes a Jolt height field, and terrainGetHeight reads the ground
|
|
height at a point.
|
|
|
|
- Sprites and text in the scene. nodeSetSprite shows a loaded sprite on
|
|
a node as a lit quad (nodeSetSpriteFrame picks a frame), nodeSetText a
|
|
line of text in the selected font, and nodeSetBillboard turns any node
|
|
to face the camera squarely or about its own up axis (BILLBOARD_*).
|
|
|
|
- Navigation. navNew, navAddNode and navBuild bake a walkable mesh from
|
|
the scene's geometry on Recast; navPath, navNearest, navRaycast and
|
|
navRandomPoint query it on Detour; agents (navAgentNew, navAgentMoveTo,
|
|
navAgentStop, navAgentGetVelocity, navAgentIsArrived, navAgentSetPlayer)
|
|
walk it on DetourCrowd without walking through each other, moving their
|
|
nodes or driving a player controller, with onNavArrived when they get
|
|
there; navSave and navLoad keep a baked mesh. Recast Navigation is
|
|
vendored under thirdparty/recastnavigation (zlib licence).
|
|
|
|
- Bloom and views. sceneSetBloom makes everything brighter than a
|
|
threshold glow, blurred over a half-size chain before the tone curve.
|
|
viewNew renders a second camera to a texture every frame (viewSetCamera
|
|
points it) that materialSetView shows on any surface: monitors,
|
|
mirrors, portals. Each view turns billboards to its own camera and
|
|
draws transparency back to front from it.
|
|
|
|
- Particle extras. Trails (emitterSetTrail: a fading ribbon behind
|
|
each particle, 2D and 3D), collisions with a floor or, by ray cast,
|
|
the physics world (emitterSetCollide, COLLIDE_*), lit particles shaded
|
|
by the lights and sky (emitterSetLit) and soft particles that fade
|
|
where they meet geometry (emitterSetSoftness).
|
|
|
|
- Culling and instancing. Meshes outside the camera's view are skipped,
|
|
and copies of one mesh with one material draw as a single instanced
|
|
call (their matrices in a per-frame buffer the vertex shaders read), in
|
|
the shadow passes too. sceneGetStats reports the draws collected, the
|
|
draws in view and the draw calls made.
|
|
|
|
- Cascaded shadows. A directional light's shadow is split into up to
|
|
four maps along the view (sceneSetShadowCascades, three by default),
|
|
each fitted to a slice of the camera's view out to
|
|
sceneSetShadowDistance and snapped to its texels, so shadows in a
|
|
large level are sharp up close and steady as the camera moves.
|
|
|
|
- Sky, environment light and fog. sceneSetSky wraps the scene in an
|
|
equirectangular panorama (Radiance .hdr, PNG or JPEG) drawn behind
|
|
everything and lighting it: its diffuse light replaces the ambient and
|
|
its reflections show on metals and glossy surfaces, blurred by
|
|
roughness (sceneSetEnvironment switches that off, sceneSetSkyIntensity
|
|
scales it). sceneSetFog fades meshes and particles toward a colour
|
|
between two distances.
|
|
|
|
- Positional sound. A playing channel can follow a scene node
|
|
(soundSetNode) or sit at a point (soundSetPosition) and is then panned
|
|
and faded by distance (soundSetRange) as heard from the camera or a
|
|
chosen node (soundSetListener); soundGetPosition reports where it lies;
|
|
soundSetPan pans a channel for 2D games; soundPlay takes a loop count.
|
|
|
|
- Animation blending. animationPlay takes a fade in seconds and
|
|
crossfades from the clip that was playing, or from the pose the nodes
|
|
are in when nothing was (so a ragdoll eases back up); up to four clips
|
|
play on layers above the base (animationPlayLayer), each with a weight
|
|
(animationSetLayerWeight) and an optional mask limiting it to part of
|
|
the skeleton (animationSetLayerMask); animationStop takes a layer.
|
|
|
|
- Material maps. A material can carry a normal map, an occlusion map, a
|
|
metallic-roughness map (glTF's packing) and an emissive map beside its
|
|
base texture, and a loaded model brings its own. Every mesh has
|
|
tangents: a model's when the file carries them, computed from the UVs
|
|
otherwise. New calls: materialSetNormalMap, materialSetOcclusionMap,
|
|
materialSetMetallicRoughnessMap, materialSetEmissiveMap.
|
|
|
|
- Mipmaps. Every scene texture carries a full mipmap chain generated on
|
|
the GPU at load and is sampled with anisotropic filtering, so textures
|
|
seen small or at a grazing angle no longer sparkle or moire.
|
|
materialSetFilter(material, FILTER_NEAREST) keeps pixel art crisp.
|
|
|
|
- Particles. Emitters spawn, move and age particles from a recipe (rate,
|
|
life, speed, direction and spread, gravity, drag, size, colour and spin
|
|
over life, a birth radius, a pool cap) in the overlay and in the 3D
|
|
scene alike. A 2D emitter draws on the GPU above or beneath the
|
|
overlay when the script asks (emitterDraw); a 3D emitter sits on a node
|
|
and draws camera-facing billboards after every mesh, depth tested, in
|
|
world units. Any loaded sprite is the picture (a random frame per
|
|
particle from an animated one); without one, a soft disc. Normal or
|
|
additive blending. New calls: emitter*, and the PARTICLE_* constants;
|
|
see the Particles chapter.
|
|
|
|
- 3D scenes. A game can draw a 3D scene between the disc video and the
|
|
overlay: primitive meshes and script-built geometry, materials with
|
|
colour, textures from sprites, the disc or a loaded video, metallic and
|
|
roughness, up to eight lights each able to cast shadows (cube maps for
|
|
point lights), any node as the camera, and glTF 2.0
|
|
models (.glb, self-contained) with node animation, skinning and morph
|
|
targets, placed any number of times. Everything is a node in one tree. New calls:
|
|
scene*, node*, mesh*, material*, light*, camera*, model*, animation*,
|
|
and the LIGHT_* constants; see the 3D Scenes chapter of the manual.
|
|
Needs a GPU with Vulkan, Direct3D 12 or Metal; the Raspberry Pi 4 is
|
|
the minimum Pi (the Pi 3 has no Vulkan driver). 2D games run as
|
|
before without one. The renderer now runs on
|
|
SDL's GPU device where there is one.
|
|
|
|
- A game can be one file: singe --pack DIRECTORY GAME.game writes the game's
|
|
scripts, art, sounds, fonts, and video into an SQLite database, and
|
|
singe GAME.game (or the menu, which lists every .game file beside the game
|
|
directories) runs it. Nothing in a game has to change to be packed:
|
|
every name a script uses (dofile, require, io.open and friends,
|
|
spriteLoad, soundLoad, fontLoad, videoLoad, framefiles, controls.cfg)
|
|
goes through one lookup that tries a loose directory named like the
|
|
database, then the game's data directory, then the database, so
|
|
packed games can be modded by dropping files beside them and files a
|
|
game writes land under data/<game>/files. Inside a database names
|
|
are matched without regard to case and a leading own-directory prefix
|
|
("DLe/Cfg/x") is ignored. singe --unpack GAME.game DIRECTORY restores
|
|
the files and singe --patch GAME.game SOURCE replaces files from a
|
|
directory or a patch database in one transaction. --entry=N picks
|
|
which games.dat entry a .game launch runs. Loose games are
|
|
untouched by all of this. Scripts get SQLite for their own data with
|
|
require("sqlite3"). The archive installer (.game, .tool, and .patch
|
|
archives dropped beside the binary) is gone along with libarchive; a
|
|
game is a .game file or a directory, copied into place.
|
|
|
|
- Video is decoded by libavcodec directly; FFMS2 is gone. Seeking is still
|
|
frame exact, indexing is faster, and the index files in the data
|
|
directory use a new format (old ones are rebuilt automatically).
|
|
Multi-channel audio is downmixed to stereo instead of refused. Indexing
|
|
is a few seconds at most, so the indexing screen and its artwork are gone.
|
|
Video is
|
|
decoded by the platform's hardware decoder when there is one (VA-API or
|
|
VDPAU on Linux, D3D11VA on Windows, VideoToolbox on macOS) and falls
|
|
back to software otherwise; --softwarevideo forces software.
|
|
|
|
- Singe is built on SDL3 (SDL 3.4, SDL3_image, SDL3_mixer 3.2, SDL3_ttf)
|
|
instead of SDL2. Nothing changes for scripts: the same functions,
|
|
constants, scancodes, and controls.cfg names. SDL2_gfx is gone; sprite
|
|
and video rotation and scaling are done by the engine itself.
|
|
|
|
- The sprite handle is now the FIRST argument of spriteDraw(), spriteLoop(),
|
|
spriteQuality(), spriteRotate(), spriteRotateAndScale(), spriteScale(), and
|
|
spriteSetFrame(), matching the video*() functions. Move the last argument
|
|
of each call to the front. Games that cannot be edited can set
|
|
SINGE_LEGACY_SPRITE_ARGS = true before loading the framework, or add
|
|
LEGACY_SPRITE_ARGS = true to their games.dat entry. See "Migrating from
|
|
Singe 2.10" in the manual.
|
|
|
|
- Singe is no longer laserdisc only. A games.dat entry with a VIDEO is a
|
|
laserdisc game; one with the line missing or blank runs without a
|
|
disc, on a canvas sized by CANVAS_X/CANVAS_Y (default 720x480), the
|
|
disc functions become harmless, and discGetState() returns the new
|
|
DISC_EJECTED. On the command line --disc (implied by --framefile)
|
|
declares the disc, since a video found beside the script no longer
|
|
does; --canvas=WxH sizes the canvas. Existing games.dat files need no
|
|
change. Scripts can test SINGE_DISC.
|
|
|
|
- mouseSetEnabled(bool) and singeSetPauseKeyEnabled(bool) added. The old
|
|
mouseEnable/mouseDisable and singeEnablePauseKey/singeDisablePauseKey are
|
|
now aliases defined in Framework.singe.
|
|
|
|
- Handles, counts, and frame numbers are returned as Lua integers.
|
|
videoIsPlaying() returns a boolean. mouseSetMode() returns nothing.
|
|
|
|
- onControllerMoved() receives the controller index (0 to 3), not SDL's
|
|
instance ID. onMouseMoved() in MANY_MOUSE mode receives real relative
|
|
motion in its third and fourth arguments.
|
|
|
|
- Runtime errors inside callbacks now end the game with a traceback, the way
|
|
argument errors always have.
|
|
|
|
- Held keys no longer auto-repeat in MODE_NORMAL.
|
|
|
|
- The engine parks the disc on frame 1 before the script runs; the framework
|
|
no longer seeks when it is loaded, so threaded games keep their own
|
|
startup positioning.
|
|
|
|
- The pause key now pauses the whole game. The engine freezes the script
|
|
(no callbacks, no singeMain), holds sound completions, releases and later
|
|
re-presses held inputs, and draws a PAUSED indicator. It acts on the key
|
|
press, and SWITCH_PAUSE reaches the script only when the key is disabled
|
|
with singeSetPauseKeyEnabled(false). singeSetPauseFlag() still pauses
|
|
media without freezing the script. Gamepad and mouse buttons mapped to
|
|
pause, quit, screenshot, or grab now work in MODE_FULL too; keyboard
|
|
mappings stay raw there.
|
|
|
|
- Video decoding runs on its own thread per video, so a discSearch() far
|
|
from a keyframe no longer stalls the game loop; the previous frame stays
|
|
on screen until the new one is ready. The laserdisc and framefile videos
|
|
are handed to the GPU as YUV and converted there instead of on the CPU,
|
|
and each video reports its keyframe spacing to the program trace when
|
|
loaded, with a warning there when seeks may be slow.
|
|
|
|
- Constants that used to be duplicated in Framework.singe (SWITCH_*,
|
|
FONT_QUALITY_*, MODE_*, MOUSE_*, OVERLAY_*, RENDER_*, SOUND_ERROR_*) are
|
|
now defined by the engine. New: DISC_STOPPED/DISC_PLAYING/DISC_PAUSED
|
|
for discGetState(), SINGE_VERSION_MAJOR/MINOR/STRING, and the SINGE_*
|
|
input code layout that Framework.singe builds GAMEPAD_N and MOUSE_N from.
|
|
|
|
- Audio and video are kept in sync from the audio clock. The engine
|
|
measures the audio device's queue at startup instead of guessing, uses
|
|
small mixer buffers, and honours a container whose first video frame is
|
|
timestamped after its first audio sample. For delay the engine cannot
|
|
see (receivers, Bluetooth, DACs) there is singeSetAudioDelay(ms) /
|
|
singeGetAudioDelay(), the games.dat key AUDIO_DELAY, and
|
|
--audiodelay=MS; positive values mean the audio is heard late.
|
|
|
|
- The menu has an audio delay calibration screen for the machine's own
|
|
delay (DAC, receiver, Bluetooth, display). Press the service key on
|
|
the game list; a click plays once a second and the screen flashes.
|
|
Adjust with the stick until the flash and the click coincide, then
|
|
press button 1 to save. The value is kept in machine.cfg in the data
|
|
root and applied to every game on the machine, from the menu or the
|
|
command line, on top of any per-game AUDIO_DELAY. Scripts can read and
|
|
set it with singeGetAudioCalibration() / singeSetAudioCalibration(),
|
|
read the measured device queue with singeGetAudioLatency(), and now
|
|
have singeGetTicks() for a wall clock in milliseconds (os.clock() is
|
|
processor time and drifts).
|
|
- Inside a packed game a relative name with a ".." component resolves
|
|
nowhere: io.open returns nil and "NAME reaches outside the game",
|
|
io.lines, io.input and io.output raise it, lfs.mkdir and lfs.rmdir
|
|
return nil with it, and asset loads report the file as missing. The
|
|
packer already refused such names; the lookup now enforces the rule at
|
|
run time. lfs.dir on a packed directory lists its entries sorted
|
|
without regard to case.
|
|
|
|
- One rule for the data directory on every launch (command line,
|
|
games.dat, scriptPush and scriptExecute): with --datadir, or for a
|
|
packed game, a directory named for the game under the base; otherwise
|
|
the game's own directory. scriptPush of a loose game without --datadir
|
|
used to write under the current directory.
|
|
|
|
- Color components handed to lightSetColor, materialSetColor (alpha
|
|
too), materialSetEmissive, lineDraw and navDraw are clamped to 0..255,
|
|
as colorForeground and colorBackground always were; they used to wrap.
|
|
|
|
- materialDelete on the private material of a sprite or text node is an
|
|
error ("belongs to a sprite node; clear the sprite instead") instead
|
|
of freeing a slot the node still used.
|
|
|
|
- lightSetCone raises the outer angle to at least the inner angle.
|
|
|
|
- Packed games: the data directory is always <base>/<database name>/
|
|
<script directory>/. An archive whose script sat in a subdirectory
|
|
(Game/Main.singe) wrote to <base>/Game/, shared with every archive of
|
|
that layout; its saves now belong under <base>/<name>/Game/ and must be
|
|
moved by hand. Archives whose script sits at the root are unaffected.
|
|
|
|
- Packed games: a path naming the .game file itself (Games/DLe.game,
|
|
with or without a trailing separator) addresses the database root, as
|
|
"." does from inside: lfs.dir lists it and lfs.attributes reports a
|
|
directory, while io.open on it returns nil and lfs.mkdir and lfs.rmdir
|
|
refuse it. Engine-owned names (Singe/ and the data directory) may use
|
|
".." as long as they stay below that prefix; any other ".." inside a
|
|
packed game is still refused.
|
|
|
|
- lfs.attributes inside a packed game answers as the real lfs does: the
|
|
whole table (mode, size, modification and the rest) with one argument,
|
|
one field by name (an unknown name raises "invalid attribute name"),
|
|
or a table of the caller's filled in; a missing file returns nil, a
|
|
message and the error number.
|
|
|
|
- sceneProject returns false with 0, 0, 0 before the scene has rendered
|
|
a frame, instead of inf and nan.
|
|
|
|
- softNew removes the node's existing soft body before it looks at the
|
|
mesh, so a call that fails leaves the node with none; softNew on a
|
|
node carrying a rope fails with "needs a mesh" (the tube was the
|
|
engine's) instead of reading freed memory.
|
|
|
|
- navAgentNew keeps the radius and height at 0.01 or more, as navNew
|
|
does, so onNavArrived fires for an agent given a radius of zero.
|
|
|
|
- Three Hypseus names the bytecode-only games turned out to need.
|
|
controllerSetPadding(boolean) and controllerGetPadding() are accepted;
|
|
the getter always answers 0, because Hypseus's offset marks a switch
|
|
code as having come from a gamepad and Singe's mapped switches carry no
|
|
such mark. videoGetVolume() with no argument answers the laserdisc's
|
|
volume on Hypseus's 0 to 64 scale, which its games double into
|
|
musicSetVolume. require("md5") gives the PUC-Rio md5 library Hypseus
|
|
bundles, all five of sum, hexsum, exor, crypt and decrypt, so a game
|
|
written against it behaves the same here; note that library names its
|
|
sumhexa hexsum.
|
|
|
|
- The ARM64 build carries the V4L2 stateless decoders: AV1, H.264,
|
|
HEVC, MPEG-2, VP8 and VP9. These reach the hardware on the boards
|
|
whose kernel drivers use the V4L2 Request API rather than the older
|
|
memory-to-memory one -- Allwinner's Cedrus, Rockchip's rkvdec and
|
|
hantro, the Pi 5's rpivid, i.MX8's hantro -- none of which the engine
|
|
could use before. Singe tries them first on ARM, then Rockchip's MPP,
|
|
then stateful V4L2, and decodes in software when a board offers none.
|
|
Desktop platforms are untouched. The support is Jonas Karlman's
|
|
V4L2 Request patchset for FFmpeg, carried in thirdparty/ffmpeg; it
|
|
brings a runtime dependency on libudev, which these boards all have.
|
|
|
|
- The Raspberry Pi build target is now linux-aarch64 and its binary is
|
|
Singe-v3.00-Linux-aarch64. Nothing in it was ever Pi specific: the
|
|
V4L2 decoder it builds talks to any memory-to-memory device, so the
|
|
same binary serves Amlogic, Exynos, Qualcomm and other ARM64 Linux
|
|
boards whose kernel offers one. The build system now decides by
|
|
architecture, and by whether a sysroot is in use, rather than by a
|
|
platform name that stood for both. cmake/zig/piPackages.cmake is
|
|
cmake/zig/arm64Packages.cmake, and the command is
|
|
./build-all.sh linux aarch64.
|
|
|
|
- src/main.c now includes windows.h where it reads the processor name
|
|
out of the registry, which the Windows build had been failing without.
|
|
|
|
- A hardware decoded frame stays NV12 the whole way to the texture
|
|
instead of being converted to planar YUV on the CPU first. The picture
|
|
is identical: the GPU does the same conversion when it samples either
|
|
way. Measured on an RX 7900 XTX at 720x480 the difference was inside
|
|
the noise, so this is for the small ARM machines where a per-frame
|
|
conversion is worth more than it is on a desktop; it has not been
|
|
measured on one.
|
|
|
|
- Rockchip's rkmpp decoders are built and used when the Rockchip Media
|
|
Process Platform library is present, and ignored everywhere else. The
|
|
superbuild probes for rockchip_mpp with pkg-config rather than asking
|
|
for --enable-rkmpp unconditionally, which would fail every configure on
|
|
a machine without it. Untested: no Rockchip hardware here.
|
|
|
|
- The manual now says plainly that the Hypseus API is for compatibility
|
|
and not for new games, in the Lua API Reference introduction and again
|
|
at the head of the Hypseus Compatibility section. Every Hypseus name,
|
|
and every Hypseus argument form on a function that otherwise takes
|
|
Singe's own, says so in its own entry.
|
|
|
|
- videoSetVolume(volume), with one argument instead of three, sets the
|
|
laserdisc's volume on Hypseus's 0 to 64 scale, the twin of the bare
|
|
videoGetVolume(). A level outside that range is ignored rather than
|
|
fatal, as in Hypseus.
|
|
|
|
- drawTransparent(boolean), getUserString(), mouseGetPadding() and
|
|
spriteBlend(boolean, sprite) are registered, completing the set of
|
|
names Hypseus's engine exposes. All four are compatibility answers:
|
|
Singe carries transparency in the alpha channel rather than a colour
|
|
key, sends no statistics and so has no machine ID to report, adds no
|
|
offset to mouse input codes, and never has blending switched off.
|
|
|
|
- allowSocketCall() and setOverlayFullAlpha() are registered so a game
|
|
that calls them does not stop on a missing function. The first always
|
|
answers false: Hypseus reports whether the user let it send data to a
|
|
stats server, and Singe sends nothing anywhere. The second is accepted
|
|
and ignored: it asks for an overlay that blends its alpha channel
|
|
rather than thresholding it, which Singe's overlay has always done.
|
|
|
|
- The other Lua modules Hypseus exposes are here too, whether or not a
|
|
game asks for them: require("bit") is LuaBitOp, require("random") a
|
|
Mersenne Twister generator, require("cjson") Lua CJSON and
|
|
require("lpeg") LPeg. Singe already had the rest of Hypseus's list
|
|
(lfs, socket, rs232) and opens every standard Lua library besides.
|
|
|
|
- materialSetCutoff(material, cutoff) discards the texels whose base
|
|
colour alpha falls below the cutoff, in the lit pass and in the shadow
|
|
pass alike, so cutout foliage casts cutout shadows. What survives the
|
|
cutoff is drawn fully opaque, which is what glTF means by MASK as
|
|
against BLEND: the sampled alpha is not passed on, so nothing behind
|
|
the scene shows through a leaf. glTF's MASK alpha mode now sets the
|
|
cutoff from the file's alphaCutoff instead of warning that the material
|
|
would be drawn opaque; BLEND and OPAQUE are unchanged. Materials that
|
|
never set a cutoff keep drawing every texel.
|
|
|
|
- --deterministic[=MS] runs the engine on a virtual clock stepped MS
|
|
milliseconds (default 15) once a frame instead of on real time, steps
|
|
the disc one video frame a frame with it, and seeds Lua's generator
|
|
and the engine's from the same number, so a screenshot taken at a
|
|
fixed frame number is byte identical between runs. It is a testing
|
|
facility: audio and pacing are meaningless in it. Settable as
|
|
deterministic in settings.cfg. With the option absent nothing
|
|
changes.
|
|
|
|
- Every image format the bundled FFmpeg can decode now loads, not only
|
|
the ones SDL_image reads: TIFF, OpenEXR, JPEG 2000, DDS, PSD, DPX,
|
|
and AVIF and HEIC through a vendored dav1d. spriteLoad,
|
|
spriteLoadData, spriteLoadFrames, meshHeightmap, --bezel artwork,
|
|
glTF embedded textures and the GUI's images all take the same path:
|
|
SDL_image first, FFmpeg for whatever it turns down. sceneSetSky
|
|
gains the same fallback and keeps a floating point source's range, so
|
|
an OpenEXR sky is lit correctly rather than clipped.
|
|
|
|
- AV1 video plays. FFmpeg's own AV1 decoder is hardware only and said
|
|
"Your platform doesn't support hardware accelerated AV1 decoding" on
|
|
any machine without an AV1 capable GPU; the vendored dav1d is the
|
|
software decoder, and it is what brings AVIF with it.
|
|
|
|
- Every audio format the bundled FFmpeg can decode now loads as a sound
|
|
or as music: AAC, ALAC, AC-3, WMA, APE, TTA, Speex and AMR among
|
|
them. An AAC track used to play when it was the audio of a video and
|
|
fail when it was a sound effect. Chiptunes arrive with a vendored
|
|
game-music-emu (NSF, NSFE, SPC, VGM, GBS, AY, GYM, HES, KSS and
|
|
SAP), and the tracker
|
|
formats libxmp was already reading -- MOD, S3M, XM, IT and fifty-odd
|
|
others -- are now documented rather than merely present.
|
|
|
|
- MIDI files play, synthesised with TinySoundFont. Singe ships no
|
|
sound bank, because a good one is tens of megabytes for a format
|
|
almost no game uses: name one with --soundfont FILE, put one at
|
|
Singe/soundfont.sf2 so a packed game carries its own, or let it find
|
|
the one your distribution installed. The trace header says which was
|
|
used. SDL_mixer's own MIDI decoder wants a GUS patch set almost
|
|
nobody has, so a .mid failed to load while the trace claimed a MIDI
|
|
decoder was there.
|
|
|
|
- MIDI ports, in and out, for a cabinet driving a real sound module or
|
|
reading a keyboard or a control surface as an input device:
|
|
midiInputCount, midiInputName, midiOpenInput, midiCloseInput,
|
|
midiIsInputOpen and the same five for output; midiSend for any
|
|
message at all; midiNoteOn, midiNoteOff, midiProgramChange,
|
|
midiControlChange and midiPitchBend; midiRescan for a device plugged
|
|
in while the game runs; and an onMidiMessage(status, data1, data2,
|
|
bytes) callback. Channels are 1 to 16. ALSA's sequencer on Linux,
|
|
CoreMIDI on macOS, the multimedia MIDI calls on Windows. On Linux
|
|
the library is opened at run time, as SDL opens the same one for
|
|
audio, so a machine without it simply has no ports.
|
|
|
|
- spriteLoad(name, width, height) rasterises a vector picture -- an SVG
|
|
-- to fit that box, keeping its proportions, instead of taking the
|
|
file's own size, which for an icon is usually 16 or 24 pixels. Every
|
|
other format ignores the size.
|
|
|
|
- --deinterlace off|auto|on, automatic by default, for a laserdisc rip
|
|
that kept its interlaced fields and combs on a progressive display.
|
|
Automatic touches only the frames a file marks interlaced, so a
|
|
progressive disc pays nothing; on is for a file whose flags are
|
|
wrong. Settable as deinterlace in settings.cfg.
|
|
|
|
- Fonts gain what FreeType was built without: WOFF and WOFF2, colour
|
|
bitmap glyphs, colour vector glyphs, and complex script shaping
|
|
through HarfBuzz, so Arabic, Hebrew and the Indic scripts join and
|
|
order correctly instead of coming out as unjoined letters.
|
|
|
|
- A framefile opens its segments as the disc reaches them instead of
|
|
opening every one at startup. A long framefile -- typing-md2 has 213
|
|
segments -- held a demuxer open for each, which cost hundreds of file
|
|
descriptors and could push a game past the 1024 that glibc's select
|
|
refuses to look past, aborting it inside a fortify check. At most
|
|
four segments are open now: the one playing, the one after it (opened
|
|
ahead so the changeover costs nothing), and room for a seek to move
|
|
between two. Measured on a twelve-segment game, 75 open descriptors
|
|
became 24. A framefile naming a file that does not exist still fails
|
|
at startup, as it always has.
|
|
|
|
- The game kit: five things every game was writing for itself.
|
|
|
|
saveGet, saveSet, saveGetAll, saveSetAll, saveDelete, saveClear and
|
|
saveFlush keep a table between runs, as JSON in the game's own data
|
|
directory. Values may be numbers, strings, booleans or tables of
|
|
them, nested; whole numbers come back whole. Tables are copied in and
|
|
out, so what you set is what is saved and what you read back is yours
|
|
to change. saveGetAll and saveSetAll read and replace the whole save
|
|
in one step, which is how an old save is moved to a new layout without
|
|
a moment in between where it is empty. One write at the end of any
|
|
frame that changed something, and again at shutdown; saveFlush writes
|
|
now, for a cabinet that may lose power. The file is renamed over the
|
|
old one, so a write that is interrupted leaves the previous save
|
|
rather than half of a new one.
|
|
|
|
timerAfter, timerEvery, timerCancel and timerIsActive run something
|
|
later without counting frames. They use the engine's clock, which is
|
|
the virtual one under --deterministic, so a timed game repeats
|
|
exactly, and they fire with the rest of the frame's callbacks.
|
|
|
|
tweenValue moves a number from one value to another over time on a
|
|
curve, with tweenCancel and tweenIsActive beside it and twenty-two
|
|
EASE_ curves to pick from. It hands the number to your function
|
|
rather than moving a node itself, so it works on anything.
|
|
|
|
collideRects, collideCircles, collideRectCircle, collidePointRect,
|
|
collidePointCircle, collidePointPolygon and collideSegments answer
|
|
whether two flat things touch. Plain functions, no handles. Jolt
|
|
already answered this in three dimensions; nothing answered it for the
|
|
overlay, where light gun hitboxes live.
|
|
|
|
statsEnable and statsIsEnabled show the developer's overlay: frame
|
|
time and its worst, frame rate, Lua memory, what is alive, how many
|
|
draw batches, and what the disc is doing. Drawn with SDL's own debug
|
|
font, so it needs no asset, and drawn after singeScreenshot takes its
|
|
picture, so it never lands in a screenshot.
|
|
|
|
- Four more Lua libraries are bundled: middleclass for classes, lume
|
|
for the small things (lerp, clamp, round, shuffle, split, serialize),
|
|
inspect for printing a table in a readable shape, and bump for moving
|
|
things that must not pass through each other -- which is what the
|
|
collide calls deliberately do not do.
|
|
|
|
- Player handles. The name on a leaderboard is a handle, not an email
|
|
address: three to twenty characters of letters, digits, hyphens and
|
|
underscores, chosen once. An operator can rename somebody; a player
|
|
cannot, and an email address cannot be changed at all. Handles are
|
|
checked against a list the operator maintains -- identities nobody
|
|
should claim, and words with no place on a screen in a public room.
|
|
|
|
- Online: accounts, a game catalogue, and high scores. A cabinet can sign
|
|
in to a master service (master.singeengine.com by default), browse every
|
|
game the service offers, download and update them, and post scores to
|
|
leaderboards. Two new service tools cover the setup: Online Account
|
|
signs in, creates an account, recovers a password and sets the server
|
|
address; Get Games lists the catalogue, marks what is installed here,
|
|
and downloads, updates or removes it.
|
|
|
|
A game posts a score in one line -- scoreSubmit(points) -- plus
|
|
scoreBoard, scorePlayerName, scoreWaiting and scoreUpdate. The account
|
|
belongs to the machine rather than the game, so a game uses whatever
|
|
sign-in the menu made. A score is queued to disk and sent when there is
|
|
a connection: a cabinet is often offline, and a game must never stall
|
|
on the wire to show its own board.
|
|
|
|
Nothing blocks a frame. Singe/Net.singe is an asynchronous HTTP client
|
|
driven from the frame loop, because Lua here is cooperative and
|
|
ssl.https.request would stutter a JSON call and make a large download
|
|
look like a hang. It does not use LuaSec's https module at all: that
|
|
ships with verify = "none", which encrypts the connection and
|
|
authenticates nobody. A connection is verified against a pinned public
|
|
key instead.
|
|
|
|
A download is checked against the digest the catalogue published before
|
|
it replaces anything, so a transfer that arrives wrong fails rather than
|
|
installing a game that will not run. Downloads resume.
|
|
|
|
New engine calls: utilSha256(data) for exactly that checking, and
|
|
singeGetGameId() for the GAME_ID a games.dat entry can now carry, which
|
|
is what a leaderboard is kept under. A game without one can be played
|
|
but not ranked.
|
|
|
|
A game the service withdraws keeps working on the cabinets that have it.
|
|
All that stops is being offered an update, and the menu shows it as
|
|
"installed, no longer offered" so it can still be removed from there.
|
|
|
|
scoreBegin() tells the service a play is starting, and the score that
|
|
follows carries how long it took measured on the server's own clock.
|
|
That is the one number in a submission that has not been through the
|
|
player's machine. Nothing is rejected on it -- it is shown to the
|
|
operator beside the score, and the judgement stays a person's.
|
|
|
|
A submitted score is a claim and the design says so: the cabinet is the
|
|
player's own machine. The service records who claimed what, rate limits
|
|
submissions, and flags outliers for a person rather than pretending to
|
|
verify them.
|
|
|
|
- One menu, two renderers. Singe/MenuClassic.singe is gone: it was the
|
|
whole menu written a second time, and the two copies duplicated their
|
|
game list, their selection, their menu.dat handling and their games.dat
|
|
walk almost line for line. Singe/Menu.singe is now everything the menu
|
|
is and draws nothing, and it picks one of two renderers at startup from
|
|
the new singeHasGpu(): Singe/MenuDocument.singe for a machine with a
|
|
GPU device, Singe/MenuOverlay.singe for one without. A machine with no
|
|
Vulkan, Direct3D 12 or Metal driver therefore gets the menu -- and all
|
|
ten service tools -- without anyone editing a launcher, which is what
|
|
editing Menu.sh used to be for.
|
|
|
|
Both renderers take the same keys, so the overlay one now moves the
|
|
selection with up and down and pages with left and right, as the
|
|
document one does; it shows one game at a time rather than a list, and
|
|
clips a description too long for its column rather than scrolling it.
|
|
MENU_OVERLAY = true in menu.dat forces it on a machine that has a GPU.
|
|
|
|
singeHasGpu() answers whether the GPU device came up. guiNew and the
|
|
scene calls end the script rather than returning false, so a game that
|
|
can do without one had no way to ask.
|
|
|
|
- The service tools. Pressing the SERVICE key on the bundled menu now
|
|
opens a list of ten screens rather than the audio delay screen alone:
|
|
Audio Delay, Input Test, System Information, Light Gun, Sound Test,
|
|
Display, Disc Test, Saved Data, MIDI Ports and Frame Statistics.
|
|
Button 2 backs out of a tool, the service key leaves. They live in
|
|
Singe/Tools.singe, which the engine extracts beside Menu.singe. Nothing
|
|
there draws: a tool builds a list of rows and the menu draws them, so
|
|
the same ten run on both renderers and a cabinet builder adding one
|
|
writes no drawing code at all.
|
|
|
|
Four calls were added for them, and are worth having on their own:
|
|
|
|
singeGetSystemInfo() returns everything the trace header knows in one
|
|
table -- version, operating system, processor, renderer, 3D device,
|
|
video decoders, audio formats, SoundFont, MIDI, window and canvas
|
|
sizes, this game's data directory and the data root every game writes
|
|
under -- so a bug report can be a photograph.
|
|
|
|
vldpGetShift() and vldpSetShift(x, y) move the picture within the
|
|
window while the game runs, the running equivalent of --shiftx and
|
|
--shifty, beside vldpGetScale and vldpSetScale.
|
|
|
|
singeSaveGeometry() keeps the scale, shift and rotation for this
|
|
machine, in the same machine.cfg the audio delay uses, and the engine
|
|
applies them to every game it starts afterwards unless the command
|
|
line names its own. A monitor is squared up once, not once per game.
|
|
The file is called machine.cfg rather than audio.cfg, because the
|
|
audio delay is no longer all it holds.
|
|
|
|
discGetFrameCount() answers how many frames the disc has. A framefile
|
|
answers for the whole disc rather than the segment playing.
|
|
|
|
SINGE_CONTROLLER_AXES and SINGE_CONTROLLER_BUTTONS join the input
|
|
layout globals, so a service screen can walk a pad without knowing the
|
|
numbers.
|
|
|
|
- Subtitles carried inside the video file: discGetSubtitleTracks(),
|
|
discGetSubtitleLanguage(track) and srtLoadTrack(track), which reads
|
|
one track out of the disc's own container and loads it exactly as
|
|
srtLoad loads a .srt beside the game. SubRip, WebVTT, ASS and MOV
|
|
text are read, with ASS override tags stripped; picture subtitles
|
|
(VobSub, PGS) are counted so the numbering matches other players, but
|
|
cannot be read, because there are no words in them.
|
|
|
|
|
|
|
|
Fixes
|
|
-----
|
|
|
|
- A machine with no enumerable mouse keeps its pointer. ManyMouse reads
|
|
/dev/input/event* directly where there is no X server, and those nodes
|
|
are root:input, so an account that was never added to the "input" group
|
|
enumerated nothing -- and Singe then switched off all mouse input,
|
|
including SDL's own pointer, which single-mouse mode is the only user
|
|
of. On a KMSDRM cabinet or handheld that left the mouse and the light
|
|
gun dead with only "No mice detected" to explain it. Only --manymouse
|
|
needs those devices now, and the message names the group.
|
|
|
|
- The menu's intro is drawn, not played, and runs straight into the menu.
|
|
A charge goes off, the Singe dragon and name fly out of it toward the
|
|
camera with the dragon's wings beating, the tagline fades in, the dragon
|
|
rears and breathes flame -- and then the grid the menu sits on comes up
|
|
out of the dark beneath the logo while the smoke is still clearing, the
|
|
logo lifts away, and the menu takes the screen. The dragon settles
|
|
centred on the sun rather than above it, with the name below crossing the
|
|
horizon, so the sun is a disc behind the animal instead of a rim around
|
|
the pair of them. It is one scene from the
|
|
first frame to the last: there is no cut and nothing loads at the join,
|
|
and the menu takes input when the logo has gone rather than when a video
|
|
frame number passes. Returning from a game starts the same timeline past
|
|
the end of the intro, so it lands on the settled backdrop. The two
|
|
models it is made of are shipped with the engine like the font and the
|
|
logos.
|
|
|
|
- The menu background video is a recording of that same backdrop, so the
|
|
machine with no GPU sees what everybody else draws instead of a
|
|
different intro and a different grid. util/renderMenuVideo.py makes it:
|
|
a headless Singe draws Singe/Backdrop.singe on a virtual clock that
|
|
moves a fixed number of milliseconds a frame, shoots every one of them,
|
|
and the frames become assets/menuBackground.mkv. Nothing in the script
|
|
knows how long the intro runs or how fast the grid moves -- it asks the
|
|
backdrop, which is the only way the recording and the drawing cannot
|
|
drift apart -- and it stops after a whole number of turns of the grid,
|
|
so the tail the menu plays over and over meets itself exactly. The two
|
|
clips the old video was cut from are gone, one of which nobody could
|
|
account for.
|
|
|
|
- The menu has a sound. util/makeMenuSound.py writes it -- every noise in
|
|
it is an oscillator, a burst of noise or an envelope over one of the two,
|
|
in a room that is a convolution with a tail of decaying noise, so there is
|
|
nothing in it that cannot be accounted for either. The charge, the rush
|
|
of the logo arriving, the wingbeats (on the same curve the animation flaps
|
|
to), the flame, and a riser as the grid comes up, over a bed whose four
|
|
bars land exactly where the menu takes the screen.
|
|
The charge and the flame are built the way the things themselves are
|
|
heard rather than the way they are drawn. Both were made of smooth
|
|
envelopes over steady noise -- the flame of a band of noise wobbled by two
|
|
sine waves, which is the textbook fake fire -- and nothing about either is
|
|
smooth, steady or periodic. The blast is now a crack, a slam through the
|
|
middle of the bass (the band a small speaker turns into "loud", where the
|
|
sub it had before moves no air at all), a drop that falls away twice over
|
|
and is saturated so its harmonics carry the pitch a cabinet speaker cannot
|
|
make, a body that flickers at random as its top closes, and rubble coming
|
|
down after it; the fire is a roar whose loudness wanders at random, a
|
|
resonance that wanders with it, and the spitting inside it, all surging
|
|
together. Everything else ducks under the blast and comes back over a
|
|
quarter of a second, because punch is contrast, and the limiter only bends
|
|
the very top so the loudest moment keeps the shape it was given. Then it fades out: music under a menu waiting for someone
|
|
to choose a game wears out its welcome, so the last seconds of the intro
|
|
are the grid running on while the sound leaves, and nothing loops. The
|
|
The menu plays the file itself, over whichever renderer is drawing, so
|
|
there is one copy of it: muxing it into the recording as well would be
|
|
the same nine seconds of sound shipped twice inside one binary.
|
|
|
|
- The dragon model is rigged. util/objToGlb.py takes --pivots, a JSON
|
|
naming where each object's own origin should sit, and moves the vertices
|
|
so the node carries it back as a translation. assets/DragonPivots.json
|
|
gives the dragon its joints, so wingL turns at the shoulder and head at
|
|
the top of the neck; before, every part's node sat at the model's origin
|
|
and turning a wing swung it around the middle of the animal. The model
|
|
looks identical -- no part moves by so much as a thousandth -- but it can
|
|
now be animated without rigging it again in script each time.
|
|
|
|
- A game whose art is missing shows that it is missing, rather than taking
|
|
the engine down with it. An empty or wrong decorator path could open
|
|
something that is not a file, report a nonsense length, and throw
|
|
std::bad_alloc out of the GUI's texture loader -- a typo in a games.dat
|
|
ended the program. The loader now refuses an impossible size, and both
|
|
menu renderers fall back to Singe/missing.png, drawn at build time, so a
|
|
bad path reads as "MISSING art" instead of a hole in the page.
|
|
|
|
- GUIs and 2D particles no longer blink. guiDraw and emitterDraw are
|
|
documented as lasting one frame, meaning one onOverlayUpdate -- but their
|
|
queues were emptied after every rendered frame, and the script's turn is
|
|
throttled to FRAME_TICK_MS while the display refreshes as often as it
|
|
can. With a 3D scene enabled it refreshes continuously, so the GUIs and
|
|
the 2D particles were drawn only on the frames the script happened to run
|
|
on: in the menu, a game list flickering over a grid that did not. The
|
|
queues are emptied when the script gets its turn instead.
|
|
|
|
- The menu shows packed games properly. A .game is a database the engine
|
|
presents through the VFS, so lfs.attributes reports it as a directory,
|
|
not a file -- and the menu tested the mode before the extension, so every
|
|
container went down the loose-directory branch and its games.dat was
|
|
loaded with no container recorded. Every CABINET, MARQUEE and ATTRACT
|
|
path in it then named a file that exists only inside the database: a
|
|
library of .game files showed no art at all and died with "Unable to
|
|
open" on the first attract video. The extension is tested first now.
|
|
Games packed with earlier releases are correct and do not need repacking.
|
|
|
|
- A lone .m2v played with --framefile now finds the .ogg beside it, as a
|
|
framefile's segments always have. A Daphne era elementary stream
|
|
carries no audio of its own, so lair.m2v played silently unless it was
|
|
listed in a framefile or --altaudio was given. Any other container
|
|
keeps using the audio inside it.
|
|
|
|
- A single-frame image saved as a 1-bit PNG (many overlay assets from
|
|
the 2.x era) loads again; spriteLoad reported "Blit combination not
|
|
supported" on it. Images with 16 bits per channel or floating point
|
|
pixels (16-bit PNGs among them) load as sprites, animation frames and
|
|
particle textures; they ended the program.
|
|
|
|
- lfs.dir(".") and other names for the root work inside a packed game;
|
|
they were refused as if the directory did not exist.
|
|
|
|
- --sindengun and --scalefactor are listed in --help and the manual;
|
|
they were hidden because mouse positions ignored the Sinden border and
|
|
the overscan shrink. Positions now map into the picture actually
|
|
drawn, so a light gun lands where it points inside the border. The
|
|
border is drawn the right way round (black ring outside, white ring
|
|
inside, the game in the middle); one too wide for the video ends the
|
|
program with a message instead of a zero-sized game; and light guns
|
|
and tablets reporting absolute positions through ManyMouse are mapped
|
|
into the picture like the mouse.
|
|
|
|
- The manual's 3D Scenes, Physics and Particles chapters are rewritten as
|
|
sections in a learning order with worked examples, every reference
|
|
entry now shows each form the call accepts with a realistic example,
|
|
and prose spelling is American throughout. Manual.pdf and Manual.html
|
|
are rendered into .builddir/ by the build and by build-docs.sh alike,
|
|
and the binary embeds the PDF from there.
|
|
|
|
- controllerGetButton() never worked; it rejected every framework button
|
|
code.
|
|
|
|
- The five argument form of spriteDraw() always failed.
|
|
|
|
- spriteSetFrame() on a still image crashed.
|
|
|
|
- Unloading the selected font left later fontPrint() calls using freed
|
|
memory.
|
|
|
|
- overlayCircle() drew around the origin instead of the requested center.
|
|
|
|
- overlaySetResolution() broke mouse coordinates and vldpGetPixel() because
|
|
the overlay scale was computed with integer division.
|
|
|
|
- mouseGetPosition() always returned 0,0 and controllerGetAxis() read the
|
|
wrong controller because the axis cache was indexed two different ways.
|
|
|
|
- Analog stick release events fired with a stale input code. Controllers
|
|
that were unplugged and replugged shifted their codes.
|
|
|
|
- The -o/--audio option was lost when the configuration was copied.
|
|
|
|
- --option=value forms of numeric options, and options after the script
|
|
name, were not accepted.
|
|
|
|
- Running "singe game.singe" from the game's own directory wrote its data
|
|
to the filesystem root.
|
|
|
|
- Framefile playback never advanced past the first segment, and switching
|
|
segments dropped the selected audio track.
|
|
|
|
- Video timing accumulated main loop latency, drifting behind the audio.
|
|
Audio is now the master clock: the picture follows the samples the sound
|
|
device has actually consumed, so the two cannot drift apart and the
|
|
mixer's buffering latency is accounted for. Silent videos follow the wall
|
|
clock. The first frame after a seek was shown one update late.
|
|
|
|
- Video without an audio track crashed on the first update.
|
|
|
|
- The video audio stream was shared with the mixer thread without a lock.
|
|
|
|
- onSoundCompleted was called from the audio thread. It is now delivered
|
|
from the game loop.
|
|
|
|
- Screenshots read the frame buffer through the window surface, which SDL
|
|
forbids alongside a renderer, and could corrupt memory on shutdown.
|
|
|
|
- .patch archives crashed the installer. Archives without an explicit top
|
|
level directory entry were rejected. Extraction now refuses paths that
|
|
escape the installation directory.
|
|
|
|
- The Sinden gun arguments could overflow the configuration structure.
|
|
|
|
- Support files in the Singe directory are refreshed when the installed
|
|
copy differs from the running build. Upgrading the binary used to keep
|
|
the old Framework.singe and Menu.singe forever.
|
|
|
|
- Lua reads a packed game where it lies. Its file handle is a C FILE * and
|
|
a database row is not a file, so anything a script opened by name inside
|
|
a .game was written out to data/<game>/cache first and the copy opened
|
|
instead -- a second copy of the game on disk, made a file at a time.
|
|
Everything else in the engine had long since stopped needing that: the
|
|
sprite loader, the decoders and the GUI's file interface all read a row
|
|
as a stream. Only Lua could not, because the io library is not ours.
|
|
It is patched now rather than worked around: io.open, io.lines and
|
|
io.input hand back a handle of Singe's own for a name that lives only in
|
|
the database, and it reads through the same stream everything else uses.
|
|
It answers to every method and format Lua's does, io.type calls it a
|
|
file, io.read and io.close take it, and it closes itself on collection or
|
|
on leaving a <close> variable's scope; writing to it is an error, which
|
|
is what a packed asset is. testScripts/packedIo holds all of that to
|
|
Lua's own behaviour. With the last reader of the cache gone, the cache
|
|
itself is gone: vfsFilePath answers with a path only when there is a
|
|
real file, and a packed name being read is asked for as a stream.
|
|
|
|
- The menu no longer unpacks a library's artwork to look at it. It asked
|
|
whether each picture was there by opening it, and io.open has to hand Lua
|
|
a file the C library can read, so every cabinet and marquee in a packed
|
|
library was written out to data/<game>/cache first -- none of which was
|
|
ever opened that way: spriteLoad and the document's texture loader both
|
|
read straight out of the database. The question is asked of the index
|
|
now, through lfs.attributes, which opens nothing. The cache remains for
|
|
what it is for: a game's own script opening its own files by name.
|
|
|
|
- A packed game writes to one data directory. Everything that named a file
|
|
inside a .game by a path that passes through it -- a script opening one
|
|
of its own videos, which is how the menu loads an attract clip -- got a
|
|
data directory named after the container including the extension, while
|
|
the container itself, and everything else the same game wrote, used the
|
|
name without it. A library of packed games therefore grew two data
|
|
directories each: the video indexes in one and the saves, the overlay
|
|
and the cache in the other. Any component naming a database is reduced
|
|
to its stem now, so both land in the same place. An installation that
|
|
has both may delete the ".game" one; what was in it is rebuilt.
|
|
|
|
- Both menus scroll their details. When a games.dat entry has more to say
|
|
than fits, the text walks down by itself a line at a time and rests at
|
|
each end: up and down belong to the library, and a cabinet has no key to
|
|
spare for scrolling a paragraph. The document renderer's description box
|
|
had a scroll bar the style sheet gave it and nothing to drive it with;
|
|
the overlay renderer clipped and left it at that.
|
|
|
|
- The overlay menu -- the one a machine with no GPU gets -- shows what a
|
|
games.dat says about a game. Its details had the attract video's column
|
|
and whatever height was left under it, which at 720x480 was four lines,
|
|
so the description used most of them and every fact about the game fell
|
|
off the bottom. They run the full width now, under the artwork, with the
|
|
short fields grouped onto shared lines the way the document renderer
|
|
groups them and the description last rather than first. Cabinet and
|
|
marquee artwork is scaled to fit its region as well: spriteDraw blits at
|
|
the picture's own size, so art larger than the space allowed for it used
|
|
to be drawn straight over whatever was beside it.
|
|
|
|
- The overlay menu says which key does what, along the bottom, as the
|
|
document menu always has. There was no way to learn that up and down
|
|
move one game and left and right move a page except to be told.
|
|
|
|
- Left and right move a third of the library, or a screenful, whichever is
|
|
smaller. A page in a library of nine games was ten, so every press
|
|
landed on the first game or the last and nothing between them could be
|
|
reached that way at all.
|
|
|
|
- The menu's Start button is gone. Nothing but a mouse could ever reach it
|
|
-- the document takes no keyboard focus, and a cabinet has no pointer --
|
|
and the line beside it already says which button starts a game. Clicking
|
|
a game's row still starts it.
|
|
|
|
- The menu background video carries a keyframe every half second, and the
|
|
first frame of its looping section is one of them, so the menu's jumps
|
|
into and around it are instant. It has no audio track and is compressed
|
|
harder than the clips it replaced -- flat colour with hard edges is easy
|
|
to encode -- so what was 26 MB of source clips and 1.3 MB inside the
|
|
binary is now 0.9 MB of picture and 1.1 MB of sound.
|
|
|
|
- Framefiles whose first line is a relative directory produced garbage
|
|
video paths (the directory string was freed before use); a lone "." now
|
|
means the framefile's own directory.
|
|
|
|
- A crash prints a backtrace to the console (Linux and macOS) so a report
|
|
can say where it happened; run with --program and send trace.txt too.
|
|
|
|
- Mouse positions are delivered in the video's coordinates again (SDL3
|
|
stopped scaling them for us), so crosshairs follow the pointer.
|
|
|
|
- os.clock() in scripts now returns wall time rather than processor time.
|
|
Nearly every existing game times its input debounces and prompts with
|
|
it; once the GPU decodes the video the engine mostly sleeps, and those
|
|
timers ran seconds slow. A game that wanted processor time is not
|
|
known to exist. This diverges from stock Lua and from other Singe
|
|
runtimes; new code that must be portable should use singeGetTicks().
|
|
|
|
- A key or button still held when a game exits no longer counts as a
|
|
press in the script that follows. Confirming Exit in a game with
|
|
button 1 used to relaunch it from the menu.
|
|
|
|
- Audio ran late by the device queue depth on most Linux audio servers:
|
|
the clock assumed one mixer buffer of latency where PulseAudio and
|
|
PipeWire hold several. See the audio sync entry above.
|
|
|
|
- Windows builds now carry the icon and version resource, with the version
|
|
number encoded correctly.
|
|
|
|
- The build no longer depends on lyx, upx, or xxd; embedded resources, the
|
|
version header, and the manual are generated by CMake. The manual is
|
|
embedded again and extracted as Singe/Manual.pdf.
|
|
|
|
- The licence Singe is under and the notices its libraries and assets
|
|
require are embedded and extracted as Singe/COPYING and
|
|
Singe/LICENSES, the way the manual is. Nothing in the build copied
|
|
them beside the binary, so a download carried the program without the
|
|
terms it is offered under.
|
|
|
|
- One command builds Singe from a clean checkout on a Debian based
|
|
system: ./build-all.sh <platform> <arch> installs the host packages,
|
|
then CMake fetches zig (the compiler for every target), the Pi's
|
|
platform packages and the macOS SDK into .builddir/toolchains and
|
|
builds every library and Singe itself. The results are a Linux binary
|
|
that runs on glibc 2.28 or newer, a Windows 10 or newer binary, a
|
|
64-bit Raspberry Pi OS (bullseye or newer) binary and a macOS 13 or
|
|
newer universal (Apple silicon and Intel) binary. The toolchains repository is no longer
|
|
needed. See INSTALL.
|
|
- On a plain video disc, discSearch, discSkipToFrame, discSkipForward,
|
|
discSkipBackward, discStepForward and discStepBackward clamp at the
|
|
first and last frame instead of wrapping around the disc.
|
|
discStepForward and discStepBackward are ignored while the disc is
|
|
stopped, like the skips; they used to move the frame behind the blue
|
|
screen. On a framefile the skips apply to the frame number
|
|
discGetFrame reports, so a skip in any segment after the first lands
|
|
where it should.
|
|
|
|
- Every collision and trigger event of a frame reaches onCollision and
|
|
onTrigger; only the first 64 used to, the rest were dropped.
|
|
onTrigger fires once per body and trigger pair even when a mesh or
|
|
compound shape touches in several places. playerJump is granted
|
|
whenever the player is on the ground.
|
|
|
|
- playerSetEnabled(node, false) removes the player's body from the
|
|
world and reports it leaving every trigger it stood in; enabling puts
|
|
it back. navPath ends a partial path at the last reachable point.
|
|
navLoad rejects data that is not a Detour mesh, or a file shorter
|
|
than the mesh its header describes ("the navigation mesh file is
|
|
truncated or damaged"). The world holds at most 4096 bodies; bodyNew
|
|
raises an error when it is full.
|
|
|
|
- A body, player, ragdoll, soft body or vehicle whose node was deleted
|
|
is released at the start of the next frame, disabled, inactive, static
|
|
or paused or not; a disabled player or an inactive ragdoll on a deleted
|
|
node kept its slot (16 players, 8 ragdolls) forever. playerExists,
|
|
softExists, ragdollExists, vehicleExists and every setter answer false
|
|
for a node handle that was deleted and reused instead of driving the
|
|
previous owner's record. bodySetTrigger(node, false) no longer
|
|
swallows the leave of a trigger the node is standing in.
|
|
physicsSetDebug's mask is cleared by a reload.
|
|
|
|
- Sprite nodes are no longer culled early at the edge of the view, and
|
|
shadow fitting sees their real size. Soft particles fade correctly in
|
|
a scene with no meshes instead of against stale depth. Point-light
|
|
shadows update when a caster's mesh is rewritten (soft bodies), given
|
|
morph targets, or swapped with nodeSetMesh at an unchanged transform;
|
|
the six cached faces could freeze in the old shape. Cascaded sun
|
|
shadows appear in render-to-texture views (viewNew) instead of
|
|
vanishing; a view takes the finest cascade that covers each point.
|
|
Debug lines no longer pile up while the scene layer is enabled but has
|
|
no target.
|
|
|
|
- modelLoad refuses a skin with more than 128 joints or a skinned mesh
|
|
naming a joint numbered 128 or more (the shader used to read past the
|
|
matrices); a joint beyond the skins that use the mesh is traced and the
|
|
load goes on, so a mesh shared by skins of different sizes loads. A
|
|
texture that cannot be decoded, or a missing KTX2 image, is traced and
|
|
the load goes on without it instead of leaving modelLastError set.
|
|
KHR_materials_emissive_strength is applied. Sparse accessors load,
|
|
animation keyframes included, so Blender shape keys are no longer
|
|
silently zero; a sampler that cannot be read is traced and skipped,
|
|
and a rotation channel with a malformed output no longer produces NaN
|
|
transforms. Alpha MASK materials, textures on TEXCOORD_1 and up, and
|
|
KHR_texture_transform are traced as unsupported.
|
|
|
|
- After a script reload, or any stall longer than a tenth of a second,
|
|
model animations advance by at most a tenth of a second instead of the
|
|
whole gap; a non-looping clip used to be finished before its first
|
|
frame. A 3D emitter whose node is deleted is removed even when the
|
|
handle is reused by a later nodeNew; it used to jump to the new node.
|
|
|
|
- Video index files are named <name>-<hash>.index, the hash from the
|
|
video's full name, so two videos with one base name in a game stop
|
|
rebuilding each other's index. Old <name>.index files are ignored and
|
|
can be deleted.
|
|
|
|
- The packer reports every forbidden file in a directory rather than
|
|
stopping at the first. --unpack refuses a database whose stored names
|
|
would land outside the target directory. Launching a .game database
|
|
from the command line no longer crashes.
|
|
|
|
- --stretch hands the script mouse positions in the game's coordinates
|
|
at any window size. --scalefactor with --sindengun shrinks the picture
|
|
inside the Sinden border instead of being cancelled by it. A negative
|
|
--audio track number is ignored. With no --datadir the controls.cfg
|
|
beside the script is read once, not twice.
|
|
|
|
- A reload (F5, --reload, singeReload) ignores keys and buttons held
|
|
across it until they are released, resets the effect channels and their
|
|
pending onSoundCompleted queue, the held switches and the 2D particle
|
|
textures, restores the effects volume, mouse mode, mouse enabled state
|
|
and mouse capture, parks the disc on frame 1 paused, and no longer
|
|
calls into the closed Lua state when it happens while paused, so the
|
|
new script starts exactly as a fresh launch does. Under --reload a
|
|
script that fails to compile stays watched, so saving the fix triggers
|
|
the reload.
|
|
|
|
- Quitting with a stick past its dead zone no longer crashes; the
|
|
release reaches onInputReleased after onShutdown, before the state
|
|
closes. Plugging in a gamepad no longer resets the positions
|
|
mouseGetPosition reports, and a controls.cfg mapping that lists the
|
|
same code twice fires its switch once.
|
|
|
|
- materialSetVideo(material) on a framefile disc keeps showing the disc
|
|
after a segment change.
|
|
|
|
- In a packed game the controls.cfg at the root of the data directory
|
|
loads again, io.open(".") returns nil with a message instead of a
|
|
handle whose first read fails, and a directory listing where a loose
|
|
file and a packed one differ only in case shows the loose spelling.
|
|
|
|
- Error messages: an invalid view, navigation mesh or navigation agent
|
|
handle reports "No <thing> N." like every other handle; navAgentNew,
|
|
vehicleSetGears and viewNew state their limits from the engine's
|
|
constants; fontLoad and soundLoad of a missing file say "Unable to open
|
|
NAME" instead of the library's "invalid parameter"; navDraw reports its
|
|
argument count, and running out of memory in it is an error rather than
|
|
a silent blank; scriptExecute and scriptPush that cannot create the new
|
|
data directory abort the script with a message instead of exiting.
|
|
materialNew, sceneGetSize and sceneGetStats no longer reject extra
|
|
arguments.
|
|
|
|
|
|
|
|
SINGE 2.10
|
|
==========
|
|
|
|
Released March 26th, 2024
|
|
|
|
|
|
New Features
|
|
------------
|
|
|
|
- Texture samplers now walk the whole mipmap chain. SDL hands max_lod
|
|
straight to Vulkan's maxLod and Direct3D 12's MaxLOD, and the scene
|
|
and GUI left it at the zero a memset gives, which clamps every lookup
|
|
to mip level 0. Mipmapping was therefore off on every backend:
|
|
distant surfaces aliased, the 8x anisotropy did far less than it
|
|
looked, and the sky cube could not pick a level by roughness.
|
|
|
|
- An OpenGL ES 3.1 backend for the 3D scene and the GUI. SDL_GPU has
|
|
Vulkan, Direct3D 12 and Metal and no OpenGL of any kind, which left the
|
|
cheap Linux handhelds and the Raspberry Pi with 2D only: their Mali and
|
|
VideoCore parts have a mature GLES driver and either no Vulkan or an
|
|
immature one. The subset of SDL_GPU the scene and the GUI use is now
|
|
behind one table of function pointers with two implementations, so both
|
|
files stay the single description of what Singe draws, and the backend is
|
|
chosen at run time -- one aarch64 binary serves a board with Vulkan and one
|
|
without. The ES shaders come from the same HLSL through SPIRV-Cross, and
|
|
the GL entry points are loaded through SDL rather than linked, so a machine
|
|
with no GLES driver simply keeps its 2D renderer. The Raspberry Pi 4
|
|
remains the minimum for 3D: the Pi 3 has only OpenGL ES 2.0, which cannot
|
|
express the scene's skinning or morph targets.
|
|
|
|
- spriteDraw() now has two more forms. In addition to being able to draw
|
|
regular sprites and stretched sprites it can now draw both using the
|
|
sprite's center as the anchor instead of the upper left. This is highly
|
|
useful when dealing with rotated sprites.
|
|
|
|
- Animated sprites! Both animated GIF and WEBP images are supported.
|
|
|
|
- Sprite anti-aliasing, scaling and rotation! Optional separate X & Y scaling.
|
|
|
|
- Video anti-aliasing, scaling and rotation! Optional separate X & Y scaling.
|
|
|
|
- Multiple audio track support for videos. New command line options to select
|
|
which default audio track you want (-o or --audio). For games with multiple
|
|
languages, you can specify AUDIO_TRACK in the games.dat as well.
|
|
|
|
- New keyboard methods to make handling input easier, especially multiple
|
|
keypresses for diagonal movement.
|
|
|
|
- controllerGetButton() added.
|
|
|
|
- SINGE_DEAD_ZONE global variable now available based on the DEAD_ZONE
|
|
controller configuration option.
|
|
|
|
- SINGE_FRAMEWORK_VERSION variable added.
|
|
|
|
- DIR variable added providing directory to the running script.
|
|
|
|
- All new Lua library handling. Previously Singe "injected" libraries
|
|
directly into the script context. Now there is a proper Lua module search
|
|
handler. Scripts can properly require() modules from the following:
|
|
- Lua Standard Library
|
|
- Lua Auxiliary Library
|
|
- LuaFileSystem
|
|
- LuaSocket
|
|
- LuaSec
|
|
- LuaRS232
|
|
- timerwheel
|
|
- binaryheap
|
|
- Copas
|
|
- json
|
|
|
|
- New command line option: -p (or --program). This is similar to the
|
|
existing -t (or --trace) option. Where "trace" displays and logs script
|
|
execution, "program" displays and logs Singe internals. This should make
|
|
troubleshooting difficult setups and odd script crashes easier.
|
|
|
|
- Auto-installer for the new GAME, TOOL, and PATCH archive formats to make
|
|
managing games easier.
|
|
|
|
- ZeroBrane Studio (https://studio.zerobrane.com) integration for live
|
|
debugging of running Singe scripts! A full IDE for developing games!
|
|
|
|
|
|
Fixes
|
|
-----
|
|
|
|
- USB controllers now work in games launched from the included menu.
|
|
|
|
- Default controller DEAD_ZONE increased.
|
|
|
|
- Framework.singe was missing keyboard mode constants. MODE_NORMAL and
|
|
MODE_FULL are now included.
|
|
|
|
- SINGE 2.00 added two new input events but never had them in the framework.
|
|
SWITCH_BUTTON4 and SWITCH_TILT are now available.
|
|
|
|
- Taking screenshots could sometimes crash Singe.
|
|
|
|
- PNG files no longer generate warnings on the console.
|
|
|
|
- Non-VLDP videos now honor command line volume settings.
|
|
|
|
- Menu system now performs "natural sorting" of game titles instead of brain
|
|
dead "computer sorting". It should make a lot more sense paging through
|
|
games now.
|
|
|
|
- Building Singe depended on a lot of undocumented software cobbled together
|
|
from other projects. This has been vastly improved and should be usable by
|
|
actual humans now. Humans running Linux anyway.
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
SINGE 2.00
|
|
==========
|
|
|
|
Released October 25th, 2020
|
|
|
|
|
|
New Features
|
|
------------
|
|
|
|
- Complete rewrite! No more dependency on Daphne.
|
|
|
|
- Support for almost every (non-patent encumbered) video and audio format
|
|
that can be played by FFMPEG.
|
|
|
|
- High definition video.
|
|
|
|
- 32 bit color.
|
|
|
|
- Additional videos can be played on top the laserdisc video.
|
|
|
|
- Scaled sprites.
|
|
|
|
- Graphics overlay resolution no longer dependent on video resolution.
|
|
|
|
- Analog controllers.
|
|
|
|
- LuaSocket and LuaFileSystem added to runtime.
|
|
|
|
- Ability to chain scripts together for menus or launchers.
|
|
|
|
- Specify if you want gun games to display on-screen crosshairs or not.
|
|
|
|
- Menu for launching games included.
|
|
|
|
- Many updates to Framework.singe to support the new APIs.
|
|
|
|
- New APIs:
|
|
- controllerGetAxis
|
|
- discGetHeight
|
|
- discGetWidth
|
|
- fontUnload
|
|
- mouseGetPosition
|
|
- mouseSetCaptured
|
|
- overlayBox
|
|
- overlayCircle
|
|
- overlayEllipse
|
|
- overlayLine
|
|
- overlayPlot
|
|
- overlaySetResolution
|
|
- scriptExecute
|
|
- scriptPush
|
|
- singeWantsCrosshairs
|
|
- soundUnload
|
|
- spriteGetHeight
|
|
- spriteGetWidth
|
|
- spriteUnload
|
|
- videoDraw
|
|
- videoGetFrame
|
|
- videoGetFrameCount
|
|
- videoGetHeight
|
|
- videoGetVolume
|
|
- videoGetWidth
|
|
- videoIsPlaying
|
|
- videoLoad
|
|
- videoPause
|
|
- videoPlay
|
|
- videoSeek
|
|
- videoSetVolume
|
|
- videoUnload
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
SINGE 1.14 - 1.19
|
|
=================
|
|
|
|
Released ?
|
|
|
|
|
|
Notes
|
|
-----
|
|
|
|
- Unofficial fan builds.
|
|
- Stand-alone releases.
|
|
|
|
|
|
New Features
|
|
------------
|
|
|
|
- New API functions:
|
|
- discPauseAtFrame
|
|
- mouseEnable
|
|
- mouseDisable
|
|
- mouseHowMany
|
|
- mouseSetMode
|
|
- singeDisablePauseKey
|
|
- singeEnablePauseKey
|
|
- singeGetScriptPath
|
|
- singeSetGameName
|
|
- soundFullStop
|
|
- soundGetVolume
|
|
- soundIsPlaying
|
|
- soundPause
|
|
- soundResume
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
SINGE 1.10 - 1.14
|
|
=================
|
|
|
|
Released ?
|
|
|
|
|
|
Notes
|
|
-----
|
|
|
|
- Unofficial fan builds.
|
|
- DLL releases for Daphne.
|
|
|
|
|
|
New Features
|
|
------------
|
|
|
|
- New API functions:
|
|
- discGetState
|
|
- keyboardGetMode
|
|
- keyboardSetMode
|
|
- singeGetPauseFlag
|
|
- singeSetPauseFlag
|
|
- singeQuit
|
|
- singeVersion
|
|
- soundStop
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
SINGE 1.00
|
|
==========
|
|
|
|
Released 2006
|
|
|
|
|
|
Notes
|
|
-----
|
|
|
|
- Original add-on for Daphne.
|
|
- Shipped with the ActionMax "emulator" and games.
|
|
|