@ikijs/engine 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @ikijs/engine
2
2
 
3
- WebGL2 runtime that plays a [`.iki`](../format) puppet model in the browser.
3
+ WebGL2 runtime that plays a [`.iki`](https://github.com/zeikar/iki/tree/main/packages/format) puppet model in the browser.
4
4
 
5
5
  The engine is **host-agnostic**: it depends only on
6
- [`@ikijs/format`](../format) and knows nothing about any particular app. A host
6
+ [`@ikijs/format`](https://github.com/zeikar/iki/tree/main/packages/format) and knows nothing about any particular app. A host
7
7
  drives it by setting parameters (from lip-sync, gaze, blink, expressions); the
8
8
  engine renders the result each frame.
9
9
 
@@ -30,8 +30,11 @@ player.setParameter(StandardParameter.MouthOpen, 0.7);
30
30
  ```
31
31
 
32
32
  `load()` decodes and uploads every texture before swapping the model in, so a
33
- frame is never half-textured. Parameter writes are clamped to the declared
34
- range; unknown ids and non-finite values are ignored.
33
+ frame is never half-textured. That swap is also why it must be awaited before
34
+ `getParameters()`: an un-awaited `load()` leaves the parameter store empty for
35
+ the rest of the tick, and the engine reports that case rather than let a host
36
+ read `[]` and conclude the model declares no parameters. Parameter writes are
37
+ clamped to the declared range; unknown ids and non-finite values are ignored.
35
38
 
36
39
  ## API
37
40
 
package/dist/index.d.mts CHANGED
@@ -50,6 +50,10 @@ declare class IkiPlayer {
50
50
  private textures;
51
51
  /** Bumped by every `load` and by `destroy`; lets a stale async load bail. */
52
52
  private loadGeneration;
53
+ /** True from the moment `load()` is entered until it resolves or throws. */
54
+ private loadPending;
55
+ /** Latches the un-awaited-load report, so a repeat caller says it once. */
56
+ private warnedLoadUnfinished;
53
57
  private destroyed;
54
58
  /**
55
59
  * Engine-internal mesh buffers, keyed by the part's INDEX in `this.parts`
@@ -78,8 +82,14 @@ declare class IkiPlayer {
78
82
  * Mesh buffer allocation failure IS fatal (unlike per-texture skip) because
79
83
  * textures have an `IkiLoadResult.failedTextures` reporting surface and mesh
80
84
  * buffers have none — there is no partial-mesh concept in the format.
85
+ *
86
+ * AWAIT THIS before reading {@link getParameters}. The swap happens after
87
+ * texture decoding, so an un-awaited `load()` leaves the parameter store
88
+ * empty for the rest of the tick; `getParameters` reports that case rather
89
+ * than letting a host conclude the model drives nothing.
81
90
  */
82
91
  load(model: IkiModel): Promise<IkiLoadResult>;
92
+ private adoptModel;
83
93
  /**
84
94
  * Start the render loop. Safe to call more than once, and a no-op after
85
95
  * {@link destroy} — the program and buffers the loop draws with are gone, so
@@ -101,7 +111,17 @@ declare class IkiPlayer {
101
111
  * mirror's clamping in step with {@link ParameterStore} by hand.
102
112
  */
103
113
  getParameter(id: string): number;
104
- /** The model's parameter descriptors, for building UI or host wiring. */
114
+ /**
115
+ * The model's parameter descriptors, for building UI or host wiring.
116
+ *
117
+ * Empty until the first {@link load} resolves. Reaching it through an
118
+ * un-awaited `load()` is the one mistake in this class that produces no
119
+ * error and no motion: the caller gets `[]`, concludes the model has no
120
+ * parameters, and drives nothing — so that case is reported instead of
121
+ * being indistinguishable from a model that really declares none. A
122
+ * reload is deliberately NOT reported: those parameters are stale rather
123
+ * than absent, and warning there would fire on legitimate concurrent reads.
124
+ */
105
125
  getParameters(): IkiParameter[];
106
126
  destroy(): void;
107
127
  private renderFrame;
@@ -212,6 +232,8 @@ declare class IdleMotion {
212
232
  private swayX;
213
233
  /** Vertical head sway in degrees, pure function of the internal clock. */
214
234
  private swayY;
235
+ /** Head roll sway in degrees, pure function of the internal clock. */
236
+ private swayZ;
215
237
  /** Ease gaze current toward target; pick a new target on the internal clock. */
216
238
  private advanceGaze;
217
239
  }
package/dist/index.d.ts CHANGED
@@ -50,6 +50,10 @@ declare class IkiPlayer {
50
50
  private textures;
51
51
  /** Bumped by every `load` and by `destroy`; lets a stale async load bail. */
52
52
  private loadGeneration;
53
+ /** True from the moment `load()` is entered until it resolves or throws. */
54
+ private loadPending;
55
+ /** Latches the un-awaited-load report, so a repeat caller says it once. */
56
+ private warnedLoadUnfinished;
53
57
  private destroyed;
54
58
  /**
55
59
  * Engine-internal mesh buffers, keyed by the part's INDEX in `this.parts`
@@ -78,8 +82,14 @@ declare class IkiPlayer {
78
82
  * Mesh buffer allocation failure IS fatal (unlike per-texture skip) because
79
83
  * textures have an `IkiLoadResult.failedTextures` reporting surface and mesh
80
84
  * buffers have none — there is no partial-mesh concept in the format.
85
+ *
86
+ * AWAIT THIS before reading {@link getParameters}. The swap happens after
87
+ * texture decoding, so an un-awaited `load()` leaves the parameter store
88
+ * empty for the rest of the tick; `getParameters` reports that case rather
89
+ * than letting a host conclude the model drives nothing.
81
90
  */
82
91
  load(model: IkiModel): Promise<IkiLoadResult>;
92
+ private adoptModel;
83
93
  /**
84
94
  * Start the render loop. Safe to call more than once, and a no-op after
85
95
  * {@link destroy} — the program and buffers the loop draws with are gone, so
@@ -101,7 +111,17 @@ declare class IkiPlayer {
101
111
  * mirror's clamping in step with {@link ParameterStore} by hand.
102
112
  */
103
113
  getParameter(id: string): number;
104
- /** The model's parameter descriptors, for building UI or host wiring. */
114
+ /**
115
+ * The model's parameter descriptors, for building UI or host wiring.
116
+ *
117
+ * Empty until the first {@link load} resolves. Reaching it through an
118
+ * un-awaited `load()` is the one mistake in this class that produces no
119
+ * error and no motion: the caller gets `[]`, concludes the model has no
120
+ * parameters, and drives nothing — so that case is reported instead of
121
+ * being indistinguishable from a model that really declares none. A
122
+ * reload is deliberately NOT reported: those parameters are stale rather
123
+ * than absent, and warning there would fire on legitimate concurrent reads.
124
+ */
105
125
  getParameters(): IkiParameter[];
106
126
  destroy(): void;
107
127
  private renderFrame;
@@ -212,6 +232,8 @@ declare class IdleMotion {
212
232
  private swayX;
213
233
  /** Vertical head sway in degrees, pure function of the internal clock. */
214
234
  private swayY;
235
+ /** Head roll sway in degrees, pure function of the internal clock. */
236
+ private swayZ;
215
237
  /** Ease gaze current toward target; pick a new target on the internal clock. */
216
238
  private advanceGaze;
217
239
  }
package/dist/index.js CHANGED
@@ -452,6 +452,10 @@ var IkiPlayer = class {
452
452
  textures = [];
453
453
  /** Bumped by every `load` and by `destroy`; lets a stale async load bail. */
454
454
  loadGeneration = 0;
455
+ /** True from the moment `load()` is entered until it resolves or throws. */
456
+ loadPending = false;
457
+ /** Latches the un-awaited-load report, so a repeat caller says it once. */
458
+ warnedLoadUnfinished = false;
455
459
  destroyed = false;
456
460
  /**
457
461
  * Engine-internal mesh buffers, keyed by the part's INDEX in `this.parts`
@@ -479,8 +483,21 @@ var IkiPlayer = class {
479
483
  * Mesh buffer allocation failure IS fatal (unlike per-texture skip) because
480
484
  * textures have an `IkiLoadResult.failedTextures` reporting surface and mesh
481
485
  * buffers have none — there is no partial-mesh concept in the format.
486
+ *
487
+ * AWAIT THIS before reading {@link getParameters}. The swap happens after
488
+ * texture decoding, so an un-awaited `load()` leaves the parameter store
489
+ * empty for the rest of the tick; `getParameters` reports that case rather
490
+ * than letting a host conclude the model drives nothing.
482
491
  */
483
492
  async load(model) {
493
+ this.loadPending = true;
494
+ try {
495
+ return await this.adoptModel(model);
496
+ } finally {
497
+ this.loadPending = false;
498
+ }
499
+ }
500
+ async adoptModel(model) {
484
501
  const { gl } = this;
485
502
  const generation = ++this.loadGeneration;
486
503
  const sources = model.textures ?? [];
@@ -692,8 +709,24 @@ var IkiPlayer = class {
692
709
  getParameter(id) {
693
710
  return this.params.get(id);
694
711
  }
695
- /** The model's parameter descriptors, for building UI or host wiring. */
712
+ /**
713
+ * The model's parameter descriptors, for building UI or host wiring.
714
+ *
715
+ * Empty until the first {@link load} resolves. Reaching it through an
716
+ * un-awaited `load()` is the one mistake in this class that produces no
717
+ * error and no motion: the caller gets `[]`, concludes the model has no
718
+ * parameters, and drives nothing — so that case is reported instead of
719
+ * being indistinguishable from a model that really declares none. A
720
+ * reload is deliberately NOT reported: those parameters are stale rather
721
+ * than absent, and warning there would fire on legitimate concurrent reads.
722
+ */
696
723
  getParameters() {
724
+ if (this.loadPending && this.model === void 0 && !this.warnedLoadUnfinished) {
725
+ this.warnedLoadUnfinished = true;
726
+ console.error(
727
+ "Iki: getParameters() ran before load() finished, so it returned an empty list \u2014 await load() before reading parameters."
728
+ );
729
+ }
697
730
  return this.params.list();
698
731
  }
699
732
  destroy() {
@@ -1051,6 +1084,8 @@ var SWAY_X_AMP_B_DEG = 1.3;
1051
1084
  var SWAY_X_PERIOD_B_MS = 9700;
1052
1085
  var SWAY_Y_AMP_DEG = 1.6;
1053
1086
  var SWAY_Y_PERIOD_MS = 7300;
1087
+ var SWAY_Z_AMP_DEG = 1.1;
1088
+ var SWAY_Z_PERIOD_MS = 11300;
1054
1089
  function lerp(a, b, t) {
1055
1090
  return a + (b - a) * t;
1056
1091
  }
@@ -1124,6 +1159,7 @@ var IdleMotion = class {
1124
1159
  this.sink(import_format.StandardParameter.EyeballY, this.gazeCurrentY);
1125
1160
  this.sink(import_format.StandardParameter.AngleX, this.swayX());
1126
1161
  this.sink(import_format.StandardParameter.AngleY, this.swayY());
1162
+ this.sink(import_format.StandardParameter.AngleZ, this.swayZ());
1127
1163
  }
1128
1164
  // ---------------------------------------------------------------------------
1129
1165
  // Private helpers
@@ -1136,6 +1172,7 @@ var IdleMotion = class {
1136
1172
  this.sink(import_format.StandardParameter.EyeballY, 0);
1137
1173
  this.sink(import_format.StandardParameter.AngleX, 0);
1138
1174
  this.sink(import_format.StandardParameter.AngleY, 0);
1175
+ this.sink(import_format.StandardParameter.AngleZ, 0);
1139
1176
  }
1140
1177
  /** Returns the current eye-open value (0..1) and advances blink state. */
1141
1178
  advanceBlink() {
@@ -1167,6 +1204,10 @@ var IdleMotion = class {
1167
1204
  swayY() {
1168
1205
  return SWAY_Y_AMP_DEG * Math.sin(2 * Math.PI * this.clockMs / SWAY_Y_PERIOD_MS);
1169
1206
  }
1207
+ /** Head roll sway in degrees, pure function of the internal clock. */
1208
+ swayZ() {
1209
+ return SWAY_Z_AMP_DEG * Math.sin(2 * Math.PI * this.clockMs / SWAY_Z_PERIOD_MS);
1210
+ }
1170
1211
  /** Ease gaze current toward target; pick a new target on the internal clock. */
1171
1212
  advanceGaze(dt) {
1172
1213
  if (this.clockMs >= this.nextGazeRetargetMs) {