@nice2dev/game-engine 1.2.0 → 1.2.2

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.
Files changed (92) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/dist/cjs/ai/BehaviorTree.js +4 -4
  3. package/dist/cjs/audio/AudioBridge.js +3 -1
  4. package/dist/cjs/build-cloud/GodotBuildCloud.js +1 -1
  5. package/dist/cjs/build-cloud/UnrealBuildCloud.js +1 -1
  6. package/dist/cjs/core/GameConfig.js +11 -9
  7. package/dist/cjs/devtools/GameplayAnalytics.js +4 -1
  8. package/dist/cjs/editor/AnimationRetargeting.js +110 -108
  9. package/dist/cjs/editor/CraftingEditor.js +2 -2
  10. package/dist/cjs/editor/NiceGameEditor.js +19 -6
  11. package/dist/cjs/editor/UndoRedoPro.js +49 -47
  12. package/dist/cjs/export/BevyExporter.js +1 -1
  13. package/dist/cjs/export/CocosExporter.js +4 -4
  14. package/dist/cjs/export/Construct3Exporter.js +4 -4
  15. package/dist/cjs/export/DefoldExporter.js +4 -4
  16. package/dist/cjs/export/GDevelopExporter.js +2 -2
  17. package/dist/cjs/export/GameMakerExporter.js +3 -3
  18. package/dist/cjs/export/GodotExporter.js +3 -3
  19. package/dist/cjs/export/MinecraftBedrockExporter.js +2 -2
  20. package/dist/cjs/export/MonoGameExporter.js +2 -2
  21. package/dist/cjs/export/Pico8Exporter.js +6 -6
  22. package/dist/cjs/export/RPGMakerExporter.js +4 -4
  23. package/dist/cjs/export/RobloxExporter.js +5 -5
  24. package/dist/cjs/export/Solar2DExporter.js +2 -2
  25. package/dist/cjs/export/ThreeJSExporter.js +3 -3
  26. package/dist/cjs/export/Tic80Exporter.js +6 -6
  27. package/dist/cjs/export/UnityExporter.js +4 -4
  28. package/dist/cjs/export/UnrealExporter.js +7 -7
  29. package/dist/cjs/export/WebExporter.js +2 -2
  30. package/dist/cjs/godot-integration/components/GodotProjectBrowser.js +4 -4
  31. package/dist/cjs/i18n/useTranslation.js +1 -1
  32. package/dist/cjs/import/GodotSceneImporter.js +4 -4
  33. package/dist/cjs/import/LDtkImporter.js +1 -1
  34. package/dist/cjs/import/OgmoImporter.js +1 -1
  35. package/dist/cjs/import/TiledMapImporter.js +1 -1
  36. package/dist/cjs/input/GamepadNavigation.js +1 -1
  37. package/dist/cjs/inventory/InventorySystem.js +1 -1
  38. package/dist/cjs/native-engine/NetworkingSystem.js +1 -1
  39. package/dist/cjs/rendering/WebGPURenderPipeline.js +41 -30
  40. package/dist/cjs/unity-integration/UnityImport.js +2 -2
  41. package/dist/cjs/unity-integration/components/UnityAssetStoreBrowser.js +4 -4
  42. package/dist/cjs/unity-integration/components/UnityProjectBrowser.js +8 -5
  43. package/dist/cjs/unreal-integration/components/UnrealProjectBrowser.js +5 -5
  44. package/dist/esm/ai/BehaviorTree.js +4 -4
  45. package/dist/esm/audio/AudioBridge.js +3 -1
  46. package/dist/esm/build-cloud/GodotBuildCloud.js +1 -1
  47. package/dist/esm/build-cloud/UnrealBuildCloud.js +1 -1
  48. package/dist/esm/core/GameConfig.js +11 -9
  49. package/dist/esm/devtools/GameplayAnalytics.js +4 -1
  50. package/dist/esm/editor/AnimationRetargeting.js +110 -108
  51. package/dist/esm/editor/CraftingEditor.js +2 -2
  52. package/dist/esm/editor/NiceGameEditor.js +20 -7
  53. package/dist/esm/editor/UndoRedoPro.js +49 -47
  54. package/dist/esm/export/BevyExporter.js +1 -1
  55. package/dist/esm/export/CocosExporter.js +4 -4
  56. package/dist/esm/export/Construct3Exporter.js +4 -4
  57. package/dist/esm/export/DefoldExporter.js +4 -4
  58. package/dist/esm/export/GDevelopExporter.js +2 -2
  59. package/dist/esm/export/GameMakerExporter.js +3 -3
  60. package/dist/esm/export/GodotExporter.js +3 -3
  61. package/dist/esm/export/MinecraftBedrockExporter.js +2 -2
  62. package/dist/esm/export/MonoGameExporter.js +2 -2
  63. package/dist/esm/export/Pico8Exporter.js +6 -6
  64. package/dist/esm/export/RPGMakerExporter.js +4 -4
  65. package/dist/esm/export/RobloxExporter.js +5 -5
  66. package/dist/esm/export/Solar2DExporter.js +2 -2
  67. package/dist/esm/export/ThreeJSExporter.js +3 -3
  68. package/dist/esm/export/Tic80Exporter.js +6 -6
  69. package/dist/esm/export/UnityExporter.js +4 -4
  70. package/dist/esm/export/UnrealExporter.js +7 -7
  71. package/dist/esm/export/WebExporter.js +2 -2
  72. package/dist/esm/godot-integration/components/GodotProjectBrowser.js +5 -5
  73. package/dist/esm/i18n/useTranslation.js +2 -2
  74. package/dist/esm/import/GodotSceneImporter.js +4 -4
  75. package/dist/esm/import/LDtkImporter.js +1 -1
  76. package/dist/esm/import/OgmoImporter.js +1 -1
  77. package/dist/esm/import/TiledMapImporter.js +1 -1
  78. package/dist/esm/input/GamepadNavigation.js +2 -2
  79. package/dist/esm/inventory/InventorySystem.js +1 -1
  80. package/dist/esm/native-engine/NetworkingSystem.js +1 -1
  81. package/dist/esm/rendering/WebGPURenderPipeline.js +41 -30
  82. package/dist/esm/unity-integration/UnityImport.js +2 -2
  83. package/dist/esm/unity-integration/components/UnityAssetStoreBrowser.js +5 -5
  84. package/dist/esm/unity-integration/components/UnityProjectBrowser.js +9 -6
  85. package/dist/esm/unreal-integration/components/UnrealProjectBrowser.js +6 -6
  86. package/dist/types/ai/BehaviorTree.d.ts +4 -4
  87. package/dist/types/editor/CraftingEditor.d.ts +1 -1
  88. package/dist/types/godot-integration/components/GodotProjectBrowser.d.ts +3 -1
  89. package/dist/types/unity-integration/components/UnityAssetStoreBrowser.d.ts +3 -1
  90. package/dist/types/unity-integration/components/UnityProjectBrowser.d.ts +3 -1
  91. package/dist/types/unreal-integration/components/UnrealProjectBrowser.d.ts +3 -1
  92. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,147 @@
1
1
  # @nice2dev/game-engine — Changelog
2
2
 
3
+ ## 1.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 06eb0d2: 1.2.2 — library texts through the translator, a translator in the packages that had none, and the
8
+ end of the hook-dependency warnings. Nothing removed or renamed. Upgrade notes:
9
+ `documentation/omniverk/OMNIVERK_UPGRADE_1.2.2.md`.
10
+
11
+ **Translations.** 2,825 new keys in the global dictionary (en, pl, de, fr, es, uk), 80 in the local
12
+ dictionaries of ui-math, ui-diagrams and ui-presentations: column headers, option labels, texts in
13
+ conditions and templates, messages given to setters, labels returned by helpers, Polish texts that
14
+ were the English default. Relative dates through `Intl.RelativeTimeFormat`, plural forms through
15
+ `…One` / `…Many` keys. ui-science, ui-platform, ui-mobile, auth, licensing, templates, social and
16
+ the icon tools translate through `useNiceTranslation` (new peer `@nice2dev/ui-core ^1.2.0`);
17
+ ui-security reads the global translator when its own provider is absent.
18
+
19
+ **Additions.** `labelKey` on ui-mobile `TabItem` and `SwipeAction` (the presets set it);
20
+ `registerWebAuthn` / `authenticateWebAuthn` take the WebAuthn options `NiceWebAuthnButton` used to
21
+ drop (defaults unchanged); `NiceKeyboardHint` shows `descriptionKey`. Eight icons in
22
+ `@nice2dev/icons` — `NtdPoint`, `NtdSegment`, `NtdPerpendicular`, `NtdParallel`, `NtdMidpoint`,
23
+ `NtdAngleBisector`, `NtdGroup`, `NtdUngroup` — and the same names as `NiceIcon` built-ins.
24
+
25
+ **Behaviour changes, agreed.** ui-math, ui-diagrams and ui-presentations follow the app's global
26
+ `NiceI18nProvider` language when their own provider is absent (they stayed English);
27
+ `NicePresentationEditor` without `lang` takes the surrounding language. Icons drawn as glyphs are
28
+ Nice icons: the `NiceGeometry` tools, Align / Group / Ungroup / Layout of the diagram toolbar, the
29
+ presentation grid toggle, zoom and "Add Track" of `NiceDMXTimeline`. icons-gaming and
30
+ icons-filetypes name an icon by its `title` and are decorative without one, like `createIcon`
31
+ (screen readers read "GiHeart" or "DOCX file icon"); new optional `title` on the gaming icon props.
32
+
33
+ **Hook dependencies (161 → 0).** Fixed along the way: `NiceLowPowerMode` re-rendered forever from
34
+ mount and overflowed the stack on wake; `GenericPlayer` / `NiceGenericPlayerStage` restarted the
35
+ track on a volume change; `NiceDMXTimeline` called `onPlay` on every frame and missed cues added
36
+ during playback; `NiceConflictResolution` `autoResolve` never resolved; `NiceMAM` selected by the
37
+ previous filter; `NiceRulesEngine` "Run All" kept only the last result; `NiceCodeEditor` saved with
38
+ the first render's `onSave`; a consumer's cursor handler switched off `NiceCodeEditorAI`
39
+ completion; `useRadioStream` lost a volume set before the first play; `NiceGameEditor` shortcuts,
40
+ `useVoiceControl` continuous sessions, `NiceDeviceTrust` with a host storing the score.
41
+
42
+ ## 1.2.1
43
+
44
+ ### Patch Changes
45
+
46
+ - 8c82c30: 1.2.1 — the decisions that waited since 1.2.0, and the debts the CI gates were holding. One API
47
+ change, agreed: `NiceIconSuggestorAI` suggests Nice icons only. Two stylesheets to import if you use
48
+ the packages: `@nice2dev/social/style.css`, `@nice2dev/ui-science/style.css`. Upgrade notes:
49
+ `documentation/omniverk/OMNIVERK_UPGRADE_1.2.1.md`.
50
+
51
+ **Tree-shaking.** An import from a package no longer brings the whole package. Module-level
52
+ statements that may have a side effect — a `displayName` assigned after each component (now
53
+ `Object.assign` inside `/* @__PURE__ */`, or under a development check for plain functions),
54
+ unannotated `forwardRef` / `memo` / `createContext` / icon-factory calls, spreads, registries filled
55
+ at import, `<style>` tags injected at import, a direct `eval` in ui-math — are gone. One import from
56
+ each of 53 packages: 5066.5 kB → 1171.6 kB (`@nice2dev/ui` 1928.2 kB → 12.9 kB, icon packs 48–275 kB
57
+ → 1.6–2.6 kB). `defaultViewRegistry` has the Nice\* input controls from the start, each loading its
58
+ module on first render; sixteen controls add their styles to `<head>` on first render.
59
+
60
+ **Dead code.** 405 source files no entry point imported (never on npm) are deleted;
61
+ `NiceClipboardStack` (`@nice2dev/ui`) and `NiceSampleBrowser` (`@nice2dev/ui-audio`), finished and
62
+ documented, are exported.
63
+
64
+ **ui-mobile draws Nice icons.** New peer `@nice2dev/icons ^1.2.0`; `TabIcons`, `DrawerIcons`,
65
+ `HeaderIcons`, the `NiceSwipeActions` presets and the `NiceInfiniteScroll` states are `Ntd*` icons
66
+ instead of pasted `<svg>`, simple glyphs are CSS marks. `@nice2dev/icons` adds `NtdReply` and
67
+ `NtdInbox`; an icon no longer throws without `window.matchMedia`.
68
+
69
+ **`NiceIconSuggestorAI` (breaking, agreed).** `AIIconLibrary` is `'nice'`; `svgPath` and `unicode`
70
+ left `AISuggestedIcon`; the preview is a `<NiceIcon>`, "Copy SVG" is "Copy JSX".
71
+
72
+ **Peer ranges.** Every internal `@nice2dev/*` peer is `^1.2.0` (was as wide as `^1.0.20`), except
73
+ `@nice2dev/icons ^1.2.1` where a package draws an icon new in 1.2.1: ui-mobile (`NtdReply`,
74
+ `NtdInbox`), ui-audio (`NtdMicrophoneOff`) and ui-security (`NtdBackspace`). ui-security, licensing
75
+ (`^1.2.0`) and templates (`^1.2.0`) gain the `@nice2dev/icons` peer: their glyph icons (`⌫`, `←`,
76
+ `→`, `↑`, `↓`) are Nice icons now. `@nice2dev/icons` adds `NtdBackspace`.
77
+
78
+ **Class names inside components.** 834 class names in library stylesheets had no `nice-` prefix
79
+ (`.tab-button`, `.status-badge`, `.selected`, …) and styled every element of that name on the page.
80
+ They follow `nice-{block}__{element}--{modifier}` now — no change on screen. The class theme hooks
81
+ next to `[data-theme]` on seven controls (`dark` on the AI chart / data grid / code and HTML
82
+ editors / form builder, `light-theme` on `NiceMAM` and `NiceVideoAnnotator`) are `nice-dark` /
83
+ `nice-light`; `NiceLowPowerMode` marks `<html>` with `nice-lpm-no-animations` /
84
+ `nice-lpm-reduced-quality`; the `NiceVideoEditor` of `@nice2dev/ui-video` styles its elements under
85
+ `nice-video-studio`, so they no longer collide with the `NiceVideoEditor` of `@nice2dev/ui`.
86
+ Rules for classes nothing rendered are gone, among them the stylesheet of `NiceGanttChart`, which
87
+ left ui-erp in 1.0.10.
88
+
89
+ **Translations.** About 3,400 UI texts in 32 packages were hard-coded; they go through `t()` now,
90
+ with the English text unchanged and pl / de / fr / es / uk in `@nice2dev/i18n` (ui-diagrams,
91
+ ui-presentations and ui-math in their own dictionaries). In `@nice2dev/events` and
92
+ `@nice2dev/admin` that includes column headers, option labels and the statuses and roles they used
93
+ to print raw (`checked-in`, `no-show`) — English labels for those come from the dictionary. ui-3d and ui-graphics read the app's
94
+ global translator, as ui-audio does since 1.2.0 — their own provider overrides it. ui-audio's
95
+ controls used 631 keys the dictionary did not have (English in every language), and the radio
96
+ player showed raw keys (`search_stations`). Texts that were hard-coded **in Polish** — in
97
+ `NiceSidebarNav`, `NiceNetworkDiscoveryPanel`, the backup and smart-home controls,
98
+ `NiceSavedQueryPanel`, `NiceNotificationPreferences`, the ui-buildings panels, the ui-printing
99
+ template editor and the photo editor — default to English now; Polish is in the dictionary. English
100
+ plurals glued on (`result{s}`) are two keys. `presentationsTranslator(lang)` (ui-presentations)
101
+ translates outside its provider.
102
+
103
+ **List props are optional.** 170 list props (`items`, `columns`, `data`, …) in 15 packages that
104
+ were required without a default are optional, with a stable empty default; every one of those
105
+ controls mounts without data (tested). `NiceStockChart` without data no longer computes NaN
106
+ coordinates.
107
+
108
+ **Unstyled components get stylesheets.** 98 components rendered class names no stylesheet had —
109
+ admin panels, auth forms, events rounds and pickers, the ERP invoice / purchase order / VAT table /
110
+ HR dashboard, the radio player, `NiceInbox`, PWA prompts, `NiceViewRenderer`, shells, smart-home,
111
+ … The fixed layout that sat in inline style objects is in classes now, so an app can override it;
112
+ values that depend on data stay inline. **`@nice2dev/social` and `@nice2dev/ui-science` ship a
113
+ stylesheet — import `@nice2dev/social/style.css` / `@nice2dev/ui-science/style.css`.** ui-science's
114
+ `sideEffects` is `["*.css"]`. Colours, spacing and radii in every stylesheet come from theme tokens
115
+ (visible in dark themes: `NiceRecipe` difficulty badges, transcoder states, warehouse heat map).
116
+
117
+ **Icons only from Nice.** About 130 glyphs (`×`, `→`, `↑`) and 100 pasted `<svg>` used as icons
118
+ are Nice icons; 11 `<svg>` stay as the control's own graphic (logos, spinners, checkbox mark). `NiceIcon`
119
+ gains `pin`, `mic-off`, `log-out`, `chevrons-left`, `chevrons-right`, `zoom-in`, `zoom-out`,
120
+ `navigation`, `refresh-ccw`, `minimize` and `smartphone`; `@nice2dev/icons` gains
121
+ `NtdMicrophoneOff`. The `icon` fields of the module, menu, tab, mode, quick-action, scene and admin
122
+ widget DTOs, notification channels and view toolbar items are rendered through `NiceIcon` — they
123
+ used to be printed as text. `NiceRatings` `thumbs` draws shapes instead of emoji.
124
+
125
+ **Dialogs instead of `window.prompt` / `confirm` / `alert`.** New `useNiceDialogs()` — `prompt`,
126
+ `confirm` and `alert` as promises on `NicePromptDialog` / `NiceConfirmDialog` — and
127
+ `NiceConfirmDialog.showReject`. Seventeen controls asked through the browser's blocking dialogs and
128
+ use the library's now (`NiceFileManager`, `NiceHtmlEditor`, `NiceViewRenderer` action `confirm`, the 3D,
129
+ vector and pixel editors, …). Keys pressed in `NicePromptDialog` / `NiceConfirmDialog` no longer
130
+ reach the page's shortcuts (Escape still closes them), and `NicePromptDialog`'s backdrop covers the
131
+ whole viewport.
132
+
133
+ **Smaller fixes.** `NiceFileManager.items` is optional. `NiceMLStudio` status badges: `draft`,
134
+ `registered`, `archived`, `cancelled` get a neutral colour, deploying / stopped deployments theirs.
135
+ Component docs (`doc.json`) list only props the components have. `NiceMobileTabs`: tabs beyond
136
+ `maxTabs` open from "More" (it only logged). `NiceInventoryManager`: new `onCreatePurchaseOrder`;
137
+ without it the "Create PO" button is gone (it only logged). Keyboard: `NiceNavShell` tabs follow the
138
+ WAI-ARIA tabs pattern, clickable `NiceDeviceTile` / `NiceCardGroup` cards and animated icons with
139
+ `trigger="click"` are focusable. ui-math: `CalculatorService.evaluate` parses instead of running
140
+ `new Function` (`asin`, `sinh`, `ceil`, exponents and `angleUnit` were wrong); `ExpressionParser`
141
+ takes `functions`, `constants`, `ignoreCase` and `strict`, multiplies implicitly (`2x`) and reads `^`
142
+ right to left. `InvoiceLine.vatRate` and `VatRate.rate` are documented as fractions (0.23).
143
+ `AmqpServerBinding` is `Record<string, never>`.
144
+
3
145
  ## 1.2.0
4
146
 
5
147
  ### Minor Changes
@@ -128,7 +128,7 @@ class BTNode {
128
128
  this.name = name;
129
129
  }
130
130
  /** Initialize the node */
131
- initialize(context) { }
131
+ initialize(_context) { }
132
132
  /** Reset the node state */
133
133
  reset() {
134
134
  this.lastStatus = 'inactive';
@@ -266,7 +266,7 @@ class BTParallel extends BTComposite {
266
266
  this.failurePolicy = 'one';
267
267
  this.childStatuses = [];
268
268
  }
269
- initialize(context) {
269
+ initialize(_context) {
270
270
  this.childStatuses = new Array(this.children.length).fill('running');
271
271
  }
272
272
  tick(context) {
@@ -350,7 +350,7 @@ class BTRandomSelector extends BTComposite {
350
350
  super('randomSelector', name);
351
351
  this.shuffledOrder = [];
352
352
  }
353
- initialize(context) {
353
+ initialize(_context) {
354
354
  this.shuffle();
355
355
  }
356
356
  shuffle() {
@@ -400,7 +400,7 @@ class BTRandomSequence extends BTComposite {
400
400
  super('randomSequence', name);
401
401
  this.shuffledOrder = [];
402
402
  }
403
- initialize(context) {
403
+ initialize(_context) {
404
404
  this.shuffle();
405
405
  }
406
406
  shuffle() {
@@ -454,7 +454,9 @@ const GameAudioSettings = ({ bridge, className, style, }) => {
454
454
  },
455
455
  }, `${Math.round((volumes[bus] ?? 1) * 100)}%`))));
456
456
  };
457
- GameAudioSettings.displayName = 'GameAudioSettings';
457
+ if (process.env.NODE_ENV !== 'production') {
458
+ GameAudioSettings.displayName = 'GameAudioSettings';
459
+ }
458
460
 
459
461
  exports.AudioBusMixer = AudioBusMixer;
460
462
  exports.DEFAULT_SPATIAL = DEFAULT_SPATIAL;
@@ -274,7 +274,7 @@ window/size/viewport_height=1080
274
274
  renderer/rendering_method="${config.scriptLanguage === 'csharp' ? 'forward_plus' : 'mobile'}"
275
275
  `;
276
276
  }
277
- generateExportPresets(config) {
277
+ generateExportPresets(_config) {
278
278
  return `[preset.0]
279
279
 
280
280
  name="Linux/X11"
@@ -333,7 +333,7 @@ class UnrealBuildCloud {
333
333
  ],
334
334
  }, null, 2);
335
335
  }
336
- generateDefaultGameIni(config) {
336
+ generateDefaultGameIni(_config) {
337
337
  return `[/Script/EngineSettings.GeneralProjectSettings]
338
338
  ProjectID=${this.generateGuid()}
339
339
  ProjectName=Nice2Dev Project
@@ -21,15 +21,17 @@ const DEFAULT_RENDERING = {
21
21
  antiAlias: true,
22
22
  backgroundColor: WHITE,
23
23
  };
24
- const DEFAULT_SETTINGS = {
25
- width: 1280,
26
- height: 720,
27
- targetFps: 60,
28
- maxPlayers: 4,
29
- physics: { ...DEFAULT_PHYSICS },
30
- audio: { ...DEFAULT_AUDIO },
31
- rendering: { ...DEFAULT_RENDERING },
32
- };
24
+ const DEFAULT_SETTINGS = /* @__PURE__ */ (function () {
25
+ return {
26
+ width: 1280,
27
+ height: 720,
28
+ targetFps: 60,
29
+ maxPlayers: 4,
30
+ physics: { ...DEFAULT_PHYSICS },
31
+ audio: { ...DEFAULT_AUDIO },
32
+ rendering: { ...DEFAULT_RENDERING },
33
+ };
34
+ })();
33
35
  class GameConfig {
34
36
  constructor(overrides) {
35
37
  this._settings = mergeSettings(DEFAULT_SETTINGS, overrides ?? {});
@@ -624,6 +624,7 @@ function useGameplayAnalytics(eventBus, telemetryConfig, recordingConfig) {
624
624
  analyticsRef.current = new GameplayAnalytics(eventBus, telemetryConfig, recordingConfig);
625
625
  }
626
626
  return analyticsRef.current;
627
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- the configs are read once, when the instance is made (it has no setters for them)
627
628
  }, [eventBus]);
628
629
  React.useEffect(() => {
629
630
  return () => {
@@ -663,7 +664,9 @@ const AnalyticsDashboard = ({ analytics, className, style, }) => {
663
664
  style: { padding: 16, fontFamily: 'monospace', fontSize: 13, ...style },
664
665
  }, React.createElement('h3', { style: { margin: '0 0 12px 0' } }, 'Gameplay Analytics'), React.createElement('div', { style: statStyle }, React.createElement('span', null, 'Session ID'), React.createElement('code', null, summary.sessionId)), React.createElement('div', { style: statStyle }, React.createElement('span', null, 'Total Events'), React.createElement('span', null, String(summary.totalEvents))), React.createElement('div', { style: statStyle }, React.createElement('span', null, 'Heatmaps'), React.createElement('span', null, String(summary.heatmapCount))), React.createElement('div', { style: statStyle }, React.createElement('span', null, 'Recording'), React.createElement('span', null, summary.isRecording ? `ON (${summary.recordedFrames} frames)` : 'OFF')));
665
666
  };
666
- AnalyticsDashboard.displayName = 'AnalyticsDashboard';
667
+ if (process.env.NODE_ENV !== 'production') {
668
+ AnalyticsDashboard.displayName = 'AnalyticsDashboard';
669
+ }
667
670
 
668
671
  exports.AnalyticsDashboard = AnalyticsDashboard;
669
672
  exports.DEFAULT_HEATMAP_CONFIG = DEFAULT_HEATMAP_CONFIG;
@@ -21,114 +21,116 @@
21
21
  // ============================================================
22
22
  // Math Utilities
23
23
  // ============================================================
24
- const MathUtils = {
25
- vector3Zero() {
26
- return { x: 0, y: 0, z: 0 };
27
- },
28
- vector3One() {
29
- return { x: 1, y: 1, z: 1 };
30
- },
31
- quaternionIdentity() {
32
- return { x: 0, y: 0, z: 0, w: 1 };
33
- },
34
- multiplyQuaternion(a, b) {
35
- return {
36
- x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
37
- y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
38
- z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
39
- w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
40
- };
41
- },
42
- inverseQuaternion(q) {
43
- const lengthSq = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w;
44
- if (lengthSq === 0) {
45
- return this.quaternionIdentity();
46
- }
47
- const inv = 1 / lengthSq;
48
- return {
49
- x: -q.x * inv,
50
- y: -q.y * inv,
51
- z: -q.z * inv,
52
- w: q.w * inv,
53
- };
54
- },
55
- slerpQuaternion(a, b, t) {
56
- let dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
57
- // Ensure shortest path
58
- const bNeg = { ...b };
59
- if (dot < 0) {
60
- dot = -dot;
61
- bNeg.x = -bNeg.x;
62
- bNeg.y = -bNeg.y;
63
- bNeg.z = -bNeg.z;
64
- bNeg.w = -bNeg.w;
65
- }
66
- // Linear interpolation for very close quaternions
67
- if (dot > 0.9995) {
68
- return this.normalizeQuaternion({
69
- x: a.x + t * (bNeg.x - a.x),
70
- y: a.y + t * (bNeg.y - a.y),
71
- z: a.z + t * (bNeg.z - a.z),
72
- w: a.w + t * (bNeg.w - a.w),
73
- });
74
- }
75
- const theta = Math.acos(dot);
76
- const sinTheta = Math.sin(theta);
77
- const wa = Math.sin((1 - t) * theta) / sinTheta;
78
- const wb = Math.sin(t * theta) / sinTheta;
79
- return {
80
- x: wa * a.x + wb * bNeg.x,
81
- y: wa * a.y + wb * bNeg.y,
82
- z: wa * a.z + wb * bNeg.z,
83
- w: wa * a.w + wb * bNeg.w,
84
- };
85
- },
86
- normalizeQuaternion(q) {
87
- const length = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
88
- if (length === 0) {
89
- return this.quaternionIdentity();
90
- }
91
- return {
92
- x: q.x / length,
93
- y: q.y / length,
94
- z: q.z / length,
95
- w: q.w / length,
96
- };
97
- },
98
- lerpVector3(a, b, t) {
99
- return {
100
- x: a.x + (b.x - a.x) * t,
101
- y: a.y + (b.y - a.y) * t,
102
- z: a.z + (b.z - a.z) * t,
103
- };
104
- },
105
- multiplyVector3(v, scalar) {
106
- return { x: v.x * scalar, y: v.y * scalar, z: v.z * scalar };
107
- },
108
- addVector3(a, b) {
109
- return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
110
- },
111
- mirrorVector3X(v) {
112
- return { x: -v.x, y: v.y, z: v.z };
113
- },
114
- mirrorQuaternionX(q) {
115
- return { x: q.x, y: -q.y, z: -q.z, w: q.w };
116
- },
117
- eulerToQuaternion(euler) {
118
- const c1 = Math.cos(euler.x / 2);
119
- const c2 = Math.cos(euler.y / 2);
120
- const c3 = Math.cos(euler.z / 2);
121
- const s1 = Math.sin(euler.x / 2);
122
- const s2 = Math.sin(euler.y / 2);
123
- const s3 = Math.sin(euler.z / 2);
124
- return {
125
- x: s1 * c2 * c3 + c1 * s2 * s3,
126
- y: c1 * s2 * c3 - s1 * c2 * s3,
127
- z: c1 * c2 * s3 + s1 * s2 * c3,
128
- w: c1 * c2 * c3 - s1 * s2 * s3,
129
- };
130
- },
131
- };
24
+ const MathUtils = /* @__PURE__ */ (function () {
25
+ return {
26
+ vector3Zero() {
27
+ return { x: 0, y: 0, z: 0 };
28
+ },
29
+ vector3One() {
30
+ return { x: 1, y: 1, z: 1 };
31
+ },
32
+ quaternionIdentity() {
33
+ return { x: 0, y: 0, z: 0, w: 1 };
34
+ },
35
+ multiplyQuaternion(a, b) {
36
+ return {
37
+ x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
38
+ y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
39
+ z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
40
+ w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
41
+ };
42
+ },
43
+ inverseQuaternion(q) {
44
+ const lengthSq = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w;
45
+ if (lengthSq === 0) {
46
+ return this.quaternionIdentity();
47
+ }
48
+ const inv = 1 / lengthSq;
49
+ return {
50
+ x: -q.x * inv,
51
+ y: -q.y * inv,
52
+ z: -q.z * inv,
53
+ w: q.w * inv,
54
+ };
55
+ },
56
+ slerpQuaternion(a, b, t) {
57
+ let dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
58
+ // Ensure shortest path
59
+ const bNeg = { ...b };
60
+ if (dot < 0) {
61
+ dot = -dot;
62
+ bNeg.x = -bNeg.x;
63
+ bNeg.y = -bNeg.y;
64
+ bNeg.z = -bNeg.z;
65
+ bNeg.w = -bNeg.w;
66
+ }
67
+ // Linear interpolation for very close quaternions
68
+ if (dot > 0.9995) {
69
+ return this.normalizeQuaternion({
70
+ x: a.x + t * (bNeg.x - a.x),
71
+ y: a.y + t * (bNeg.y - a.y),
72
+ z: a.z + t * (bNeg.z - a.z),
73
+ w: a.w + t * (bNeg.w - a.w),
74
+ });
75
+ }
76
+ const theta = Math.acos(dot);
77
+ const sinTheta = Math.sin(theta);
78
+ const wa = Math.sin((1 - t) * theta) / sinTheta;
79
+ const wb = Math.sin(t * theta) / sinTheta;
80
+ return {
81
+ x: wa * a.x + wb * bNeg.x,
82
+ y: wa * a.y + wb * bNeg.y,
83
+ z: wa * a.z + wb * bNeg.z,
84
+ w: wa * a.w + wb * bNeg.w,
85
+ };
86
+ },
87
+ normalizeQuaternion(q) {
88
+ const length = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
89
+ if (length === 0) {
90
+ return this.quaternionIdentity();
91
+ }
92
+ return {
93
+ x: q.x / length,
94
+ y: q.y / length,
95
+ z: q.z / length,
96
+ w: q.w / length,
97
+ };
98
+ },
99
+ lerpVector3(a, b, t) {
100
+ return {
101
+ x: a.x + (b.x - a.x) * t,
102
+ y: a.y + (b.y - a.y) * t,
103
+ z: a.z + (b.z - a.z) * t,
104
+ };
105
+ },
106
+ multiplyVector3(v, scalar) {
107
+ return { x: v.x * scalar, y: v.y * scalar, z: v.z * scalar };
108
+ },
109
+ addVector3(a, b) {
110
+ return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
111
+ },
112
+ mirrorVector3X(v) {
113
+ return { x: -v.x, y: v.y, z: v.z };
114
+ },
115
+ mirrorQuaternionX(q) {
116
+ return { x: q.x, y: -q.y, z: -q.z, w: q.w };
117
+ },
118
+ eulerToQuaternion(euler) {
119
+ const c1 = Math.cos(euler.x / 2);
120
+ const c2 = Math.cos(euler.y / 2);
121
+ const c3 = Math.cos(euler.z / 2);
122
+ const s1 = Math.sin(euler.x / 2);
123
+ const s2 = Math.sin(euler.y / 2);
124
+ const s3 = Math.sin(euler.z / 2);
125
+ return {
126
+ x: s1 * c2 * c3 + c1 * s2 * s3,
127
+ y: c1 * s2 * c3 - s1 * c2 * s3,
128
+ z: c1 * c2 * s3 + s1 * s2 * c3,
129
+ w: c1 * c2 * c3 - s1 * s2 * s3,
130
+ };
131
+ },
132
+ };
133
+ })();
132
134
  // ============================================================
133
135
  // Animation Retargeting System
134
136
  // ============================================================
@@ -417,7 +417,7 @@ class CraftingEditor {
417
417
  this.activeProgress.delete(progressId);
418
418
  this.onCraftingCompleted?.(recipe, outputs, quality);
419
419
  }
420
- determineQuality(recipe, playerId) {
420
+ determineQuality(recipe, _playerId) {
421
421
  const qualities = [
422
422
  'poor',
423
423
  'normal',
@@ -535,7 +535,7 @@ class CraftingEditor {
535
535
  return this.discoveredRecipes.has(recipeId);
536
536
  }
537
537
  // ─── Salvage ───────────────────────────────────────────
538
- canSalvage(itemId, playerId) {
538
+ canSalvage(itemId, _playerId) {
539
539
  const rule = this.salvageRules.get(itemId);
540
540
  if (!rule) {
541
541
  return false;
@@ -121,7 +121,7 @@ function editorReducer(state, action) {
121
121
  return state;
122
122
  }
123
123
  }
124
- const EditorContext = React.createContext(null);
124
+ const EditorContext = /* @__PURE__ */ React.createContext(null);
125
125
  function useEditor() {
126
126
  const ctx = React.useContext(EditorContext);
127
127
  if (!ctx) {
@@ -197,12 +197,20 @@ const NiceGameEditor = ({ project, onSave, className, style, tutorial, }) => {
197
197
  addLog('info', `Created entity "${name}" (#${entity.id})`);
198
198
  return entity.id;
199
199
  }, [world, refreshWorld, addLog]);
200
- // Keyboard shortcuts
200
+ // Keyboard shortcuts. The list is built once; Save and Delete reach the handlers of the latest
201
+ // render through a ref (they are declared below and change with the selection and the props) —
202
+ // until 1.2.2 they called the first render's, so Delete never saw a selection.
203
+ const shortcutActionsRef = React.useRef({ save: () => { }, deleteSelected: () => { } });
201
204
  const shortcuts = React.useMemo(() => [
202
205
  { key: 'z', ctrl: true, action: () => undoStack.undo(), description: 'Undo' },
203
206
  { key: 'z', ctrl: true, shift: true, action: () => undoStack.redo(), description: 'Redo' },
204
207
  { key: 'y', ctrl: true, action: () => undoStack.redo(), description: 'Redo' },
205
- { key: 's', ctrl: true, action: () => handleSave(), description: 'Save' },
208
+ {
209
+ key: 's',
210
+ ctrl: true,
211
+ action: () => shortcutActionsRef.current.save(),
212
+ description: 'Save',
213
+ },
206
214
  {
207
215
  key: 'v',
208
216
  action: () => dispatch({ type: 'SET_TOOL', tool: 'select' }),
@@ -229,13 +237,17 @@ const NiceGameEditor = ({ project, onSave, className, style, tutorial, }) => {
229
237
  action: () => dispatch({ type: 'TOGGLE_GRID' }),
230
238
  description: 'Toggle grid',
231
239
  },
232
- { key: 'Delete', action: () => deleteSelected(), description: 'Delete selected' },
240
+ {
241
+ key: 'Delete',
242
+ action: () => shortcutActionsRef.current.deleteSelected(),
243
+ description: 'Delete selected',
244
+ },
233
245
  {
234
246
  key: 'Escape',
235
247
  action: () => dispatch({ type: 'DESELECT_ALL' }),
236
248
  description: 'Deselect all',
237
249
  },
238
- ], []);
250
+ ], [undoStack]);
239
251
  useKeyboardShortcuts(shortcuts);
240
252
  const handleSave = React.useCallback(() => {
241
253
  if (!onSave) {
@@ -302,7 +314,8 @@ const NiceGameEditor = ({ project, onSave, className, style, tutorial, }) => {
302
314
  }
303
315
  },
304
316
  });
305
- }, [state.selectedEntities, world, undoStack]);
317
+ }, [state.selectedEntities, world, undoStack, refreshWorld, addLog, events]);
318
+ shortcutActionsRef.current = { save: handleSave, deleteSelected };
306
319
  const ctxValue = React.useMemo(() => ({
307
320
  state,
308
321
  dispatch,