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. Forge/Author.singe and Forge/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. - Forge runs as a game rather than only as a library: started from the menu it opens on a chooser (its own descriptions, a new game from a starter, a copy of anything dropped into its directory), the keys, the mouse and the pad reach the editor, P saves, builds and plays what is on screen and comes back to it, and ESC closes or leaves. Entities can be added, duplicated, deleted and typed field by field -- position, look, behaviours and their parameters, all from the manifest -- and renaming one renames it in every rule. Conditions and actions are picked from the vocabulary with their help beside them, rules reorder, a rule's note is editable, and U undoes forty steps. The compiler loads the runtime from Forge/, where it lives, so it no longer fails looking for a Singe/Author.singe that never shipped. - 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 .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