Skip to content

Front ends: one interface between the engine and graphical apps (settings, build progress, runtime events) #388

Open

Description

@v0lumehi

Graphical front ends come up almost daily: #104, #244, #264, #332, #374. NX2Apple already builds games, keeps a library and starts them with their settings. To follow main, it re-applies patches to nine of our files on every sync (sync_upstream.py). I think the useful work here isn't another GUI in this repository. It's a clear interface between the engine and any app around it, so front ends can build on nxnative without forking or patching it.

Three layers

  1. The game's own app (front_mac.m, front_ios.m, the iPhone's settings screen). Everything that has to appear while a game runs lives here, because no other app is on screen then: a controller that went away, "Preparing shaders", a thermal hint, an error.
  2. The interface between the engine and its front ends, versioned, documented and tested:
    • in: settings.tsv → launcher.json → environment switches (exists);
    • out while building: the NSP script's --progress json events (exists);
    • out while a game runs: missing. Today the host reports only in its log, as text meant for people.
  3. Front ends outside this repository: building, a library, launch options, artwork, updates. NX2Apple is one.

Steps

Design

What the player sees should be the same everywhere: the setting names, sections, tested defaults and "Reset to tested defaults" come from one list, settings.tsv / launcher.json, and every front end draws from it. How it looks follows each platform's own conventions (SwiftUI forms, Apple's Human Interface Guidelines), not one front end's style.

Out of scope here

An app to build games or keep a library inside this repository. Those stay separate projects, with nxnative providing what they need.

Activity

  1. v0lumehi commented on Oct 27, 2026

    @v0lumehi
    ContributorAuthor

    @LNPANDY, NX2Apple is where most people looking for an app end up, so your view would help shape this. Three questions:

    1. Patches. sync_upstream.py re-applies patches to nine files on every sync. Which would you most like to see upstream, so NX2Apple can follow main as it is? From the list, the RomFS archive hooks (hle_fs.c, build_ipa.sh, the NSP script) and the adjustable system clock (hle_time.c) look like candidates for small PRs here. I'm happy to help write them.
    2. Runtime events. Which would NX2Apple show if the host reported them: the game reaching its frame loop, shader preparation progress, controllers, why the game ended? Anything else?
    3. Launch options outside your library. Today they apply only when NX2Apple starts the game. Would writing them to the app's own preferences (org.nxnative.<title ID>, keys launcher.<switch>, as the iPhone app stores them) work for you? The Mac launcher would then read them on every start, from Finder or the Dock too.
  2. seedds commented on Oct 27, 2026

    @seedds
    Owner

    Let's use this issue to coordinate the shared interface. nxnative should own game execution, the settings supported by each game, and the native UI needed inside a game's app. Build wizards, game libraries, artwork and update management can remain separate community projects.

    I'll take #352 first, then #387. The first milestone is that a Mac app carries its supported settings and tested defaults in launcher.json, and frontend authors have a clear document describing the build progress protocol, launch behavior, settings, saves and logs. Once #352 is integrated, #387 should contain only the documentation changes.

    For the following work:

    1. Agree on one settings schema before extending the UIs. launcher: sub-settings that appear only when they can do anything (needs column) #355 uses the ninth TSV column for needs, while launcher.json: a level for the settings shown first, and the render scale's base size #399 uses it for level; Mac: settings window before the game starts #379 also introduces its own platform and resolution metadata. Reconcile these into one generator and schema that iOS, the Mac app and outside frontends consume. Document compatibility rules: readers tolerate unknown optional fields, and existing supported fields retain their meaning.
    2. Define persistence and precedence together. Use one documented per-game preferences store that the game's own window and outside frontends can share. Keep only choices that differ from tested defaults, and validate saved choices against the current app's settings. Explicit launch overrides should be temporary and take precedence: launch environment, user env.txt, bundled env.txt, saved choices, tested defaults. In Mac: settings window before the game starts #379, the shared model currently protects env-file keys but can overwrite inherited environment values; resolve that before integrating the window. Hiding the window and ignoring saved choices should have separately documented behavior.
    3. Allow a small, optional settings window inside each Mac game app. This revisits the earlier decision in Proposal: a settings launcher on iOS and macOS, driven by one table of player-facing switches #64 now that standalone Mac app packaging exists. Mac: a settings window before the game starts (resolution, display mode, frame rate) #374/Mac: settings window before the game starts #379 fit this scope, drawing from the shared metadata. Review the remaining menus, controller assignment and remapping in Mac: native AppKit app bundle, preferences, and controller mapping #104 as smaller changes on top of the current packaging.
    4. Make community frontends discoverable consistently. Add issue forms, a pull request template and CONTRIBUTING.md #306 can explain the contribution boundary and improve reports. A community frontend page can list NX2Apple and evaluate projects such as NX Play: a Mac library app on top of build_ipa_from_nsp.py (no fork) #264 and Community Project: NxNative Launcher (macOS Native GUI & Game Library frontend) #332 by the same criteria, with frontend-specific support going to their own repositories. The Windows work in Working on a Community Windows Port of NXNative + Desktop GU #244 needs its own portability discussion.
    5. Add runtime events incrementally after the first milestone. Start with shader preparation, the first frame and termination. Native UI can consume typed internal notifications, with JSON exported for external consumers; keep delivery off the rendering critical path. The supervising launcher should report exit codes and signals, since a crashed host cannot reliably emit a final event. Controller and thermal notifications can follow. The controller applet should be a separate Horizon-semantics fix.

    For the settings milestone, check Finder launch, launch through an external frontend, explicit environment overrides, and reset after rebuilding with changed defaults. Audit fork patches individually for general fixes or missing interfaces; a fork's packaging or product choices do not all need to become upstream features.

  3. v0lumehi commented on Oct 27, 2026

    @v0lumehi
    ContributorAuthor

    Thanks, @seedds. Agreed on the split and the order. #352 and #387 are in. Before anyone extends a UI, here is a proposal for the two things you asked to settle first: one settings schema, and one store with one precedence. It also covers a third point: how a settings or controls UI can be one interface with many skins. Nothing below is final. I'd like the authors of the open PRs on this topic to weigh in, so that we end up with one design and not six.

    Who this touches

    1. One schema

    Problem. #355 and #399 both claim settings.tsv's ninth column, and #379 brings its own metadata.

    Proposal.

    • Keep the eight positional columns as they are.
    • Make the ninth an optional meta column of key=value pairs separated by ;. For example level=basic;needs=DM_RUMBLE;platform=mac.
    • settings.py validates each key against a short, documented list and writes the pairs into the row of launcher.json as fields.
    • Data that belongs to a game, not to a setting, stays in its profile. An example is the render scale's base size (render_base in launcher.json: a level for the settings shown first, and the render scale's base size #399), which settings.py attaches to the row.

    Compatibility rules (as you wrote):

    • Readers ignore unknown keys and fields.
    • A supported field keeps its meaning.
    • A new key is added in one place: the list in settings.py and its line in docs/frontends.md.
    • A test fails on a key the docs don't list, the way test_env_switches.py does for switches.

    The contract is the JSON, not the TSV.

    • launcher.json gets "schema": 1.
    • The format is published as a JSON Schema in docs/, so a front end can validate it and generate types from it.
    • settings.tsv stays the authoring format; front ends never read it.

    Keys we already need:

    I'm happy to rewrite #399 onto this. @Xipzer and @surajmandalcell: would #355 and #379 move onto the same column? The generator change would then be one PR that the others rebase onto.

    2. One store, one precedence

    This is your order:

    1. launch environment
    2. the user's env.txt
    3. the bundled env.txt
    4. saved choices
    5. tested defaults

    The store is the app's own preferences domain, org.nxnative.<title ID>, with keys launcher.<switch>, the same as the iPhone app.

    • Only choices that differ from the tested default are kept.
    • They are validated against the current app's launcher.json when they are read, so a rebuild with changed defaults drops values that no longer apply.
    • dm/mac/launcher.c reads them with CFPreferences on every start, so a start from the Finder or the Dock has them too.

    A known limit: a front end writes another app's preferences domain, which works for an app outside the App Sandbox. A sandboxed front end could not. If that ever matters, the store moves to a file next to the saves, with the same keys and the same precedence.

    Launch overrides stay temporary. A front end that starts the game with an environment does not write the store.

    #379's issue: a value inherited from the environment must not be overwritten by a saved one. Precedence 1 covers exactly that case.

    3. One interface, many skins

    nxnative should provide the model, and any UI is a view of it:

    A front end then has two options:

    • Draw the same data in its own style and write the same store. Its view and the in-app window never disagree, because there is only one set of choices.
    • Start the game with a documented switch that skips the in-app window, the same DM_LAUNCHER=0 the iPhone app uses. This needs a defined difference between "skip the window" and "ignore saved choices", as you asked.

    The same goes for controls. The mapping is data, and #104's AppKit preferences and controller mapping, NX2Apple's Controls page and a future in-app panel all write the same keys. @jamessturges: #104 covers a lot of this ground. Would you split it along these lines (the app bundle and preferences on one side, the mapping UI on the store on the other)?

    Menus inside a running game (#405's Upscaling menu, #406's cheats) would read and write the same model wherever they change a launch setting, so a choice made in a menu shows up in every other view.

    4. Runtime events

    These come last, as you suggested, once 1–3 have landed.

    Proposed order

    1. A schema PR (the meta column, the key list, "schema": 1 and the JSON Schema, docs/frontends.md, the test).
    2. launcher.json: a level for the settings shown first, and the render scale's base size #399, launcher: sub-settings that appear only when they can do anything (needs column) #355 and Mac: settings window before the game starts #379's metadata rebased onto it.
    3. The store and precedence in launcher.c, with your checklist: Finder launch, external front end, explicit overrides, reset after a rebuild with changed defaults.
    4. Mac: settings window before the game starts #379's window on top, then Mac: native AppKit app bundle, preferences, and controller mapping #104's mapping parts.

    I can do 1 and 3 if nobody else wants them.

  4. v0lumehi commented on Oct 27, 2026

    @v0lumehi
    ContributorAuthor

    Section 1 as a concrete diff to discuss: #418 (draft, not for merge until we agree here). It contains the meta column, "schema": 1, a JSON Schema in docs/launcher.schema.json, and level and needs as the first two keys. #355's needs keeps its format there.

  5. seedds commented on Oct 27, 2026

    @seedds
    Owner

    Thanks, @v0lumehi. I agree with sections 1–3 and the order. Decisions:

    1. Schema: the optional meta column, "schema": 1 and the JSON Schema in launcher.json schema 1: a meta column for optional fields, and a JSON Schema for front ends (draft for #388) #418 are the contract. Front ends read launcher.json, never settings.tsv. A new meta key comes with the PR that first uses it, added to META, docs/frontends.md and the schema together.
    2. Growing schema 1: section becomes a plain string in the schema, so ios/launcher: a Picture section, and the thermal cap as a setting #357's Picture section doesn't break validating readers. A reader draws an unknown section after the ones it knows, in list order. kind stays a fixed list, because a reader can't draw a kind it doesn't know. A new kind means schema 2.
    3. Platform stays in the shown column (platform:ios|mac), as Mac app carries the launcher settings; shader_split.py reports its progress #352 put it. No platform key in meta.
    4. Order: launcher.json schema 1: a meta column for optional fields, and a JSON Schema for front ends (draft for #388) #418 → launcher.json: a level for the settings shown first, and the render scale's base size #399 and launcher: sub-settings that appear only when they can do anything (needs column) #355 rebased onto it → a PR for the store and precedence in launcher.c (@v0lumehi, yes please) → Mac: settings window before the game starts #379's window on top of both → Mac: native AppKit app bundle, preferences, and controller mapping #104's mapping parts.
    5. Skipping the window and ignoring saved choices are separate. DM_LAUNCHER=0 only skips the window, and saved choices still apply. Ignoring saved choices gets its own documented switch, defined in the store PR. Launch overrides are never written to the store.

    @Xipzer: please move needs onto #418's meta column. The format is unchanged. @surajmandalcell: please drop #379's own metadata and propose the keys you need (resolution) on #418. The window then reads the shared store, which also fixes inherited environment values being overwritten.

  6. seedds commented on Oct 27, 2026

    @seedds
    Owner

    Progress: the settings schema is on main.

    Next is the store and precedence in dm/mac/launcher.c (section 2 of the proposal; @v0lumehi offered to write it), checked against the list above: Finder launch, an external front end, explicit overrides, and reset after a rebuild with changed defaults.

    @surajmandalcell: could you rebase #379 onto the meta column? Its platform split stays in the shown column (platform:ios|mac). Propose any other keys it needs, such as resolution, in a PR that adds them to META, docs/frontends.md and the schema together. The window should then read and write the shared store once that lands, which also fixes inherited environment values being overwritten.

  7. seedds commented on Oct 27, 2026

    @seedds
    Owner

    The store and precedence landed on main as 6d1e07b (#431).

    • A Mac app's launcher reads the player's saved choices at every start, including from Finder or the Dock. They are launcher.<VAR> strings in the app's preferences domain (its bundle ID), the same keys the iPhone app keeps.
    • Each saved value is checked against what this build offers (DMSettings in Info.plist, copied from launcher.json).
    • The order is: launch environment, the player's env.txt, the bundle's env.txt, saved choices, tested defaults. Launch overrides are never written to the store.
    • DM_SAVED_CHOICES=0 ignores saved choices and leaves them saved. DM_LAUNCHER=0 only skips the iPhone's settings screen, and saved choices still apply.
    • docs/frontends.md has a new Saved choices section for front-end authors.

    @surajmandalcell: #379 can now rebase onto the meta column and this store. The window should read and write launcher.<VAR> in the app's domain and treat a variable that is already set (environment or env.txt) as fixed, as the launcher does.

  8. seedds commented on Oct 27, 2026

    @seedds
    Owner

    The Mac settings window landed as 1e207fa (#379). It is drawn from launcher.json with the iPhone's model and rows, and keeps its choices in the saved-choices store. The schema, the store and the window, steps 1 to 4 of the proposal, are now on main.

    A correction: f646545 (#418) also undid 1963fab and 35e1b6e by mistake. It was built from a diff against an older base. b4f1790 restores both.

    Next on this issue: #104's controller mapping on the same store, #306's front-end guidance, and runtime events.

  9. varyform commented on Oct 27, 2026

    @varyform
    Contributor

    My only proposal is the ability to specify "run" (saves) folder location per mac app.

    That way I can keep my whole library in a single folder, keep all game related stuff in a single folder and, probably, remove --platform=mac from my frontend app altogether.

  10. 44 remaining items

  11. v0lumehi commented on Oct 31, 2026

    @v0lumehi
    ContributorAuthor

    @seedds all six open PRs of this issue are rebased on today's main again, mergeable and green, including clang-tidy (ratchet).

    A suggested order:

    1. Runtime events: DM_EVENTS with frame_loop, shaders, red, end and the launcher's exit (#388) #474, then Runtime events: controller, thermal, save and the background prewarm (#388) #481, then Status notices: controller, heat and background shader prewarm in the game's app (#388) #489. They are a stack, and each branch holds the commits of the one before.
    2. Controller applet: wait for the players the game asks for, in a native sheet #480, Mac app: a dataFolder key keeps a game's save, caches and log in one folder a front end chooses #460 and Mac front end: DM_KEYMAP and DM_PAD_MAP move buttons to other keys and controller buttons #386 in any order. They do not depend on the stack or on each other.

    They touch some of the same places, mostly docs/frontends.md (a new section after Cheats), the run: line in dm/host/tests/Makefile, front_mac.m and dm/mac/launcher.c. So the second one to merge may conflict. Tell me when you merge one and I rebase the rest the same day.

    One change in #474 affects the lint baseline. LOG and MLOG now go through DM_LOG_TAGGED, and its extra branch shifted the clang analyzer's findings in files that never log. Under __clang_analyzer__ the macro now expands to the old plain print, so the findings match main exactly (273). The compiled code is unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions