@kurot/core 2.5.3 → 2.5.4

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 CHANGED
@@ -4,9 +4,50 @@ All notable changes to `@kurot/core` are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## [2.5.4] — 2026-10-12
8
+
9
+ Prepared for publication; not yet published.
10
+
11
+ ### Fixed
12
+
13
+ - Fix Web Audio repeats to use one native looping source, retaining the total
14
+ `loops` count and first-play offset. Finite playback ends on the audio clock;
15
+ joins no longer wait for JavaScript ended callbacks.
16
+ - Wrap channel position per iteration, freeze finite completion, suppress queued
17
+ callbacks after manual stop and release source/gain nodes.
18
+ - Use native HTML looping for unlimited fallback playback; retain finite fallback
19
+ counting and document its browser-dependent timing limitation.
20
+ - Add unit and minified browser regressions, including sample comparisons with
21
+ Tentax's application-owned MP3. See [audio playback](docs/audio-playback.md).
22
+
23
+ ### Compatibility
24
+
25
+ - Preserve existing `Sound.play(startTime, loops)` parameters: non-positive counts
26
+ are unlimited, positive counts are total plays, and the offset applies only to
27
+ the first play. No public API, dependency, resource or XML-format change.
28
+ - Existing UI/Game/DragonBones/Spine/ui-runtime peers accept 2.5.4 without new
29
+ SDK releases or raised minima. Development and application locks are unchanged.
30
+ Apps adopt published 2.5.4 through their installation/lock and rebuild; exact
31
+ Core pins must be updated explicitly. CLI and headless packages need no release.
32
+ - Published Core 2.5.3 does not contain the fix. Continuous scheduling reproduces
33
+ decoded samples; it does not trim asset silence or repair waveform boundaries.
34
+
35
+ ### Verification — 2026-10-12
36
+
37
+ - Core build and all 941 unit tests pass (81 files), including 41 audio tests.
38
+ - All 22 minified browser checks pass, covering 44.1/48 kHz, finite/unlimited
39
+ counts and first-play offsets. Tentax's current MP3 has zero inserted/missing
40
+ frames across unlimited/two-play joins and stops at the exact finite boundary.
41
+ Its unchanged music manager crosses the real-time boundary on one source
42
+ during a 200 ms control-thread stall, using an isolated checkout Core bundle.
43
+ - Clean prepublish build and package packing verify version 2.5.4, all 642 files,
44
+ native looping/finite scheduling, declarations and public docs. Packed files
45
+ match the checkout byte for byte; tests and the application MP3 are excluded.
46
+ Dependency declarations are unchanged and `git diff --check` passes.
47
+
7
48
  ## [2.5.3] — 2026-10-10
8
49
 
9
- Prepared; not yet published. Published 2.5.2 remains the registry baseline.
50
+ Published; package download, registry integrity and tested alignment modules verified on 2026-10-10.
10
51
 
11
52
  ### Changed
12
53
 
@@ -21,9 +62,9 @@ Prepared; not yet published. Published 2.5.2 remains the registry baseline.
21
62
 
22
63
  - Text with omitted alignment can move within a taller box. Set top explicitly
23
64
  for content intended to begin there; XML is not migrated automatically.
24
- - Prepared ui-document 0.13.1 records middle for all four UI text types without
65
+ - Published ui-document 0.13.1 records middle for all four UI text types without
25
66
  adding a Core dependency or inserting omitted properties. Apps adopt runtime
26
- and authoring installations/locks explicitly after publication and rebuild.
67
+ and authoring installations/locks explicitly and rebuild.
27
68
  - No public API, runtime dependency, resource or XML-format change. Existing SDK
28
69
  peers accept the patch without another UI/Game/DragonBones/Spine/runtime release.
29
70
  The standalone bitmap-font kernel retains its own layout default.
package/README.md CHANGED
@@ -1,13 +1,29 @@
1
1
  # @kurot/core
2
2
 
3
- **2.5.3 is prepared, not yet published.** TextField and BitmapText now default
4
- to middle alignment, inherited by UI text components. Explicit top/bottom remain
5
- effective and multiline controls only line generation. Published 2.5.2 remains
6
- the verified npm baseline. See [CHANGELOG.md](CHANGELOG.md).
3
+ **2.5.4 is prepared for publication; not yet published.** It fixes continuous
4
+ Web Audio loops while preserving `Sound.play(startTime, loops)` semantics.
5
+ Published 2.5.3 remains the verified registry baseline and does not include this
6
+ audio correction. See [CHANGELOG.md](CHANGELOG.md).
7
7
 
8
8
  A modern rewrite of the Egret game engine. Maintains Egret-compatible display object and event APIs while upgrading the rendering architecture, type safety, and tooling.
9
9
 
10
- > **Package version: 2.5.3 (prepared, not published).** Targets ES2022 and evergreen browsers (Chrome / Edge / Firefox / Safari). No IE / old-Android / pre-2022 Safari support shims.
10
+ > **Package version: 2.5.4 (prepared for publication).** Targets ES2022 and evergreen browsers (Chrome / Edge / Firefox / Safari). No IE / old-Android / pre-2022 Safari support shims.
11
+
12
+ ## Continuous audio loops in 2.5.4
13
+
14
+ Core 2.5.4 loops Web Audio through one native source, with
15
+ finite counts ending on the audio clock. Existing `sound.play(startTime, loops)`
16
+ semantics remain: `loops <= 0` is unlimited, positive `loops` counts total plays,
17
+ and the offset applies only to the first play. Position wraps per iteration;
18
+ manual stop suppresses completion and releases nodes. HTML unlimited playback
19
+ uses native looping; finite HTML fallback retains browser-dependent transitions.
20
+
21
+ After 2.5.4 is published, update the application's Core installation/lock and
22
+ rebuild to adopt the fix. Exact Core pins require an explicit declaration update;
23
+ compatible ranges such as `^2.5.3` already accept it. SDK peers and development
24
+ locks remain unchanged; UI/Game/DragonBones/Spine/runtime need no new release.
25
+ No new dependency or public API is introduced.
26
+ See [audio playback](docs/audio-playback.md) for asset boundaries and tests.
11
27
 
12
28
  ## Default middle alignment in 2.5.3
13
29
 
@@ -26,8 +42,8 @@ its bitmap-font line frame. Nominal measurement and font-fitting sizes are uncha
26
42
  Content that omitted alignment may move within a taller box. Set
27
43
  `verticalAlign = "top"` explicitly when it should start at the top, such as a
28
44
  paragraph in a fixed-height region. No XML is rewritten automatically.
29
- Authoring catalog defaults match in prepared ui-document 0.13.1; the headless
30
- bitmap-font kernel is unchanged. After publication, adopt Core 2.5.3 in the
45
+ Authoring catalog defaults match in published ui-document 0.13.1; the headless
46
+ bitmap-font kernel is unchanged. Adopt Core 2.5.3 in the
31
47
  application installation/lock and rebuild. Existing SDK peers accept it without
32
48
  new SDK releases or higher minima. See [text layout](docs/text-layout.md#default-middle-alignment-core-253).
33
49
 
@@ -178,8 +194,10 @@ matching UI release; Core does not expose `textFit` or `minFontSize`.
178
194
 
179
195
  ## Installation
180
196
 
197
+ After 2.5.4 is published:
198
+
181
199
  ```bash
182
- pnpm add @kurot/core@^2.5.2
200
+ pnpm add @kurot/core@^2.5.4
183
201
  ```
184
202
 
185
203
  Core depends on `@kurot/bitmap-font ^0.1.0` for bitmap-font data and shared
@@ -422,8 +440,8 @@ Release regression gates are deliberately machine-scoped. Set a stable
422
440
  name and exact browser version match. Timing limits use both observed variance
423
441
  and a 12% floor; a baseline row needs at least five samples before it can gate.
424
442
 
425
- Deterministic visual regression tests cover Canvas 2D, WebGL 1, WebGL 2, and
426
- WebGL context restoration:
443
+ Visual regression is a local, on-demand check for renderer changes. The tests
444
+ cover Canvas 2D, WebGL 1, WebGL 2, and WebGL context restoration:
427
445
 
428
446
  ```bash
429
447
  pnpm test:visual
@@ -433,6 +451,17 @@ After an intentional renderer change has been visually reviewed, regenerate the
433
451
  golden images with `pnpm test:visual:update`. Failed comparisons retain expected,
434
452
  actual, and diff images under the ignored local `test-results/` directory.
435
453
 
454
+ The golden scene explicitly uses middle-aligned text, matching Core 2.5.3.
455
+ It loads its bundled Chakra Petch bold font before layout, so glyph-block
456
+ placement does not depend on a system Arial substitute. The font and its SIL
457
+ Open Font License are in `examples/visual-regression/fonts/`. Text-alignment
458
+ tests separately check the default and explicit top/middle/bottom modes.
459
+ Baselines live in `examples/visual-regression/tests/snapshots/<platform>/`
460
+ because font rasterization and ink metrics can differ between operating systems
461
+ even with the same font file. `pnpm test:visual:update` updates only the current
462
+ platform's baselines. Review text-layout changes before updating snapshots and
463
+ keep the existing pixel thresholds.
464
+
436
465
  Resource-lifetime soak tests repeatedly rebuild scenes, upload and dispose
437
466
  textures, pressure blur framebuffer pools, force garbage collection, and restore
438
467
  a lost WebGL context. The short release smoke is:
@@ -25,6 +25,11 @@ export declare class Sound extends EventDispatcher<SoundEvents> {
25
25
  private _generation;
26
26
  get length(): number;
27
27
  load(url: string): void;
28
+ /**
29
+ * Starts at startTime seconds on the first play, then repeats from zero.
30
+ * loops <= 0 repeats indefinitely; positive loops is the total play count.
31
+ * Web Audio loops continuously; finite counts end on the audio clock.
32
+ */
28
33
  play(startTime?: number, loops?: number): SoundChannel;
29
34
  close(): void;
30
35
  private loadWebAudio;
@@ -1 +1 @@
1
- {"version":3,"file":"Sound.d.ts","sourceRoot":"","sources":["../../../src/kurot/media/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC5C,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;IACxB,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;CACtC;AAED,eAAO,MAAM,SAAS;;;CAGZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAoDnE;;;;GAIG;AACH,qBAAa,KAAM,SAAQ,eAAe,CAAC,WAAW,CAAC;IAG/C,IAAI,EAAE,SAAS,CAAoB;IAE1C,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,MAAM,CAAC,CAAmB;IAClC,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAK;IAIxB,IAAW,MAAM,IAAI,MAAM,CAI1B;IAIM,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAavB,IAAI,CAAC,SAAS,SAAI,EAAE,KAAK,SAAI,GAAG,YAAY;IAa5C,KAAK,IAAI,IAAI;IAYpB,OAAO,CAAC,YAAY;IAkCpB,OAAO,CAAC,aAAa;CA0BrB"}
1
+ {"version":3,"file":"Sound.d.ts","sourceRoot":"","sources":["../../../src/kurot/media/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC5C,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;IACxB,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;CACtC;AAED,eAAO,MAAM,SAAS;;;CAGZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAoDnE;;;;GAIG;AACH,qBAAa,KAAM,SAAQ,eAAe,CAAC,WAAW,CAAC;IAG/C,IAAI,EAAE,SAAS,CAAoB;IAE1C,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,MAAM,CAAC,CAAmB;IAClC,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAK;IAIxB,IAAW,MAAM,IAAI,MAAM,CAI1B;IAIM,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAa9B;;;;OAIG;IACI,IAAI,CAAC,SAAS,SAAI,EAAE,KAAK,SAAI,GAAG,YAAY;IAa5C,KAAK,IAAI,IAAI;IAYpB,OAAO,CAAC,YAAY;IAkCpB,OAAO,CAAC,aAAa;CA0BrB"}
@@ -79,6 +79,11 @@ export class Sound extends EventDispatcher {
79
79
  this.loadHtmlAudio(url, generation);
80
80
  }
81
81
  }
82
+ /**
83
+ * Starts at startTime seconds on the first play, then repeats from zero.
84
+ * loops <= 0 repeats indefinitely; positive loops is the total play count.
85
+ * Web Audio loops continuously; finite counts end on the audio clock.
86
+ */
82
87
  play(startTime = 0, loops = 0) {
83
88
  if (!this._loaded) {
84
89
  IOErrorEvent.dispatchIOErrorEvent(this);
@@ -1 +1 @@
1
- {"version":3,"file":"Sound.js","sourceRoot":"","sources":["../../../src/kurot/media/Sound.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Sound.js","sourceRoot":"","sources":["../../../src/kurot/media/Sound.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,8 @@ export declare class SoundChannel extends EventDispatcher {
13
13
  private _audioBuffer?;
14
14
  private _webAudioStartTime;
15
15
  private _startOffset;
16
+ private _playbackDuration?;
17
+ private _endPosition;
16
18
  private _audio?;
17
19
  private _stopped;
18
20
  constructor(context: AudioContext | undefined, audioBuffer: AudioBuffer | undefined, audio: HTMLAudioElement | undefined, startTime: number, loops: number);
@@ -22,6 +24,7 @@ export declare class SoundChannel extends EventDispatcher {
22
24
  stop(): void;
23
25
  private playWebAudio;
24
26
  private onWebAudioEnded;
27
+ private disconnectWebAudio;
25
28
  private onHtmlAudioEnded;
26
29
  }
27
30
  //# sourceMappingURL=SoundChannel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SoundChannel.d.ts","sourceRoot":"","sources":["../../../src/kurot/media/SoundChannel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D;;;GAGG;AACH,qBAAa,YAAa,SAAQ,eAAe;IAGhD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAK;IAEpB,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,SAAS,CAAC,CAAW;IAC7B,OAAO,CAAC,aAAa,CAAC,CAAwB;IAC9C,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,YAAY,CAAK;IAEzB,OAAO,CAAC,MAAM,CAAC,CAAmB;IAElC,OAAO,CAAC,QAAQ,CAAS;gBAKxB,OAAO,EAAE,YAAY,GAAG,SAAS,EACjC,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,KAAK,EAAE,gBAAgB,GAAG,SAAS,EACnC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM;IAsBd,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,MAAM,CAAC,KAAK,EAAE,MAAM,EAI9B;IAED,IAAW,QAAQ,IAAI,MAAM,CAK5B;IAIM,IAAI,IAAI,IAAI;IAoBnB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,eAAe,CAUrB;IAEF,OAAO,CAAC,gBAAgB,CAWtB;CACF"}
1
+ {"version":3,"file":"SoundChannel.d.ts","sourceRoot":"","sources":["../../../src/kurot/media/SoundChannel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D;;;GAGG;AACH,qBAAa,YAAa,SAAQ,eAAe;IAGhD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAK;IAEpB,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,SAAS,CAAC,CAAW;IAC7B,OAAO,CAAC,aAAa,CAAC,CAAwB;IAC9C,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,iBAAiB,CAAC,CAAS;IACnC,OAAO,CAAC,YAAY,CAAK;IAEzB,OAAO,CAAC,MAAM,CAAC,CAAmB;IAElC,OAAO,CAAC,QAAQ,CAAS;gBAKxB,OAAO,EAAE,YAAY,GAAG,SAAS,EACjC,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,KAAK,EAAE,gBAAgB,GAAG,SAAS,EACnC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM;IAyBd,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,MAAM,CAAC,KAAK,EAAE,MAAM,EAI9B;IAED,IAAW,QAAQ,IAAI,MAAM,CAU5B;IAIM,IAAI,IAAI,IAAI;IAoBnB,OAAO,CAAC,YAAY;IA2BpB,OAAO,CAAC,eAAe,CAMrB;IAEF,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,gBAAgB,CAWtB;CACF"}
@@ -15,6 +15,8 @@ export class SoundChannel extends EventDispatcher {
15
15
  _audioBuffer;
16
16
  _webAudioStartTime = 0;
17
17
  _startOffset = 0;
18
+ _playbackDuration;
19
+ _endPosition = 0;
18
20
  _audio;
19
21
  _stopped = false;
20
22
  // ── Constructor (Web Audio) ───────────────────────────────────────────────
@@ -31,8 +33,11 @@ export class SoundChannel extends EventDispatcher {
31
33
  }
32
34
  else if (audio) {
33
35
  this._audio = audio;
36
+ this._audio.loop = loops <= 0 || loops === Infinity;
34
37
  this._audio.currentTime = startTime;
35
- this._audio.addEventListener('ended', this.onHtmlAudioEnded);
38
+ if (!this._audio.loop) {
39
+ this._audio.addEventListener('ended', this.onHtmlAudioEnded);
40
+ }
36
41
  this._audio.play();
37
42
  }
38
43
  }
@@ -48,8 +53,15 @@ export class SoundChannel extends EventDispatcher {
48
53
  this._audio.volume = this._volume;
49
54
  }
50
55
  get position() {
51
- if (this._context && this._bufferSource) {
52
- return this._context.currentTime - this._webAudioStartTime + this._startOffset;
56
+ if (this._context && this._audioBuffer) {
57
+ if (this._stopped)
58
+ return this._endPosition;
59
+ const duration = this._audioBuffer.duration;
60
+ const elapsed = Math.max(0, this._context.currentTime - this._webAudioStartTime);
61
+ if (this._playbackDuration !== undefined && elapsed >= this._playbackDuration)
62
+ return duration;
63
+ const position = this._startOffset + elapsed;
64
+ return position < duration ? position : duration > 0 ? position % duration : 0;
53
65
  }
54
66
  return this._audio?.currentTime ?? 0;
55
67
  }
@@ -61,11 +73,11 @@ export class SoundChannel extends EventDispatcher {
61
73
  if (this._bufferSource) {
62
74
  this._bufferSource.onended = null;
63
75
  this._bufferSource.stop();
64
- this._bufferSource.disconnect();
65
- this._bufferSource = undefined;
66
76
  }
77
+ this.disconnectWebAudio();
67
78
  if (this._audio) {
68
79
  this._audio.removeEventListener('ended', this.onHtmlAudioEnded);
80
+ this._audio.loop = false;
69
81
  this._audio.pause();
70
82
  this._audio.currentTime = 0;
71
83
  }
@@ -77,25 +89,44 @@ export class SoundChannel extends EventDispatcher {
77
89
  const source = this._context.createBufferSource();
78
90
  this._bufferSource = source;
79
91
  source.buffer = this._audioBuffer;
92
+ const duration = this._audioBuffer.duration;
93
+ // Positive fractional counts historically play ceil(loops) times; NaN plays once.
94
+ const count = this._loops <= 0 ? Infinity : this._loops > 0 ? Math.ceil(this._loops) : 1;
95
+ source.loop = count > 1;
96
+ source.loopStart = 0;
97
+ source.loopEnd = duration;
80
98
  source.connect(this._gainNode);
81
99
  this._gainNode.gain.value = this._volume;
82
100
  source.onended = this.onWebAudioEnded;
83
101
  this._webAudioStartTime = this._context.currentTime;
84
- source.start(0, this._startOffset);
85
- }
86
- onWebAudioEnded = () => {
87
- if (this._stopped)
88
- return;
89
- this._loopCount++;
90
- if (this._loops <= 0 || this._loopCount < this._loops) {
91
- this._startOffset = 0;
92
- this.playWebAudio();
102
+ const offset = Math.min(this._startOffset, duration);
103
+ if (Number.isFinite(count)) {
104
+ // The initial partial play counts once; subsequent iterations start at zero.
105
+ this._playbackDuration = duration - offset + duration * (count - 1);
106
+ source.start(this._webAudioStartTime, this._startOffset, this._playbackDuration);
93
107
  }
94
108
  else {
95
- this._stopped = true;
96
- this.dispatchEventWith(Event.SOUND_COMPLETE);
109
+ source.start(this._webAudioStartTime, this._startOffset);
97
110
  }
111
+ this._startOffset = offset;
112
+ }
113
+ onWebAudioEnded = () => {
114
+ if (this._stopped || this._playbackDuration === undefined)
115
+ return;
116
+ this._stopped = true;
117
+ this._endPosition = this._audioBuffer?.duration ?? 0;
118
+ this.disconnectWebAudio();
119
+ this.dispatchEventWith(Event.SOUND_COMPLETE);
98
120
  };
121
+ disconnectWebAudio() {
122
+ if (this._bufferSource) {
123
+ this._bufferSource.onended = null;
124
+ this._bufferSource.disconnect();
125
+ this._bufferSource = undefined;
126
+ }
127
+ this._gainNode?.disconnect();
128
+ this._gainNode = undefined;
129
+ }
99
130
  onHtmlAudioEnded = () => {
100
131
  if (this._stopped)
101
132
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"SoundChannel.js","sourceRoot":"","sources":["../../../src/kurot/media/SoundChannel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D;;;GAGG;AACH,MAAM,OAAO,YAAa,SAAQ,eAAe;IAChD,6EAA6E;IAErE,MAAM,CAAS;IACf,UAAU,GAAG,CAAC,CAAC;IACf,OAAO,GAAG,CAAC,CAAC;IAEZ,QAAQ,CAAgB;IACxB,SAAS,CAAY;IACrB,aAAa,CAAyB;IACtC,YAAY,CAAe;IAC3B,kBAAkB,GAAG,CAAC,CAAC;IACvB,YAAY,GAAG,CAAC,CAAC;IAEjB,MAAM,CAAoB;IAE1B,QAAQ,GAAG,KAAK,CAAC;IAEzB,6EAA6E;IAE7E,YACC,OAAiC,EACjC,WAAoC,EACpC,KAAmC,EACnC,SAAiB,EACjB,KAAa;QAEb,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QAEpB,IAAI,OAAO,IAAI,WAAW,EAAE,CAAC;YAC5B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;YACxB,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;YAChC,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;YAC9B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;YACtC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,CAAC,YAAY,EAAE,CAAC;QACrB,CAAC;aAAM,IAAI,KAAK,EAAE,CAAC;YAClB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,WAAW,GAAG,SAAS,CAAC;YACpC,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC7D,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,CAAC;IACF,CAAC;IAED,6EAA6E;IAE7E,IAAW,MAAM;QAChB,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IACD,IAAW,MAAM,CAAC,KAAa;QAC9B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAC/C,IAAI,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7D,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;IACpD,CAAC;IAED,IAAW,QAAQ;QAClB,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,YAAY,CAAC;QAChF,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,EAAE,WAAW,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,6EAA6E;IAEtE,IAAI;QACV,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QAErB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CAAC,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;YAClC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;YAC1B,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC;YAChC,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QAChC,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAChE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC;QAC7B,CAAC;IACF,CAAC;IAED,6EAA6E;IAErE,YAAY;QACnB,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAEpE,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC;QAClD,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC;QAC5B,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC;QAClC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC;QACzC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC;QACtC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;QACpD,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC;IAEO,eAAe,GAAG,GAAS,EAAE;QACpC,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACvD,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;YACtB,IAAI,CAAC,YAAY,EAAE,CAAC;QACrB,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAC9C,CAAC;IACF,CAAC,CAAC;IAEM,gBAAgB,GAAG,GAAS,EAAE;QACrC,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACvD,IAAI,CAAC,MAAO,CAAC,WAAW,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,MAAO,CAAC,IAAI,EAAE,CAAC;QACrB,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,MAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACjE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAC9C,CAAC;IACF,CAAC,CAAC;CACF"}
1
+ {"version":3,"file":"SoundChannel.js","sourceRoot":"","sources":["../../../src/kurot/media/SoundChannel.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,19 @@
1
1
  # @kurot/core — AI context map
2
2
 
3
- Core 2.5.3 is prepared, not published. TextField and BitmapText default to
3
+ Core 2.5.4 is prepared for publication; not yet published. It fixes SoundChannel
4
+ loops with one native Web Audio source and audio-clock finite duration.
5
+ Preserve total `loops` counts and initial offset semantics, wrapped position,
6
+ completion once and manual-stop cleanup.
7
+ HTML unlimited loops are native; finite fallback timing is browser-dependent.
8
+ Read [audio playback](audio-playback.md). Registry 2.5.3 and consumer installations
9
+ do not contain this correction. Dependency ranges and consumer/development locks
10
+ are unchanged. Existing SDK peers accept 2.5.4 without bumps; applications adopt
11
+ the published patch through their own installation/lock and rebuild.
12
+
13
+ Core 2.5.3 is published, verified on npm on 2026-10-10. TextField and BitmapText default to
4
14
  middle; inherited UI text components use the same host default. Explicit top/bottom
5
15
  remain effective and multiline only controls line generation. Catalog metadata
6
- matches in prepared ui-document 0.13.1. Existing omitted alignment can move;
16
+ matches in published ui-document 0.13.1. Existing omitted alignment can move;
7
17
  use explicit top when required. Applications remain on their published locks.
8
18
  Read [default alignment](text-layout.md#default-middle-alignment-core-253).
9
19
 
@@ -24,8 +34,8 @@ agent unfamiliar with Kurot does not need to re-derive the architecture from
24
34
  scratch on every session. Treat the package source and its `src/index.ts`
25
35
  barrel as the authority for current behavior and exports.
26
36
 
27
- Package identity: `@kurot/core@2.5.3`, prepared, not published. It provides Kurot's scene graph,
28
- events, rendering, text, resource, network and media runtime. Rendering uses a
37
+ Package identity: `@kurot/core@2.5.4`, prepared for publication. It provides Kurot's
38
+ scene graph, events, rendering, text, resource, network and media runtime. Rendering uses a
29
39
  flat `InstructionSet + RenderPipe` pipeline. ES2022 / evergreen browsers only
30
40
  with `strict: true`. Two
31
41
  rendering backends: WebGL (primary, two-phase build/execute, InstructionSet-
@@ -263,7 +273,7 @@ capture padding so an overflowing viewport cannot inflate surfaces. Edge glyphs
263
273
  retain antialiasing; CanvasRenderer skips fully hidden dynamic glyph rows before
264
274
  stroke painting. Link hits share the offset. Measurements remain nominal.
265
275
  Read [text layout](text-layout.md#default-middle-alignment-core-253). Core 2.5.3
266
- is prepared; published 2.5.2 uses the same geometry but defaults to top. Existing
276
+ is published; 2.5.2 uses the same geometry but defaults to top. Existing
267
277
  peers accept 2.5.3 without another SDK release.
268
278
  Applications update installations/locks and rebuild explicitly.
269
279
 
@@ -1,6 +1,6 @@
1
1
  # @kurot/core architecture
2
2
 
3
- > Package version: 2.5.0 (published; registry version metadata verified on 2026-10-10). See [CHANGELOG.md](../CHANGELOG.md) for release notes.
3
+ > Package version: 2.5.4 (prepared for publication; not yet published). See [CHANGELOG.md](../CHANGELOG.md) for release notes.
4
4
  > The [AI context map](./ai-context.md) covers directories, non-obvious behavior,
5
5
  > terminology and task-to-file references. This document explains design choices
6
6
  > and internal mechanisms for contributors; the two documents complement each other.
@@ -18,6 +18,12 @@ Since 2.2.0, Core uses published `@kurot/bitmap-font ^0.1.0` as the headless fon
18
18
  and layout kernel. Core manages font resources, image-page ownership and glyph
19
19
  drawing in both backends. UI components belong to the UI package.
20
20
 
21
+ Since 2.5.4, SoundChannel schedules Web Audio repeats through one native looping
22
+ source. Finite total counts end on the audio clock, independently of JavaScript
23
+ completion callbacks. HTML unlimited fallback uses native looping; finite
24
+ fallback transitions remain browser-timed. See [audio playback](audio-playback.md)
25
+ for count/offset semantics, lifecycle, asset boundaries and verification.
26
+
21
27
  Since 2.4.0, `DisplayObject.flipX`/`flipY` reflect around a local frame center
22
28
  independently of authored scale and anchors. `matrix` exposes the authored
23
29
  transform; `$getMatrix()` supplies the effective reflection transform to
@@ -0,0 +1,82 @@
1
+ # Sound playback and continuous loops
2
+
3
+ Core 2.5.4 fixes continuous Web Audio looping and is prepared for publication;
4
+ not yet published. Published Core 2.5.3 does not contain this correction.
5
+ Dependency ranges and consumer locks are unchanged. Native Sound loads through
6
+ Web Audio when available and decodable, with HTMLAudioElement as a fallback. `Sound.type` labels
7
+ music/effects; it does not select a different loop implementation.
8
+
9
+ ## Playback contract
10
+
11
+ ```ts
12
+ const infinite = sound.play(0, 0);
13
+ const once = sound.play(0, 1);
14
+ const three = sound.play(1.5, 3);
15
+ ```
16
+
17
+ `loops <= 0` repeats indefinitely. Positive values count total plays, so 1 means
18
+ once and 3 means three plays. `startTime` is an offset in seconds for the first
19
+ play only; repetitions start at zero. The initial partial play counts once,
20
+ including an empty initial play when the offset reaches the buffer's end.
21
+ Existing fractional counts round upward; positive Infinity is unlimited and
22
+ NaN retains the old single-play behavior. Native offset validation is retained.
23
+
24
+ Web Audio uses one AudioBufferSourceNode with `loopStart = 0` and `loopEnd` equal
25
+ to the decoded buffer duration. Repeated plays use `loop = true`; no JavaScript
26
+ callback, timeout or new source is needed at each join. Finite playback passes
27
+ the initial remaining duration plus the full repetitions to `start()` as total
28
+ buffer content. Rendering stops on the audio clock even if the control thread is
29
+ busy or the AudioContext is suspended and later resumed.
30
+
31
+ `position` is seconds within the current iteration, including the first offset.
32
+ It wraps with the audio clock, freezes at buffer duration after finite completion
33
+ and returns zero after an early manual stop. Finite natural completion dispatches
34
+ SOUND_COMPLETE once and disconnects source/gain nodes. `stop()` is idempotent,
35
+ suppresses queued completion callbacks and disconnects both nodes. Volume retains
36
+ its 0–1 range and does not restart playback.
37
+
38
+ HTML fallback uses the element's native `loop` for unlimited playback. Finite
39
+ fallback playback retains ended-based counting because HTMLAudioElement does not
40
+ offer a sample-accurate finite repeat count. Sample-continuous finite looping is
41
+ a Web Audio guarantee; fallback transitions depend on browser media behavior.
42
+
43
+ ## Audio assets and adoption
44
+
45
+ Continuous scheduling does not trim silence, repair musical phrasing, remove
46
+ waveform discontinuities or apply a crossfade. MP3 encoder delay/padding handling
47
+ belongs to the browser decoder. Prepare seamless source material as needed; the
48
+ runtime reproduces the decoded samples at their original loop boundary.
49
+
50
+ After 2.5.4 is published, applications adopt it through their Core installation/
51
+ lock and rebuild. Compatible ranges such as `^2.5.3` already accept it; exact
52
+ Core pins require an explicit declaration update. Existing Core 2.x peers,
53
+ including UI 3.6.0's ^2.5.2, accept 2.5.4 without new SDK versions or raised peer
54
+ minima. Development lock adoption is optional. UI, Game, DragonBones, Spine,
55
+ runtime, CLI and the document kernel have no API migration.
56
+ No application dependency or audio asset is edited for isolated verification.
57
+
58
+ ## Verification
59
+
60
+ Unit tests cover counts, offsets, volume, position, completion, cleanup and HTML
61
+ fallback. Minified browser tests compare every rendered sample against repeated
62
+ decoded PCM at 44.1/48 kHz while holding the control thread. They check both
63
+ continuous output and the exact finite stop boundary, including end offsets.
64
+
65
+ ```sh
66
+ pnpm build
67
+ pnpm test
68
+ pnpm exec playwright test --config examples/visual-regression/playwright.config.ts audio-loop.spec.ts
69
+ KUROT_AUDIO_FIXTURE=/absolute/path/to/gameplay.mp3 \
70
+ pnpm exec playwright test --config examples/visual-regression/playwright.config.ts audio-loop.spec.ts
71
+ ```
72
+
73
+ The optional MP3 remains application-owned and is not copied into the SDK.
74
+
75
+ On 2026-10-12, the build and all 941 unit tests pass, including 41 audio tests;
76
+ all 22 minified browser checks pass. Tentax's gameplay.mp3 decodes to 2,016,000
77
+ frames at 44.1 kHz (45.714285714 seconds). Unlimited and two-play rendering insert
78
+ zero missing frames; the maximum difference from directly repeated decoded PCM
79
+ after the 0.6 gain is 4.77e-8. Finite output stops at the exact expected boundary.
80
+ An isolated minified bundle of Tentax's unchanged SoundMusicManager with built
81
+ checkout Core crosses the real-time music boundary on one source, even with a
82
+ 200 ms control-thread stall at the join. The verification output is muted.
@@ -55,7 +55,7 @@ layout caches assume data does not mutate. Glyph subtextures are borrowed views.
55
55
 
56
56
  BitmapText has text/font, lineSpacing/letterSpacing, smoothing, multiline,
57
57
  left/center/right textAlign and top/middle/bottom verticalAlign. The default is
58
- multiline=true. Prepared Core 2.5.3 defaults verticalAlign to middle; published
58
+ multiline=true. Published Core 2.5.3 defaults verticalAlign to middle; published
59
59
  2.5.2 and earlier default to top. BitmapLabel inherits its host BitmapText default.
60
60
  Explicit top/bottom remain effective in either line mode. Core passes the selected
61
61
  alignment into the unchanged headless kernel, whose standalone layout default
@@ -42,7 +42,7 @@ the browser's grapheme implementation.
42
42
 
43
43
  ## Default middle alignment (Core 2.5.3)
44
44
 
45
- Core 2.5.3 is prepared, not yet published. TextField and BitmapText default to
45
+ Core 2.5.3 is published, verified on npm on 2026-10-10. TextField and BitmapText default to
46
46
  middle, inherited by UI Label, RichLabel, EditableText and BitmapLabel. Explicit
47
47
  `top`, `middle` and `bottom` use the same boundaries described below. Changing
48
48
  `multiline` or TextField type never changes the selected alignment.
@@ -63,7 +63,7 @@ UI component in its parent and does not choose its internal text alignment.
63
63
  Omitted alignment in old content now centers within a taller box. Set top
64
64
  explicitly where content should start at the top, for example a fixed-height
65
65
  paragraph region. Explicit top/bottom, presets and named states are retained;
66
- no source XML is rewritten. Prepared document 0.13.1 reports middle in its catalog
66
+ no source XML is rewritten. Published document 0.13.1 reports middle in its catalog
67
67
  without inserting omitted properties. Editors must adopt matching runtime/catalog
68
68
  installations and remove any private fallback that forces omitted alignment to top.
69
69
  Existing SDK peers accept these patches; no UI/CLI/runtime release or higher peer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kurot/core",
3
- "version": "2.5.3",
3
+ "version": "2.5.4",
4
4
  "description": "Modern rewrite of the Egret game engine — WebGL multi-texture batching, instruction-driven render pipeline, strict TypeScript, Egret-compatible API",
5
5
  "author": "Kelvin <kyia.x52@gmail.com>",
6
6
  "license": "MIT",