• exec/load/syncretro_lib.j

    From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Sun Jul 12 05:11:00 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/c7f8c479d1bfafc0bd49accc
    Modified Files:
    exec/load/syncretro_lib.js syncretro_lobby.js
    Log Message:
    syncretro: strip cataloging tags from the titles, and page with the nav keys

    The NES sets are named to the No-Intro/GoodNES convention, which staples cataloging tags onto the filename -- "Super Mario Bros. (World).nes", "Castlevania (USA) (Rev 1).nes", "Excitebike (U) [!].nes". The lobby drew
    them verbatim, so every line carried noise the player did not ask for.

    The tags now come off the displayed title. They are NOT discarded, because
    they are the only thing telling two otherwise-identical entries apart:
    "Contra (USA)" and "Contra (Europe)" are different dumps (NTSC vs a 50 Hz
    PAL conversion), and the variant collapse -- which keys on title, year and publisher and HIDES the losers -- would have folded them onto one key and silently dropped one from the picker. So a tag is parsed out of the title,
    kept on the entry, made part of the collapse key, and put back on screen
    only for the entries that would otherwise render identically.

    A tag is recognized by CONTENT, not position: known region words, language codes, GoodNES letter codes, and status/revision words, each alone or in a comma list. Anything else in a trailing parenthetical is part of the title
    and stays -- so "Adventure (AD&D - Cloudy Mountain) (1982) (Mattel).int"
    and the rest of the Intellivision naming are untouched.

    The same collision rule fixes an older wart it did not cause: two different PORTS of one game -- "Pac-Man (1983) (Atarisoft)" and "Pac-Man (1983) (Intv Corp)" -- are deliberately both kept by the collapse, and have always drawn
    as two identical "Pac-Man (1983)" lines. When a colliding entry has no tag,
    its publisher now does the job.

    Paging: Enter and PgDn advance like N, PgUp goes back like P, Home and End
    jump to the first and last page. PgUp is safe to bind even though it
    translates to CTRL_P, the BBS's node-message hotkey: the terminal layer
    turns the escape sequence into the key code and returns before inkey() sees
    its Ctrl-P case, so only a literally typed Ctrl-P pages a node.

    The ROM cache stores sizes and hashes, not titles, so no cache is stale.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Sun Jul 12 05:11:00 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/29cd63642d5b9d77d00a71b6
    Modified Files:
    exec/load/syncretro_lib.js xtrn/syncivision/lobby.js
    Log Message:
    syncretro: keep the system ROMs a dumped set ships in roms/ out of the picker

    The Intellivision lobby listed "IntelliVoice BIOS (1981)" as a cartridge --
    a firmware image the player can pick and cannot play. A dumped ROM set does
    not keep its BIOS images under the tidy names the core wants (exec.bin, grom.bin): it names them like cartridges and drops them in with the
    cartridges. They clear the size band (a BIOS is cartridge-sized), they are
    not called exec.bin, and their hashes are not the two the console knew --
    so both nets missed, and they landed in the picker.

    Add the two hashes this set actually ships (IntelliVoice, and the Sears alternate exec, which was listed too). But enumerating hashes is a losing
    game: a set may carry the Intellivision II exec, the ECS ROM, and any number
    of redumps of each. What they all share is that the SET SAYS SO, in the
    title -- so a console may now name words that mark a title as firmware
    rather than a game, matched as whole words against the PARSED title (the
    year, publisher and dump markers are already off it). The Intellivision
    names one: "bios". No cartridge on that console has the word in its name.

    It is a per-console rule and not a global one because it is a naming convention, not a fact: a console whose games could plausibly carry the word simply does not set it. The NES does not, and its list is unchanged.

    Also key the disambiguation added in the previous commit on what the player actually SEES -- the title plus the year the cell draws after it -- rather
    than the title alone. Two dumps of "4-TRIS" from 2000 and 2001 are already
    told apart by that year, and stamping a publisher onto them as well was
    noise of exactly the kind that change set out to remove. The two Pac-Man
    ports, which share a year, still get theirs.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Windows@VERT to Git commit to main/sbbs/m on Tue Jul 21 01:41:00 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/c5cecef99b9c2feb4b50fe5c
    Modified Files:
    exec/load/syncretro_lib.js syncretro_lobby.js
    Log Message:
    syncretro lobby: shared saves, and display names for identifier ROMs

    Two facts an ARCADE console needs that a cartridge console does not, both
    added as console-spec data so nothing in the shared lobby learns which
    console it is running:

    shared_saves -- the save directory the door is handed. Per-user is right for
    a cartridge console: one player's battery save is not another's. An arcade cabinet is the opposite -- the high-score table is the whole point of the machine, and there is no per-player save to keep apart -- so every player
    gets one directory, under data_dir like the per-user one (both are generated run-time state). Default stays per-user, so the existing consoles are unchanged.

    names.json -- display titles keyed by ROM name. Every cartridge console
    names its files for humans ("Astrosmash (1981) (Mattel).rom"), so the title parser can read a title out of them. An arcade romset cannot be renamed at
    all: MAME finds the driver BY the zip's basename, so puckman.zip has to stay puckman.zip, and a picker showing filenames would list a screenful of eight-letter codes. A console with no names.json is completely unaffected -- the map is empty and the parser runs as before. Keys beginning with '_' are ignored, so the shipped file can carry its own comment (JSON has none).

    Also records, at the point where the door command line is built, that it
    lands in a BBS buffer which truncates silently at 260 characters and that
    the line already runs to ~240 -- so anything added there from now on has to
    buy its space from something else on it.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Sun Aug 2 22:35:00 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/1b49c18900f3a4b2d78070ca
    Modified Files:
    exec/load/syncretro_lib.js syncretro_lobby.js exec/tests/syncretro_config_test.js syncretro_state_test.js
    Log Message:
    syncretro: lobby decides suspend/resume permission and passes -state

    The lobby is the only half that knows all three inputs the suspend/resume decision needs: the console/romset save_state capability, the sysop's
    [state] auto_resume switch, and whether this player is on a private
    machine. syncretro_lobby_state_key() combines them into the key8 the door
    is handed on the command line, or "" when a snapshot is not permitted --
    the door infers nothing from -home.

    syncretro_lib.js gains the supporting, UI-free pieces: syncretro_state_list() reads a player's snapshot directory in one directory() call regardless of cartridge count (the constraint the whole design exists to satisfy); syncretro_state_marked() and syncretro_state_sweep() mark a picker entry resumable and delete snapshots whose key no longer matches (a core upgrade cleans up after itself); syncretro_core_md5() hashes the libretro core once, cached on size+mtime like the existing ROM hash cache. Discovered ROM
    objects now carry their md5, which syncretro_lobby_state_key() needs and
    which discovery had computed and discarded.

    The lobby never receives -core, so syncretro_core_path() has to find the
    same core file the door will load on its own -- it mirrors
    syncretro_config.c's sr_find_core() (named core, else the lone "*_libretro" match, at the door root or one level into any sub-directory) so both halves hash the identical file.

    The picker marks a resumable cartridge's cell with a trailing " *" and the lobby loop refreshes marks and sweeps stale snapshots after every play, from one extra directory read.

    syncretro_lobby_private() is added as a placeholder that returns
    !shared_saves: the per-player cabinet-privacy toggle for shared consoles is
    a separate feature, and until it exists a shared-saves console is never private, which is the safe default.

    Fixed along the way: the state_key auto_resume check used `ini.state.auto_resume || "true"`, but Synchronet's iniGetObject() auto-types an ini value of "false" to the JS boolean false, which is falsy -- so an explicit `auto_resume = false` was silently replaced by the "true" fallback
    and the sysop's switch could never actually disable anything. Compare String(ini.state.auto_resume) directly instead.

    exec/tests/syncretro_config_test.js also gains coverage for the end-to-end decision (capability, auto_resume, per-romset override, shared cabinet) and
    is the regression test for the auto_resume bug above; this is broader than
    the plan's file list for this task, but the decision lives in the ini-merge path that file already covers.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Sun Aug 2 22:35:00 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/f9c6eb1b14e99a4096db4101
    Modified Files:
    exec/load/syncretro_lib.js syncretro_lobby.js exec/tests/syncretro_config_test.js syncretro_state_test.js
    Log Message:
    SyncRetro: add the player-facing cabinet toggle

    A shared-saves console (today only the arcade) has one NVRAM and one
    high-score table for every player, and never offers a snapshot for
    that reason: a restore there would roll the shared table back. syncretro_lobby_private() previously stubbed this to "never private";
    this wires it to a real per-player, per-console preference so a
    player can opt onto a machine of their own, where their scores are
    theirs and a game resumes where they left off.

    The preference lives in the stock per-user properties file (data/user/<####>.ini, [syncretro] cabinet.<console-id>) via
    userprops.js, not inside the per-console save directory itself: that
    directory is the private -home handed to the emulator core and what
    a sysop deletes to reclaim space from a dormant player, so a
    preference living there would vanish with it. Absent, unreadable, or
    a guest account all resolve to public -- userprops.js already
    no-ops a guest's set() and defaults its get(), and a guest account
    is shared, so a "private" cabinet keyed to it would be private in
    name only.

    The picker draws the current cabinet and a toggle hint only on a
    shared-saves console, and hides the hint (though not the line) for a
    guest, whose keypress cannot persist anything. Toggling re-runs the suspended-game marking pass, since a private cabinet's snapshots are
    invisible from the public one and back.

    The toggle is bound to "C", not "P" as an earlier sketch of this
    picker assumed: "P" is already this lobby's Prev-page hotkey, spelled
    out in the shipped footer prompt, and no installed console overrides
    it.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Sun Aug 2 22:35:00 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/f9ed60eb36a5eefb17c02f31
    Modified Files:
    exec/load/syncretro_lib.js syncretro_lobby.js exec/tests/syncretro_config_test.js syncretro_state_test.js src/doors/syncretro/DESIGN.md syncretro_state.h syncretro_statekey.h test_statekey.c
    Log Message:
    syncretro: fix final-review findings on save/resume

    Close out the whole-branch review of the save/resume feature before
    merge:

    - syncretro_lobby_state_key() now returns "" when the core hash is
    empty (a case-mismatched [console] core resolves via the door's
    case-insensitive globi() but not the lobby's case-sensitive
    directory()), instead of silently keying the snapshot on romset
    and options alone. Add a regression test.
    - Document the deploy-ordering hazard in DESIGN.md: the lobby goes
    live on save, the door binary only on rebuild + deploy, and an
    older binary misreads a new flag's value as the ROM path.
    - Fix stale comments left by the syncretro_state.c/syncretro_statekey.c
    split (DESIGN.md module map, both header banners, test_statekey.c),
    and rewrite its golden-value comment to drop a reference to an
    implementation-plan step no shipped reader will see.
    - syncretro_cell() now reserves room for the " *" resume marker
    before clipping the title, so a title at or over the cell width no
    longer clips the marker away.
    - Add a "* = resumes" legend to the picker's footer prompt.
    - Declare the implicit global `f` in syncretro_state_test.js.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Sun Aug 2 22:35:00 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/1597f74d68f492ba402a3c4a
    Modified Files:
    exec/load/syncretro_lib.js syncretro_lobby.js xtrn/syncivision/tests/test_syncretro_lib.js
    Log Message:
    syncretro: one-line cabinet block, and move the resume marker left

    The cabinet state, what it costs and how to change it now occupy one row
    rather than two, returning a row to the cartridge grid.

    The resume marker moves from after the title to the column before it. In a grid, a trailing marker sits hard against the NEXT column's number and
    reads as belonging to that cell. The shipped cell format now ends at its separator so the marker occupies the space that was already there: a
    marked cartridge reads " 1 |*Title" and an unmarked one " 1 | Title",
    the same width either way, with the title in the column it always
    started in. A custom [text] cell_fmt keeps its own spacing and gains the
    marker column, which is what the two adjusted assertions record.

    The marker stays in the title field rather than the format's prefix
    because cell_fmt is a sysop override taking exactly two substitutions, the number and the title; a third would feed the marker to an existing
    two-argument format in place of the title it expects.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Sun Aug 2 22:35:00 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/7b7b83f53efdcda7775df067
    Modified Files:
    exec/load/syncretro_lib.js syncretro_lobby.js xtrn/syncarcade/syncretro.ini xtrn/syncivision/syncretro.ini xtrn/syncnes/syncretro.ini
    Log Message:
    syncretro: move the resume marker between the number and the separator

    A cartridge with a saved game reads " 1*| Title", one without " 1 |
    Title". Placing the marker immediately after the digits ties it to its own row's number and leaves the title's first column clear, which is easier to
    read than a marker abutting the title. The prefix spends the same six
    columns it always did, so no title moves.

    This puts the marker in the format's prefix, so cell_fmt now takes three substitutions: the number, the marker, the title. A sysop override
    carrying only one %s is still read as the older two-substitution form,
    with the marker placed immediately before the title instead -- an override written from the previously documented example keeps working and still
    shows the marker, rather than printing it where the cartridge belongs.

    The commented example in each console's shipped ini is updated to the three-substitution form and documents both.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Sun Aug 2 22:35:00 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/950d6dee981b35d3ece22526
    Modified Files:
    exec/load/syncretro_lib.js syncretro_lobby.js exec/tests/syncretro_state_test.js
    Log Message:
    syncretro: only promise a resume where a game can actually be saved

    The private cabinet advertised "Scores yours, games resume." on every shared-saves console, and the picker's footer carried the resume marker's legend on every console. Neither is true on the arcade, which ships
    save_state = false for all 4740 romsets because none has been verified: a player who switched to a private cabinet on the strength of that promise
    would quit their game and find nothing waiting, having done exactly what
    the screen told them to expect.

    syncretro_savable() answers whether anything on this console can be saved
    -- its own [console] save_state, or one romset overriding it -- and the
    lobby claims a resume only when it can. The moment a sysop enables a
    romset the promise and the legend appear on their own.

    It lives in the UI-free lib rather than inline in the lobby's init so it
    can be tested directly, which is where the eight new assertions live,
    including the auto-typed boolean forms an ini actually yields.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net