@gamedevland/engine 0.0.9 → 0.0.10
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.
- package/CHANGELOG.md +42 -0
- package/README.md +71 -2
- package/dist/sdk.d.ts +38 -11
- package/dist/sdk.js +4496 -4289
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.10 — 2026-11-07
|
|
4
|
+
|
|
5
|
+
### Localization and text
|
|
6
|
+
|
|
7
|
+
- Expand `LocaleCatalog` with Spanish, Brazilian Portuguese, German, French, Italian,
|
|
8
|
+
Turkish, Simplified Chinese, Japanese, and Korean, including regional-code normalization.
|
|
9
|
+
- Add `LocalesService.initialize()` with an explicit supported subset and default language.
|
|
10
|
+
Resolve saved manual preferences before device/platform hints, browser preferences, and
|
|
11
|
+
the configured default. Automatic detection does not write a manual preference.
|
|
12
|
+
- Persist explicit `setLang()` choices when storage is configured, tolerate unavailable
|
|
13
|
+
browser storage, and expose locale revisions and disposable change subscriptions.
|
|
14
|
+
- Preserve the English/Russian default subset for existing consumers and support partial
|
|
15
|
+
locale-specific text-style maps.
|
|
16
|
+
- Add measured `Text.fitTo(width, height?)` font fitting without changing node transforms,
|
|
17
|
+
including proportional line-height adjustment and validation of fitting bounds.
|
|
18
|
+
|
|
19
|
+
### Audio
|
|
20
|
+
|
|
21
|
+
- Attempt music autoplay and recover blocked playback on the first trusted canvas
|
|
22
|
+
pointer/touch interaction or eligible keyboard interaction. Engine-owned capture listeners
|
|
23
|
+
run before scene input, work during boot, and are removed on audio disposal.
|
|
24
|
+
- Retry Web Audio resume on each user gesture even if an earlier automatic attempt remains
|
|
25
|
+
pending. Verify context state and handle rejected resume attempts and unavailable audio.
|
|
26
|
+
- Retain blocked HTML media music requests and reuse their sound IDs on native activation
|
|
27
|
+
or Howler unlock. Report confirmed playback separately from pending handles.
|
|
28
|
+
- Deduplicate active/loading music in both `AudioManager` and the default backend. Repeated
|
|
29
|
+
requests and scene changes preserve the handle and playback position; replacing a pending
|
|
30
|
+
track cancels its old Howler queue to prevent stale stops and orphaned playback.
|
|
31
|
+
- Preserve manual pause/stop, music/SFX preferences, channel/platform/background mute gates,
|
|
32
|
+
and terminal listener cleanup. Drop blocked one-shot SFX rather than replaying a backlog.
|
|
33
|
+
- Add optional `AudioBackend.isHandlePlaying()` and `isPlaybackReady()` hooks; existing
|
|
34
|
+
custom backends remain compatible.
|
|
35
|
+
- Reject unknown `channel.*` keys in the existing `system.audio.volumeConfigId` JSON contract.
|
|
36
|
+
Document centrally editable music/SFX channel and per-asset volumes in engine guidelines.
|
|
37
|
+
|
|
38
|
+
### Verification
|
|
39
|
+
|
|
40
|
+
- Add deterministic locale preference/text fitting and browser audio activation regressions,
|
|
41
|
+
plus tests using real Howler queue/event code with a simulated HTML media device.
|
|
42
|
+
- Run these checks in the npm publishing workflow. Simulated browser/media tests do not
|
|
43
|
+
constitute physical iOS/Android or portal-device verification.
|
|
44
|
+
|
|
3
45
|
## 0.0.9 — 2026-11-05
|
|
4
46
|
|
|
5
47
|
- Add opt-in `screen.dpr: 'native'` to target the viewport's physical pixels.
|
package/README.md
CHANGED
|
@@ -12,8 +12,10 @@ Source files are available in [the SDK repository](https://github.com/gamedevlan
|
|
|
12
12
|
|
|
13
13
|
## Changelog
|
|
14
14
|
|
|
15
|
-
**0.0.
|
|
16
|
-
|
|
15
|
+
**0.0.10** adds configurable language preferences, observable locale changes,
|
|
16
|
+
measured text fitting, automatic audio activation recovery, and music deduplication
|
|
17
|
+
across loading, user gestures, and scene changes. The default locale subset and
|
|
18
|
+
existing custom audio backends remain compatible.
|
|
17
19
|
[Full engine changelog](https://github.com/gamedevland/sdk/blob/main/packages/engine/CHANGELOG.md).
|
|
18
20
|
|
|
19
21
|
## Install
|
|
@@ -132,3 +134,70 @@ type declarations may conflict with newer TypeScript DOM library declarations in
|
|
|
132
134
|
MIT. See [LICENSE](./LICENSE). Source code and contributions: [gamedevland/sdk](https://github.com/gamedevland/sdk).
|
|
133
135
|
|
|
134
136
|
Earlier npm releases retain the license included in their respective archives. Third-party dependencies retain their own licenses.
|
|
137
|
+
|
|
138
|
+
## Language preferences
|
|
139
|
+
|
|
140
|
+
`LocaleCatalog` recognizes `en`, `ru`, `es`, `pt-BR`, `de`, `fr`, `it`, `tr`,
|
|
141
|
+
`zh-CN`, `ja`, and `ko`, including regional browser codes. Games opt into their
|
|
142
|
+
own subset with `LocalesService.initialize({ supportedLocales, defaultLocale,
|
|
143
|
+
deviceLocale, storageKey })`. Existing games retain their English/Russian subset.
|
|
144
|
+
The priority is a saved manual choice, a device/platform hint, browser language
|
|
145
|
+
preferences, then the configured default. Initialization never persists an
|
|
146
|
+
automatic choice. `setLang()` persists manual choices, including selection of the
|
|
147
|
+
current language; unavailable browser storage does not prevent play. Dictionaries
|
|
148
|
+
must already be present in ConfigStore. `getRevision()` and `subscribe()` support
|
|
149
|
+
live UI updates; `subscribe()` returns an unsubscribe function. Text-style locale
|
|
150
|
+
overrides accept partial maps of supported languages.
|
|
151
|
+
|
|
152
|
+
`Text.fitTo(width, height?)` shrinks the current font to fit measured local bounds
|
|
153
|
+
without changing node transforms. Set the desired font, wrapping and content
|
|
154
|
+
before fitting; invoke it again after a language/content change.
|
|
155
|
+
|
|
156
|
+
### Autoplay and first-interaction recovery
|
|
157
|
+
|
|
158
|
+
Request music normally with `audio.playMusic(id)` when assets and player preferences
|
|
159
|
+
are ready. Do not wait for a scene-specific tap. Engine initialization attaches
|
|
160
|
+
native capture listeners to its canvas before scene input is routed. The audio
|
|
161
|
+
manager attempts Web Audio context resume automatically; a rejected or pending
|
|
162
|
+
attempt is retained for the first trusted pointer/touch interaction or keyboard
|
|
163
|
+
interaction while game/body has focus. Gesture retries call `resume()` before any
|
|
164
|
+
await, independently of any pending automatic attempt. Synthetic events, secondary
|
|
165
|
+
pointers/buttons, repeated/modifier key input, and hidden documents do not activate
|
|
166
|
+
recovery. Listeners belong to `AudioManager` and are removed by `audio.dispose()`.
|
|
167
|
+
|
|
168
|
+
The Howler backend checks the actual context state. HTML media `playerror` preserves
|
|
169
|
+
requested music and retries its existing sound id from native activation or Howler's
|
|
170
|
+
`unlock` event. Repeated taps cannot create layered music. `isMusicPlaying()` reports
|
|
171
|
+
confirmed playback rather than a queued handle. Stale blocked one-shot SFX are
|
|
172
|
+
suppressed/dropped, not replayed after unlock. User/channel disabled flags, manual
|
|
173
|
+
pause/stop, and platform/background mute remain authoritative.
|
|
174
|
+
|
|
175
|
+
`AudioBackend` optionally exposes `isHandlePlaying(handle)` and `isPlaybackReady()`
|
|
176
|
+
for precise state and one-shot suppression; existing custom backends remain compatible.
|
|
177
|
+
The default backend implements both. `AudioManager.attachInteractionTarget(element)`
|
|
178
|
+
is idempotent and is called by Engine; standalone AudioManager hosts should attach
|
|
179
|
+
it to their own game canvas and dispose the manager on teardown.
|
|
180
|
+
|
|
181
|
+
All authored levels belong in a manually editable JSON file selected by
|
|
182
|
+
`system.audio.volumeConfigId`, including `channel.music`, `channel.sfx`, and per-asset
|
|
183
|
+
levels. The SDK validates finite values in `[0, 1]` and rejects unknown channel keys.
|
|
184
|
+
Do not duplicate these values in consumer playback calls. Per-play volume options
|
|
185
|
+
remain available for deliberate runtime envelopes/custom backend integrations.
|
|
186
|
+
|
|
187
|
+
Run `node scripts/verify-audio-activation.mjs` from the SDK workspace to exercise the
|
|
188
|
+
real audio classes with simulated browser contexts/media events. These are deterministic
|
|
189
|
+
unit checks; they do not replace Safari/iOS/Android and embedded-portal device checks.
|
|
190
|
+
|
|
191
|
+
### Music request idempotency
|
|
192
|
+
|
|
193
|
+
Repeated `playMusic()` requests for the same active or loading track preserve its
|
|
194
|
+
handle and seek position. The backend independently deduplicates pending/playing
|
|
195
|
+
music and limits its music channel to one source. Music is engine-scoped and does
|
|
196
|
+
not restart when changing scenes. Source-wide stops must not be queued ahead of
|
|
197
|
+
an undecoded play: Howler can reuse a sound id and later stop the newer request.
|
|
198
|
+
Cancelling undecoded music unloads the old Howl queue and recreates a clean source.
|
|
199
|
+
|
|
200
|
+
Run `node scripts/verify-music-single-instance.mjs` to check the actual bundled
|
|
201
|
+
Howler queue/events against a simulated HTML media device, including cold load,
|
|
202
|
+
boot/main duplicates, unlock races, preserved seek, mute/unmute during decoding,
|
|
203
|
+
and cancellation. This supplements `verify-audio-activation.mjs`.
|
package/dist/sdk.d.ts
CHANGED
|
@@ -343,6 +343,10 @@ export interface AudioBackend {
|
|
|
343
343
|
createSource(source: AudioSourceDescriptor): void;
|
|
344
344
|
hasSource(id: AudioId): boolean;
|
|
345
345
|
isHandleActive(handle: AudioHandle): boolean;
|
|
346
|
+
/** Actual playback state, excluding queued/blocked requests. */
|
|
347
|
+
isHandlePlaying?(handle: AudioHandle): boolean;
|
|
348
|
+
/** False while Web Audio is suspended; used to discard stale one-shot SFX. */
|
|
349
|
+
isPlaybackReady?(): boolean;
|
|
346
350
|
play(id: AudioId, options?: PlayAudioOptions): AudioHandle;
|
|
347
351
|
pause(handle: AudioHandle): void;
|
|
348
352
|
stop(handle: AudioHandle): void;
|
|
@@ -371,16 +375,13 @@ export interface IStateStore<T> {
|
|
|
371
375
|
save(value: T): void;
|
|
372
376
|
clear(): void;
|
|
373
377
|
}
|
|
374
|
-
export type SupportedLocaleCode = "en" | "ru";
|
|
375
|
-
/**
|
|
376
|
-
* Canonical supported locale codes used by runtime, build validation, and platform hints.
|
|
377
|
-
*/
|
|
378
|
+
export type SupportedLocaleCode = "en" | "ru" | "es" | "pt-BR" | "de" | "fr" | "it" | "tr" | "zh-CN" | "ja" | "ko";
|
|
379
|
+
/** Canonical locale codes shared by runtime, configs and build tools. */
|
|
378
380
|
export declare class LocaleCatalog {
|
|
379
|
-
static readonly EN
|
|
380
|
-
static readonly RU
|
|
381
|
-
static readonly DEFAULT
|
|
381
|
+
static readonly EN = "en";
|
|
382
|
+
static readonly RU = "ru";
|
|
383
|
+
static readonly DEFAULT = "en";
|
|
382
384
|
private static readonly SUPPORTED_LIST;
|
|
383
|
-
private static readonly SUPPORTED;
|
|
384
385
|
static isSupported(locale: string): locale is SupportedLocaleCode;
|
|
385
386
|
static getSupportedList(): ReadonlyArray<SupportedLocaleCode>;
|
|
386
387
|
static normalize(input: string | null | undefined): SupportedLocaleCode | null;
|
|
@@ -418,7 +419,7 @@ export interface TextStyleDef {
|
|
|
418
419
|
readonly dropShadow?: TextStyleDropShadowDef;
|
|
419
420
|
readonly wordWrap?: boolean;
|
|
420
421
|
readonly wordWrapWidth?: number;
|
|
421
|
-
readonly byLocale?: Readonly<Record<
|
|
422
|
+
readonly byLocale?: Readonly<Partial<Record<SupportedLocaleCode, TextStyleDef>>>;
|
|
422
423
|
}
|
|
423
424
|
/** Map of style name to definition. */
|
|
424
425
|
export type TextStylesConfig = Record<string, TextStyleDef>;
|
|
@@ -556,14 +557,32 @@ export declare class IndexedDbStorageAdapter {
|
|
|
556
557
|
private openDatabase;
|
|
557
558
|
private logStorageError;
|
|
558
559
|
}
|
|
559
|
-
|
|
560
|
+
export interface LocalePreferences {
|
|
561
|
+
readonly supportedLocales: readonly SupportedLocaleCode[];
|
|
562
|
+
readonly defaultLocale?: SupportedLocaleCode;
|
|
563
|
+
readonly deviceLocale?: string | null;
|
|
564
|
+
readonly preferredLocales?: readonly string[];
|
|
565
|
+
readonly storage?: IStorageAdapter;
|
|
566
|
+
readonly storageKey?: string;
|
|
567
|
+
}
|
|
568
|
+
/** Dictionary lookup and persistent, observable language preferences. */
|
|
560
569
|
export declare class LocalesService {
|
|
561
570
|
private readonly configStore;
|
|
562
571
|
private current;
|
|
572
|
+
private supported;
|
|
573
|
+
private defaultLocale;
|
|
574
|
+
private storage;
|
|
575
|
+
private storageKey;
|
|
576
|
+
private revision;
|
|
577
|
+
private readonly listeners;
|
|
563
578
|
private readonly fallback;
|
|
564
579
|
private readonly loadedDictionaries;
|
|
565
580
|
constructor(configStore: ConfigStore, fallback?: readonly string[]);
|
|
581
|
+
initialize(options: LocalePreferences): void;
|
|
566
582
|
setLang(lang: string): void;
|
|
583
|
+
getRevision(): number;
|
|
584
|
+
subscribe(listener: (locale: SupportedLocaleCode) => void): () => void;
|
|
585
|
+
private apply;
|
|
567
586
|
getLang(): SupportedLocaleCode;
|
|
568
587
|
private hasKey;
|
|
569
588
|
t(key: string, params?: Readonly<Record<string, string | number>>): string;
|
|
@@ -867,8 +886,12 @@ export declare class AudioManager {
|
|
|
867
886
|
private musicVolumeInitialized;
|
|
868
887
|
private musicVolumeOverridden;
|
|
869
888
|
private disposed;
|
|
889
|
+
private autoplayAttempt;
|
|
890
|
+
private readonly interactionUnlock;
|
|
870
891
|
static createDefault(assets: AssetManager, options?: AudioManagerOptions): AudioManager;
|
|
871
892
|
constructor(assets: AssetManager, backend: AudioBackend, options?: AudioManagerOptions);
|
|
893
|
+
/** Engine-owned, scene-independent activation on the game's native input target. */
|
|
894
|
+
attachInteractionTarget(target: HTMLElement): void;
|
|
872
895
|
playMusic(assetId: AudioId, options?: PlayAudioOptions): AudioHandle | undefined;
|
|
873
896
|
playSfx(assetId: AudioId, options?: PlayAudioOptions): AudioHandle | undefined;
|
|
874
897
|
preloadInBackground(assetId: AudioId): void;
|
|
@@ -902,6 +925,7 @@ export declare class AudioManager {
|
|
|
902
925
|
private rememberMusicRequest;
|
|
903
926
|
private restoreRememberedMusicIfNeeded;
|
|
904
927
|
private clearActiveMusic;
|
|
928
|
+
private tryAutomaticUnlock;
|
|
905
929
|
private clearRememberedMusicRequest;
|
|
906
930
|
private ensureRememberedMusicSourceInBackground;
|
|
907
931
|
private isChannelEnabled;
|
|
@@ -1587,6 +1611,8 @@ declare class Text$1 extends NodeBase implements TextNode {
|
|
|
1587
1611
|
}): DisplayNode;
|
|
1588
1612
|
get view(): PIXI.Text;
|
|
1589
1613
|
setText(value: string): this;
|
|
1614
|
+
/** Shrink the current font to fit local bounds, preserving node transforms. */
|
|
1615
|
+
fitTo(width: number, height?: number): this;
|
|
1590
1616
|
protected applyAnchor(anchorX: number, anchorY: number): this;
|
|
1591
1617
|
}
|
|
1592
1618
|
export declare abstract class BaseComponent<TNode extends NodeBase = NodeBase> {
|
|
@@ -4904,7 +4930,7 @@ export declare abstract class BaseTypedCommand<TPayload> extends BaseCommand {
|
|
|
4904
4930
|
protected abstract execute(payload: TPayload): Promise<void> | void;
|
|
4905
4931
|
run(data?: Record<string, unknown>): Promise<void> | void;
|
|
4906
4932
|
}
|
|
4907
|
-
export type EngineErrorCodeValue = "CORE_INVALID_ARGUMENT" | "CORE_MISSING_REQUIRED" | "CORE_INVALID_CONFIG" | "CORE_INVARIANT_VIOLATION" | "SCENE_INVALID_ARGUMENT" | "SCENE_INVALID_STATE" | "SCENE_LIFECYCLE_FAILURE" | "DI_INVALID_CONFIG" | "DI_PROVIDER_NOT_FOUND" | "DI_INVALID_PROVIDER" | "DI_DUPLICATE_PROVIDER" | "DI_FORBIDDEN_OVERRIDE" | "DI_UNRESOLVABLE_TOKEN" | "ASSET_MISSING_REQUIRED" | "ASSET_INVALID_CONFIG" | "ASSET_CONFLICT" | "ASSET_LOAD_FAILURE" | "LAYOUT_MISSING_REQUIRED" | "LAYOUT_INVALID_CONFIG" | "LAYOUT_CONFLICT" | "LAYOUT_INVARIANT_VIOLATION" | "AUDIO_MISSING_REQUIRED" | "AUDIO_INVALID_ARGUMENT" | "AUDIO_INVALID_CONFIG" | "AUDIO_CONFLICT" | "PARTICLE_MISSING_REQUIRED" | "PARTICLE_INVALID_ARGUMENT" | "PARTICLE_INVALID_CONFIG" | "PARTICLE_INVARIANT_VIOLATION" | "PARTICLE_RUNTIME_FAILURE" | "PREFAB_MISSING_REQUIRED" | "PREFAB_INVALID_CONFIG" | "PREFAB_CONFLICT" | "PREFAB_INVARIANT_VIOLATION" | "FSM_INVALID_CONFIG" | "FSM_INVALID_STATE" | "COMMAND_MISSING_REQUIRED" | "COMMAND_INVALID_PAYLOAD" | "CONFIG_MISSING_REQUIRED" | "CONFIG_INVALID_ARGUMENT" | "CONFIG_INVALID_SCHEMA";
|
|
4933
|
+
export type EngineErrorCodeValue = "CORE_INVALID_ARGUMENT" | "CORE_MISSING_REQUIRED" | "CORE_INVALID_CONFIG" | "CORE_INVARIANT_VIOLATION" | "SCENE_INVALID_ARGUMENT" | "SCENE_INVALID_STATE" | "SCENE_LIFECYCLE_FAILURE" | "DI_INVALID_CONFIG" | "DI_PROVIDER_NOT_FOUND" | "DI_INVALID_PROVIDER" | "DI_DUPLICATE_PROVIDER" | "DI_FORBIDDEN_OVERRIDE" | "DI_UNRESOLVABLE_TOKEN" | "ASSET_MISSING_REQUIRED" | "ASSET_INVALID_CONFIG" | "ASSET_CONFLICT" | "ASSET_LOAD_FAILURE" | "LAYOUT_MISSING_REQUIRED" | "LAYOUT_INVALID_CONFIG" | "LAYOUT_CONFLICT" | "LAYOUT_INVARIANT_VIOLATION" | "AUDIO_MISSING_REQUIRED" | "AUDIO_INVALID_ARGUMENT" | "AUDIO_INVALID_CONFIG" | "AUDIO_CONFLICT" | "AUDIO_PLAYBACK_BLOCKED" | "PARTICLE_MISSING_REQUIRED" | "PARTICLE_INVALID_ARGUMENT" | "PARTICLE_INVALID_CONFIG" | "PARTICLE_INVARIANT_VIOLATION" | "PARTICLE_RUNTIME_FAILURE" | "PREFAB_MISSING_REQUIRED" | "PREFAB_INVALID_CONFIG" | "PREFAB_CONFLICT" | "PREFAB_INVARIANT_VIOLATION" | "FSM_INVALID_CONFIG" | "FSM_INVALID_STATE" | "COMMAND_MISSING_REQUIRED" | "COMMAND_INVALID_PAYLOAD" | "CONFIG_MISSING_REQUIRED" | "CONFIG_INVALID_ARGUMENT" | "CONFIG_INVALID_SCHEMA";
|
|
4908
4934
|
export declare class EngineErrorCode {
|
|
4909
4935
|
static readonly CoreInvalidArgument = "CORE_INVALID_ARGUMENT";
|
|
4910
4936
|
static readonly CoreMissingRequired = "CORE_MISSING_REQUIRED";
|
|
@@ -4931,6 +4957,7 @@ export declare class EngineErrorCode {
|
|
|
4931
4957
|
static readonly AudioInvalidArgument = "AUDIO_INVALID_ARGUMENT";
|
|
4932
4958
|
static readonly AudioInvalidConfig = "AUDIO_INVALID_CONFIG";
|
|
4933
4959
|
static readonly AudioConflict = "AUDIO_CONFLICT";
|
|
4960
|
+
static readonly AudioPlaybackBlocked = "AUDIO_PLAYBACK_BLOCKED";
|
|
4934
4961
|
static readonly ParticleMissingRequired = "PARTICLE_MISSING_REQUIRED";
|
|
4935
4962
|
static readonly ParticleInvalidArgument = "PARTICLE_INVALID_ARGUMENT";
|
|
4936
4963
|
static readonly ParticleInvalidConfig = "PARTICLE_INVALID_CONFIG";
|