Engine Chronicles

  • The song loader does more than search for recognised file extensions.

    When OutFox finds a song folder, it also has to decide which parser should handle it. That becomes more complicated when several formats share extensions, tags or structures that have changed over time.

    One of the more expensive examples involved Pump content.

    OutFox supports Pump-specific formats and behaviour, including older content stored in SSC files. Because of that history, the loader could treat some ordinary SSC files as possible Pump charts and send them through additional checks before eventually returning to the normal SSC path.

    The #VERSION:0.81 marker was one of the signals involved, but that marker alone is not enough to prove that a chart contains Pump data.

    Across a large library, those false matches added up.

    We have now made that detection more precise. Files using the dedicated PIU format still go directly to the Pump parser. SSC files can also be identified as Pump content when they contain clearer evidence, such as Pump step types or explicit format markers.

    Normal SSC files no longer need to pass through the more expensive Pump checks based on one broad signal alone.

    On our 3,722-song test library, this reduced one loading run from 677.246704 seconds to 511.741608 seconds.

    That is still far slower than we want, but the result confirmed that format detection was responsible for a meaningful part of the loading time.

    The wider loader work is now applying the same approach elsewhere. Parsers should be checked when the files provide a reasonable reason to check them, rather than asking every possible parser to inspect every folder.

    Compatibility remains the priority.

    We are not narrowing support or abandoning unusual content. Older Pump charts and other formats still need to be recognised correctly. The aim is to reach the correct parser with less repeated work along the way.

    This change has removed one large source of unnecessary parsing, and it gives us a clearer direction for the remaining loader work.

  • This work started with a number we could no longer ignore.

    One of our older benchmarks recorded 3,902 songs taking 791.658415 seconds to load. That is more than thirteen minutes.

    Our current test library is slightly smaller at 3,722 songs, but early measurements were still showing a startup time of 677.246704 seconds.

    Clearly, this was not a case of shaving a little time off the loading screen.

    OutFox has to support libraries built up over many years. That can include several chart formats, converted packs, edits, autogen content, courses, keysounds, banners, jackets, videos and folders that do not always follow the structure the engine expects.

    The loader has gradually learned how to deal with all of that.

    When it finds a song folder, it may need to identify the format, choose a parser, check existing cache data, locate media, rebuild missing information and apply compatibility rules carried forward from older versions of StepMania.

    A lot of that work is necessary.

    Some of it was also being repeated.

    StillRiver allowed us to separate the different parts of startup and see where the time was actually going. Storage speed was only part of the picture. Parsing, cache handling, media checks, file probing and repeated data conversion were all contributing to the delay.

    That gave us something more useful than a single startup time.

    We could see which parts of the loader were doing real compatibility work, which were acting as old safety checks, and which were repeating work that had already been completed elsewhere.

    We are not replacing the entire loader in one sweep.

    There is too much unusual and historical content depending on it, and a faster loader is not useful if songs disappear, charts change or theme-visible information comes back differently.

    Instead, we are measuring each stage and removing unnecessary work while checking that the same library still produces the same usable result.

    The goal is straightforward: large collections should not take several minutes to become playable simply because the engine is checking the same information more than once.

    This is still ongoing work, but we now have a clear baseline and a much better view of where that startup time is being spent.

    That gives us somewhere useful to begin.

  • Replacing RageSound does not mean every old audio interface can disappear at the same time.

    SereneAudio now handles the main audio paths we have qualified, but parts of the engine still call older RageSound methods and objects. Themes, Lua scripts, editor tools, gameplay systems and older content may all depend on those interfaces, even when RageSound is no longer doing the work underneath them.

    Removing those interfaces too early would make the code look cleaner, but it could also break content that currently works.

    We are therefore retiring them in stages, with the old output drivers are no longer used on the SereneAudio routes we have tested. Normal playback, mixing and device output can now run through the new system without RageSound controlling the result.

    What remains is mostly the older surface around that work.

    Some of those calls are straightforward to replace, such as menu sounds and other small interface effects. Others need more care. Music playback, editor behaviour, assist sounds and keysound systems all have wider consequences if their behaviour changes.

    AutoKeysounds and live keysound banks are especially sensitive. Older BMS, PMS and other keysound-heavy content can rely on behaviour that is not obvious from the modern code path alone. Those systems need to be tested properly before their remaining RageSound dependencies are removed.

    The work is therefore being handled one route at a time, slowly, moving each caller to SereneAudio, tested against the existing behaviour, and checked with the content and platforms it affects. Once nothing relies on the older path, that part of RageSound can be removed safely.

    This takes longer than deleting the old classes in one pass, but it avoids making players, theme authors and chart creators absorb the cost of the rewrite.

    OutFox supports a large amount of content built across different versions of StepMania and several rhythm-game communities. The internals can change, but the behaviour people rely on should remain unless there is a clear reason to change it.

    SereneAudio is now doing the real work in our playback and delivery.

    The remaining job is to move the last callers across without breaking the history they still support.

  • With the newer audio and timing paths settling into place, we have started looking more closely at the work still being driven by the gameplay frame loop.

    Over the years, several responsibilities accumulated around the code that tracks notes crossing rows. That path now touches holds, checkpoints, assist sounds, misses, theme messages and cabinet lighting, even though those events do not all need to be checked or triggered every time a frame is drawn.

    This can be difficult to spot during normal play. Each individual operation is small, and the engine still produces the expected result. The problem becomes clearer when the same work is repeated hundreds or thousands of times simply because the frame loop continues to run.

    We have added StillRiver measurements around these row and gameplay-window paths so we can see what is happening before changing their behaviour.

    The aim is not to pull the whole system apart at once. Some of this code controls gameplay behaviour that has existed for many years, across different modes and themes. Changing too much in one pass would make it harder to tell whether a fault came from the original behaviour or from the rewrite.

    The first adjustment has been made to gameplay-side lighting updates.

    Those updates are now limited to a steady 60 Hz instead of running at the full gameplay frame rate. The existing global lighting option is unchanged, and the lower-level lights manager has been left alone. This only prevents the gameplay path from asking for the same lighting work more often than it can usefully change.

    It is a deliberately small change, but it gives us a clean result to measure before moving further into the row-processing code.

    The longer-term work is to separate actions that currently happen together simply because they share an old update path. Row crossings should happen when the song reaches them. Input work should happen when an input arrives. Lighting should update when a lighting event is due. Presentation can continue to follow the frame loop.

    StillRiver is helping us identify those boundaries without guessing or rewriting working behaviour for the sake of tidier code.

    There is more to untangle here, but the first unnecessary work has now been removed without changing scoring, chart timing or input handling.

  • Recent SereneIO testing has focused on something more important than whether a button press reaches the game.

    We also need to know when it happened.

    Older input paths often record an event when the engine processes it. That is not always the same moment the player pressed the button. If an event waits for the next frame or polling cycle, the timestamp can describe when OutFox noticed it rather than when the device produced it.

    For ordinary menu input, that difference may not matter much, but for gameplay timing, it does.

    SereneIO is being built so that OutFox can keep track of where an input timestamp came from. Some backends provide their own event timing, while others rely on callbacks, polling or compatibility paths with less precise information.

    Those routes can all remain useful, but they should not all be treated as though they provide the same evidence.

    During testing, we found that some older input paths were still reaching the engine without enough information about how their timing had been captured. The inputs worked, but we could not use them to support stronger timing claims until that provenance was clear.

    That does not mean those devices or drivers are being removed.

    OutFox supports keyboards, game controllers, dance pads, MIDI equipment, arcade I/O and a wide range of older hardware. Some of those systems need compatibility routes, and keeping them working remains important.

    The aim is to distinguish those routes honestly.

    Where a backend provides a reliable timestamp, SereneIO can preserve and use it. Where the engine has to fall back to polling or older behaviour, that path can remain available without pretending it offers the same timing certainty.

    This also gives us a clearer way to test specialist hardware. Rather than assuming every controller behaves alike, we can look at how each input was captured and decide whether that route is suitable for normal play, compatibility support or more demanding timing use.

    For players, the result should remain simple, presses should arrive reliably, and judgement should reflect when they happened as closely as the hardware and operating system allow.

    For us, the important part is knowing which input paths can support that claim, and which ones still need more work.

  • We have been testing a new metronome calibration path built around the Serene timing work.

    The process is familiar: the game plays a steady beat, the player taps along, and OutFox measures the difference between the expected beat and the input it received. From that, it can suggest a global offset.

    The awkward part is that people do not tap with machine-like consistency.

    Even an experienced player will have some variation between taps. A few may land early, a few late, and an otherwise useful test can contain one or two poor inputs. The older calibration logic could treat that spread too harshly and reject the result outright.

    We have changed that behaviour; the tool still warns when the taps are inconsistent, but it can now allow the tester to use the median result when the underlying timing remains stable. That keeps obviously noisy data visible without throwing away an otherwise useful measurement.

    We also corrected how the suggested offset is applied.

    When a tap arrives late, the measured difference is positive. The correction has to move in the opposite direction, so the applied global offset is negative. The previous flow did not make that relationship clear enough and could make a valid result appear backwards.

    The new path now shows and applies that correction consistently, calibration also works properly with the normal save and revert controls. A tester can try the suggested value, keep it if the result improves, or leave without the engine retaining a half-applied change.

    The aim is not to pretend that every set of human taps is perfect. It is to give the tester enough information to judge the result, while making sure OutFox applies that result correctly.

    The Serene metronome now handles variable taps more sensibly, gives clearer offset results, and fits properly into the engine’s existing settings flow.

  • While testing the new audio and timing work, normal gameplay was behaving well. SereneAudio and the gameplay position remained closely aligned, with no sign of the timing drifting during a song.

    SyncMachine was less convincing.

    After restarting its test, it would show a small but repeatable offset. Because the value stayed fixed rather than growing over time, we knew this was unlikely to be the audio path wandering out of sync.

    The problem was in how timing information was being passed between systems.

    One part of the engine recorded the song position at a particular moment. Another part received that value slightly later and treated it as though it described the current moment. The delay was small, but large enough for a calibration tool to notice.

    We corrected that handoff so the timing value remains tied to the moment it was measured and is interpreted correctly when another part of the engine uses it.

    That removed the fixed offset without adding another compensation value or changing normal gameplay timing to cover the problem.

    This distinction matters because calibration tools should measure the player and the hardware, not compensate for an internal disagreement inside the engine.

    The work also confirmed that the new gameplay timing path was stable. The problem was not SereneAudio drifting or the song position gradually separating during play. It was a specific transition between two older parts of the calibration flow.

    SyncMachine now receives timing information that represents the correct point in gameplay, rather than mixing values captured at slightly different moments.

    For players, that means calibration results should be more dependable, without asking them to correct an offset created by the engine itself.

    It was a small fault, but exactly the sort of thing that needs fixing before the newer timing systems can fully replace the older paths.

  • While testing SereneAudio, we noticed a small but repeatable stutter during gameplay.

    Because the audio system had just been replaced, that was the obvious place to start looking. We checked the output paths, buffering and timing measurements, but SereneAudio was behaving normally. There were no underruns, and nothing suggested that the music or sound scheduling was causing the problem.

    The stutter was still there, though, so we kept tracing it through the engine.

    It eventually led us away from the audio system and into background video playback.

    Some hardware-decoded videos were creating brief stalls when their frames were prepared for rendering. The delay was small, but it happened at the wrong point in the frame and was enough to disturb gameplay presentation.

    This is why the problem initially looked like a timing fault. The music continued correctly, but the moving notes were not being presented as evenly as they should have been.

    StillRiver helped us separate the two, the audio path remained stable during the stutter, while the video and rendering measurements showed where the extra work was occurring. Once we knew which path was responsible, we could fix the video handling rather than trying to compensate elsewhere in the engine.

    We have now changed how those video frames are prepared before reaching the renderer, reducing the work that was interrupting active gameplay.

    There was no need to adjust song timing, increase audio buffering or add another setting for players to manage. Those would only have hidden the symptom while leaving the actual fault in place.

    This was a useful reminder of why we measure several parts of the engine together.

    Audio, video, rendering and gameplay all run alongside one another. A fault in one system can easily look like a problem in another, especially when the effect is only a brief interruption rather than a complete failure.

    In this case, SereneAudio was doing its job.

    The stutter was coming from the video path, and that is where we fixed it.

  • Audio devices do not all report playback in the same way.

    Some drivers provide useful timing information. Others report estimates, buffered positions or values affected by additional layers between the game and the speakers. That makes audio timing more complicated than simply asking the sound device where the music is.

    Older parts of the engine relied heavily on those reports. That worked well enough on many systems, but it also meant that differences between drivers could affect how the engine understood playback.

    SereneAudio handles this more carefully.

    Rather than assuming that every backend provides the same quality of information, the new system checks how audio is being submitted and played across each supported output path. This allows us to account for differences between modern callback drivers, older polling systems and compatibility backends without allowing one unreliable report to disturb the rest of the engine.

    Recent testing exposed an underrun in one of the older paths. The problem was not simply a buffer that was too small. The backend was being fed using behaviour inherited from the previous audio system, which no longer matched the way SereneAudio operates.

    We corrected the feed path rather than hiding the fault behind a larger buffer and more latency.

    StillRiver was used throughout the testing to confirm that audio remained stable, that the device was receiving data at the expected rate, and that the change had not introduced clipping or unnecessary delay.

    For players, the result should be straightforward, audio should start reliably, remain stable, and behave consistently across different devices and operating systems. Keysounds should play cleanly, and older compatibility routes should continue working without holding newer hardware back.

    SereneAudio gives us a clearer and more dependable way to handle those differences.

    The complicated details stay inside the engine, where they belong.

  • You may have noticed us using the word Silver when talking about newer parts of the engine.

    It is worth explaining what we mean by that, because Silver does not mean finished.

    When a system reaches Silver grade, it means it has moved beyond being an experiment. We have run it through real engine paths, measured how it behaves, and reached a point where we are comfortable continuing to build on it.

    That is an important checkpoint, but it is still a checkpoint.

    Recent work on OutFox has involved replacing several older foundations rather than adding another layer on top of them. SereneAudio is taking over the audio path, SereneIO is replacing older input behaviour, the renderer has moved onto programmable and portable paths, and StillRiver gives us a clearer view of what the engine is doing while we make those changes.

    Work at that level does not move neatly from “broken” to “finished”.

    A system may be working correctly on the platforms and hardware we have tested, while still needing wider coverage. It may be stable during normal play but still have unusual charts, devices or modes left to check. It may also be doing its own job properly while another older part of the engine still depends on behaviour we intend to remove.

    Silver means the foundation is working well enough that we no longer consider it temporary.

    It has been measured, it has survived meaningful testing, and the remaining work is understood well enough for us to keep moving forward without pretending there are no gaps left.

    Gold is the higher bar.

    That requires broader testing across the systems we support, more edge cases resolved, fewer remaining dependencies on the older engine paths, and enough evidence that we can treat the work as mature rather than merely promising.

    This is why you may see us describe something as Silver while continuing to discuss fixes around it.

    That is not us moving the goalposts. It is us being clear about where the work has reached.

    Rhythm games are particularly unforgiving of vague confidence. Audio, input, timing, rendering and chart behaviour all affect the player at once. A system can look correct in isolation and still produce a problem when it meets a different driver, controller, platform or game mode.

    Calling something finished too early helps nobody.

    Silver tells you that the new path is real, useful and stable enough to build upon. It also tells you that we are still testing it before calling the job complete.

    That may not be as dramatic as declaring victory every time a component compiles, but it is a much more useful way to describe engineering progress.

    Silver is not Gold, it is the point where we know the new foundation can carry the next stage of the work.