@jgengine/react 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ This reference defines the required visual and interaction quality for JGengine
6
6
 
7
7
  A game must visually own its viewport **and its entire interface**. Presentation is game content: each title designs a custom HUD, menus, feedback, and art direction. It must not resemble a dashboard, landing page, documentation page, ordinary responsive web app, **or another JGengine game's default widget stack**.
8
8
 
9
- The engine does not ship a finished game UI. `@jgengine/react` offers headless data/layout/accessibility seams and optional building blocks. Dropping in stock `StatBar` / `Hotbar` / `Coins` / glass `HudFrame` / theme presets without game-specific composition is incomplete work — not "using the engine correctly."
9
+ The engine ships both headless data/layout/accessibility seams *and* good drop-in building blocks — inventory grids, toggleable windows, character sheets, bars, action bars. `@jgengine/react` offers all of them. Composing and reskinning those building blocks into this game's arrangement is using the engine correctly. What's incomplete is shipping a whole *unarranged, unskinned generic face* — a page that looks like a dashboard or another game's untouched default stack, with no art direction, terminology, or placement of its own.
10
10
 
11
11
  HTML and React are valid implementation tools. Website visual grammar is not the default. Engine default chrome is not the default either.
12
12
 
@@ -51,11 +51,14 @@ Identify the screens the game actually needs:
51
51
 
52
52
  - boot/loading
53
53
  - title or attract screen
54
+ - **main menu** (the game's front-end — see the Main menu section)
54
55
  - mode selection
56
+ - character create / character select where the game has classes, characters, or profiles
55
57
  - onboarding/tutorial
56
58
  - gameplay HUD
57
59
  - pause
58
60
  - settings
61
+ - credits
59
62
  - map/inventory/dialogue where relevant
60
63
  - victory/results
61
64
  - failure/retry
@@ -239,6 +242,12 @@ instead of mirroring external entities into `createMarkerSet`.
239
242
 
240
243
  **Action cards / command bars / ability bars** are a headless data → renderer → chrome split, not a hand-rolled cooldown grid. The model is caller-owned data: build `ActionDef[]` (`@jgengine/core/ui/actionModel` — `id`, optional `label`/`icon`/`hotkey`/`group`/`active`/`disabled`, plus `cooldown` via `actionCooldown`/`actionCooldownFromFraction` and `costs` via `actionCost`); `resolveAction`/`resolveActionCollection` compute `enabled` + ordered blocking `reasons` (cooldown, then unmet cost, then caller reasons). React side (`@jgengine/react`): `useActionBar(defs, { columns, wrap, hotkeys, onActivate })` is the DATA/HOOK layer (`action-bar-model` — focus, hover, `moveGridFocus` keyboard/controller grid nav, `actionByHotkey` routing); `ActionBarChrome` is the composable RENDERER (`action-bar-chrome` — `role="toolbar"`, roving tabindex, `layout: "grid" | "list"`, swap per-item chrome with `renderItem`); `ActionButton` is the default item chrome (`action-button` — radial cooldown wipe, cost line, `Keycap` hotkey, full rest/hover/focus/pressed/active/disabled states, `aria-disabled`/`aria-pressed`/`aria-keyshortcuts`); `ActionBar` is the batteries-included skinned default. A radial UI ignores the chrome and lays out `ActionButton`s from the model. Bind a combat `AbilityKit` with `actionFromAbilitySlot(slot, { label, icon, hotkey, resourceId })` over `useAbilitySlots`. Tooltips use the shared seam (no more DOM `title=` hand-rolling): `actionTooltip(resolved)` → `TooltipContent`, rendered by `ActionTooltip` inside a `Popover` (`popover` — pure `placePopover` viewport flip/clamp math from `@jgengine/core/ui/tooltipModel`, SSR-safe). **Selection panels / party frames / squad summaries** are the same split for entities: feed caller-owned `EntitySummaryDef[]` (`@jgengine/core/ui/selectionModel` — `id`, `name`, `kind`, `icon`, `vitals`, `tags`) to `summarizeSelection` (primary focus, `kind` buckets, `grouped` past a threshold, `selectionWindow` virtualization) or the `useSelectionView` hook (`selection-model` — focus stepping + keyboard nav). Renderers (`@jgengine/react`): `EntityPortrait` (`entity-portrait` selectable tile + health strip), `EntitySummary` (`entity-summary` primary detail with `VitalBar` rows), `SelectionGroupChip` (`selection-group-chip` icon+count), `SelectionCollectionChrome` (`selection-collection` portrait strip that flips to group chips when large, roving tabindex), and `SelectionPanel` (`selection-panel` batteries-included primary-summary-beside-strip default). Keep layout, dimensions, hotkeys, catalog-id mapping, renderer, and chrome caller-owned — the defaults are examples, not fixed RTS rules; never fork the model to reskin.
241
244
 
245
+ **Inventory grid** is already shipped, not hand-rolled: `<InventoryGrid inventoryId columns>` (`@jgengine/react`, backed by the `useInventoryGrid` hook) is a drop-in NxN grid bound to a live inventory — it renders the bound inventory's slots and wires drag-to-move / swap / stack / split through the built-in `inventory.move` / `inventory.split` commands, so a bag is one element plus a reskin, never a re-derived slot matrix over raw divs. It is distinct from the atomic `EquipmentSlots` / `WeaponSlots`, which are single-slot targets for paperdoll and equip layouts — reach for `InventoryGrid` for the bag/backpack surface and the atomic slots for the worn/equipped layout. Reskin via `className`/`data-*` slots and HudTheme tokens; the mechanics stay caller-agnostic.
246
+
247
+ **Toggleable game windows** are already shipped, not hand-rolled open/close state: `usePanels(defs)` + `<PanelHost>` + `<Window>` (`@jgengine/react`) over the headless `@jgengine/core/ui/panelModel` are THE way to build openable windows — bag (`B`), character (`C`), spellbook, quest log — with keybind toggles, drag-to-move, close buttons, ESC-to-close, and z-stack ordering all handled. Declare each window in `defs` (id, keybind, title), mount one `<PanelHost>`, and render each panel's body inside a `<Window>`; the game owns the body content and skin, the model owns focus/stacking/keybind wiring. Never re-derive window toggling from local `useState` booleans and hand-rolled keydown listeners.
248
+
249
+ **Character sheet / paperdoll** is already shipped, not hand-rolled: `<CharacterSheet>` / `<Paperdoll>` / `<StatList>` (`@jgengine/react`) compose the paperdoll equip layout (worn `EquipmentSlots` arranged around a portrait) and the derived-stat readout into one window body. `Paperdoll` is the equip-slot arrangement, `StatList` the derived-stat rows, `CharacterSheet` the batteries-included default that pairs them — the game composes and reskins them (slot layout, stat labels/terminology, skin) rather than re-deriving a paperdoll from raw divs and manual stat math.
250
+
242
251
  **Toast/announcer stacks**: two supported sources, pick the one that matches where the message comes from. An engine event already bound onto `ctx.game.feed` (kill feed, loot log, quest update) renders with `@jgengine/react`'s `ToastStack({ action })` directly. A game-raised one-off message with its own lifetime (boss-intro banner, objective callout) belongs in a `createToastQueue` (`@jgengine/core/game/toasts`, `toast-feed` capability — see `jgengine-gameplay`) rendered from `queue.list()`; don't reimplement the append-cap-plus-TTL-prune loop as a local `Toast`/`pushToast` pair.
243
252
 
244
253
  Do not create a primitive whose only value is wrapping a `div` with border radius.
@@ -296,8 +305,28 @@ Three composable-chrome seams for FPS/TPS polish — a placement hook plus a goo
296
305
  - **Render cues for any custom rig.** `useEntityRenderCues(instanceId, tuning?)` (`@jgengine/shell/render/useEntityRenderCues`) returns the same `cuesRef` the viewmodel gets, for a custom `renderEntity` component: velocity-driven `bobPhase`, `firing`/`recoil` from `ctx.game.playEntityAnimation(instanceId, "fire")`, `reloading` from `"reload"`/`"reloadEnd"`, `hit` from `combat.hitReaction`, `dead` from `entity.died` — no diffing the parent group's position, no game-side module map for attack timing. `RenderCueTuning` (`@jgengine/core/combat/renderCues`) overrides the bob rate / recoil decay / pulse durations; see `jgengine-combat`'s `combat/renderCues` for the underlying pure math.
297
306
  `@jgengine/shell/terrain`'s `ProceduralGround`/`CarvedTerrain`/`GrassField` are the game-facing terrain surface; the height-field math underneath (`arenaField`, `flatField`, `fractalNoise`, `resolveGroundStep`, `valueNoise`, `withNormal`, re-exported from `@jgengine/core/world/terrain`) and `GamePlayerShell`'s physics helpers (`applyMotionImpulses`, `nearbyObstacles`, `resolvePhysicsTuning`, `hasEnvironmentTerrain`, sourced from `core/runtime/motionIntents` / `core/movement`) are internal building blocks the shell composes for you — reach for `defineGame({ movement, world })` and the ground primitives above instead of calling these directly.
298
307
 
308
+ ## Main menu
309
+
310
+ **The main menu is the game's front-end, not a marketing splash.** The hosting website/runner loads straight into the game's own menu (see rejection criteria) — there is no engine-supplied main menu, and the page must not render a title card, tagline, or "enter game" button of its own. What the player first sees is a screen the game designs and skins in its own art direction: the same shape/material/type language as its HUD, not a landing page, hero banner, feature list, or store page. Homage/credit lines are fine as small print; the screen's job is to start play, not to sell it.
311
+
312
+ Compose it from the game's own markup over the headless `StartScreen` (`@jgengine/react`, `start-screen` capability) — a full-bleed `data-jg-menu` overlay that centers your content and offers an opt-in settings corner; it imposes no look. Menu rows are the copy-in `MenuList` / `MenuButton` / `TitleScreen` registry scaffolds (keyboard/controller focus + `KeybindBadge` that self-hides on touch already wired) restyled into the game's chrome — never shipped as raw scaffold.
313
+
314
+ **The front-end entries, when the game has them.** A menu is more than one Start button. Offer the real front-end verbs a player expects, gated to what the game actually supports:
315
+
316
+ - **New Game** — begin a fresh run. When a save exists, keep it distinct from Continue rather than silently overwriting.
317
+ - **Continue / Load** — resume the most recent save, and (when the game keeps more than one) a **Load** entry that lists save slots. Save slots are `createSaveStore` instances keyed by slot id; read them reactively with `useSaveStore` (`@jgengine/react`, offline `localSaveBackend` or cloud `remoteSaveBackend` — same hook, different backend). Disable Continue/Load with a real disabled state when there is nothing to resume; do not hide the verb entirely.
318
+ - **Create Character / Choose Class** — when the game has characters, classes, loadouts, or profiles, the create/select flow lives here as an authored screen, not a stray dropdown. Keep the character/roster model serializable and game-owned (persist it through the game's save store); the menu is its presentation. Do not reach for a genre "class picker" preset — compose the choices this game actually offers.
319
+ - **Load Character / Roster** — choose an existing profile or party when the game persists more than one.
320
+ - **Settings** — reachable from the main menu, always (see Settings menu below). This is one of the two canonical places settings live; pause is the other.
321
+ - **Credits** — a real, reachable screen. Player-facing games owe HUD and website credit (see governance) and record borrowed work in `CREDITS.md`; the menu's Credits entry surfaces that in-game. Read it from the game's own credits data — do not bury attribution only in a build artifact.
322
+ - **Quit / Back to site** — where it makes sense for the host.
323
+
324
+ Only show entries the game supports, and disable (don't delete) verbs that are temporarily unavailable so the menu shape stays legible. Character create/select and Credits are their own authored sub-screens reached from the menu, composed to the same art direction — not modal form dumps.
325
+
299
326
  ## Settings menu
300
327
 
328
+ **Settings is game-owned and reachable from a place that makes sense.** Every game ships settings — but as *its* settings, skinned in its own art direction and placed inline with its own UI, never the engine's stock face dropped into a corner. The two canonical homes are the **main menu** and the **pause** screen; put the entry where the player would look, not floating over live play. The engine gives you the menu's contents and behavior for free — you own its skin, layout choice, and placement. If the default four layouts do not match the game, drive `useSettings()` and render the rows entirely inside your own chrome.
329
+
301
330
  **Settings menu (themed, four layouts, no forced chrome).** The engine builds the whole menu for free — Sound (master + per-bus volume), Graphics (quality/dpr + shadows), Gameplay (FOV slider, default 40–120), Controls (per-action key rebinding, inline click-to-rebind, persisted) — from the game's `audio.buses` and `input` map. What it does **not** do is bolt a fixed gear onto every game: **there is no auto trigger.** You place the entry yourself so it lives *inline with your game's own UI*, never a stray corner overlay. Drop `<SettingsTrigger className=…>` (from `@jgengine/react`) anywhere in your HUD or menu — headless button, `className` for skin/placement, optional `children` to replace the default gear glyph, renders nothing when there's nothing to show. Or call `useSettings().open()` from your own control. Tune the menu via `defineGame({ settings })` (`GameSettingsConfig` from `@jgengine/core/settings/settingsModel`):
302
331
 
303
332
  - `variant: "panel" | "sheet" | "sidebar" | "fullscreen"` — the layout + skin (default `panel`; `sheet` is the mobile bottom-sheet). All four are fixed-size (no shrink-to-content jitter) and read the game's `--jg-*` theme tokens, falling back to a neutral dark skin.
@@ -472,7 +501,7 @@ Require revision when any of these are true:
472
501
  - controls overlap each other
473
502
  - keyboard instructions appear on touch devices
474
503
  - large instruction panels remain visible during gameplay
475
- - more than three ordinary card-like panels are persistently visible
504
+ - more than three undifferentiated, equally-weighted card-like panels are persistently visible (this is about generic card soup — using the shipped windows/bars is fine; the fault is unstyled boxes with no hierarchy)
476
505
  - the main action looks like a standard website button
477
506
  - pause resembles a generic website modal
478
507
  - victory/failure is only text plus restart
@@ -487,6 +516,11 @@ Require revision when any of these are true:
487
516
  - ordinary document flow determines the HUD layout
488
517
  - generic default styling is used because no art direction was written
489
518
  - the hosting website page renders its own title, tagline, or "enter game" button instead of loading straight into the game's own menu
519
+ - the game's own main menu reads as a marketing/landing page (hero banner, feature list, store framing) rather than a game front-end
520
+ - the game supports saves, characters, or classes but the main menu offers no Continue/Load or character create/select entry
521
+ - Continue/Load is hidden rather than shown disabled when there is nothing to resume
522
+ - settings ship as the engine's unskinned stock menu instead of the game's own skin and placement, or sit floating over live play with no sensible home
523
+ - credits are absent from the game, or attribution lives only in a build artifact instead of a reachable in-game screen
490
524
 
491
525
  ## 15. Compact implementation API appendix
492
526
 
@@ -494,7 +528,7 @@ Use the main `jgengine` skill for the authoritative engine API routing. The Reac
494
528
 
495
529
  Use these APIs to bind state; do not let API wiring dictate visual composition. Keybind labels should derive from the game’s binding table, and UI actions should dispatch through game commands rather than existing only as click handlers.
496
530
 
497
- Headless components are not finished design. They are behavior and accessibility seams that the game must art-direct.
531
+ The shipped building blocks — inventory grids, toggleable windows, character sheets, bars — are legitimate to compose and are the right starting point; reach for them instead of re-deriving inventory, windowing, or a paperdoll. But neither they nor the headless seams are finished design on their own: the game must still arrange, place, name, and art-direct them into its own look. Compose the building blocks, then skin and lay them out — don't ship them unarranged.
498
532
 
499
533
  ## Definition of done
500
534
 
@@ -507,6 +541,9 @@ UI work is complete only when:
507
541
  - mobile controls do not cover critical content
508
542
  - touch controls match the genre and game identity
509
543
  - title, pause, and results screens feel authored
544
+ - the main menu is a game-owned front-end (New Game / Continue / Load / character create+select / Settings / Credits as the game supports them), not a marketing splash
545
+ - settings are game-skinned and reachable from the main menu and pause, not a stock corner overlay
546
+ - credits are reachable in-game and record borrowed work
510
547
  - interaction states and transitions are present
511
548
  - screenshots have been reviewed and revised
512
549
  - the implementation remains accessible and performant