@react-shimeji/core 0.2.2 → 0.3.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/dist/index.js CHANGED
@@ -1,32 +1,44 @@
1
1
  // src/dom.ts
2
2
  var DomManager = class {
3
- /** Uses the supplied element only as a mount point for independent mascots. */
3
+ /** Uses the supplied element as the containing block and clipping boundary. */
4
4
  constructor(container, sprites) {
5
5
  this.container = container;
6
6
  this.sprites = sprites;
7
+ const view = container.ownerDocument.defaultView;
8
+ const computedStyle = view?.getComputedStyle(container);
9
+ if (!computedStyle?.position || computedStyle.position === "static") this.applyContainerStyle("position", "relative");
10
+ if (computedStyle?.overflowX === "visible" || !computedStyle?.overflowX) {
11
+ this.applyContainerStyle("overflowX", "clip");
12
+ }
7
13
  }
8
14
  container;
9
15
  sprites;
10
16
  handles = /* @__PURE__ */ new Set();
17
+ restoreContainerStyles = [];
11
18
  /** Reattaches mascot elements if application code temporarily removed them. */
12
19
  ensureMounted() {
13
20
  for (const handle of this.handles) {
14
21
  if (handle.element.parentElement !== this.container) this.container.appendChild(handle.element);
15
22
  }
16
23
  }
17
- /** Returns viewport bounds because fixed mascots use viewport coordinates. */
24
+ /** Returns bounds in the container-local coordinate system. */
18
25
  getBounds() {
19
- const view = this.container.ownerDocument.defaultView ?? window;
20
- return { x: 0, y: 0, width: view.innerWidth, height: view.innerHeight };
26
+ return { x: 0, y: 0, width: this.container.clientWidth, height: this.container.clientHeight };
27
+ }
28
+ /** Converts a viewport client coordinate into container-local coordinates. */
29
+ toLocalPoint(clientX, clientY) {
30
+ const rectangle = this.container.getBoundingClientRect();
31
+ return { x: clientX - rectangle.left, y: clientY - rectangle.top };
21
32
  }
22
33
  /** Creates a mascot node and acquires its spritesheet resource. */
23
34
  createMascot(spec, mascotId, mascotClassName) {
24
35
  const spriteLease = this.sprites.acquire(spec.spritesheet);
25
- const element = document.createElement("div");
36
+ const element = this.container.ownerDocument.createElement("div");
26
37
  element.dataset.shimejiId = mascotId;
38
+ element.setAttribute("aria-hidden", "true");
27
39
  if (mascotClassName) element.className = mascotClassName;
28
- Object.assign(element.style, { position: "fixed", left: "0", top: "0", width: "0", height: "0", pointerEvents: "none", zIndex: "9999", userSelect: "none", willChange: "transform" });
29
- const spriteElement = document.createElement("div");
40
+ Object.assign(element.style, { position: "absolute", left: "0", top: "0", width: "0", height: "0", pointerEvents: "none", zIndex: "9999", userSelect: "none", willChange: "transform" });
41
+ const spriteElement = this.container.ownerDocument.createElement("div");
30
42
  Object.assign(spriteElement.style, { position: "absolute", left: "0", top: "0", backgroundRepeat: "no-repeat", transformOrigin: "center center", pointerEvents: "auto", touchAction: "none", userSelect: "none" });
31
43
  element.appendChild(spriteElement);
32
44
  const handle = { element, spriteElement, spriteLease };
@@ -37,30 +49,34 @@ var DomManager = class {
37
49
  /** Paints one mascot state into its existing DOM nodes. */
38
50
  render(handle, spec, state) {
39
51
  const sprite = this.sprites.resolve(spec, handle.spriteLease, state.sprite);
40
- handle.element.style.transform = `translate3d(${state.x - state.anchorX}px, ${state.y - state.anchorY}px, 0)`;
41
52
  handle.spriteElement.style.left = "0";
42
53
  handle.spriteElement.style.top = "0";
43
54
  handle.spriteElement.style.transform = `scaleX(${state.lookRight ? -1 : 1})`;
44
- if (!sprite) return;
55
+ if (!sprite) {
56
+ handle.element.style.transform = `translate3d(${state.x - state.anchorX}px, ${state.y - state.anchorY}px, 0)`;
57
+ return;
58
+ }
59
+ let width = 128;
60
+ let height = 128;
45
61
  handle.spriteElement.style.backgroundImage = `url("${sprite.url.replaceAll('"', '\\"')}")`;
46
62
  if (sprite.rectangle) {
47
- handle.spriteElement.style.width = `${sprite.rectangle.width}px`;
48
- handle.spriteElement.style.height = `${sprite.rectangle.height}px`;
49
- handle.element.style.width = `${sprite.rectangle.width}px`;
50
- handle.element.style.height = `${sprite.rectangle.height}px`;
63
+ width = sprite.rectangle.width;
64
+ height = sprite.rectangle.height;
51
65
  handle.spriteElement.style.backgroundPosition = `${-sprite.rectangle.x}px ${-sprite.rectangle.y}px`;
52
66
  handle.spriteElement.style.backgroundSize = "auto";
53
67
  } else {
54
68
  handle.spriteElement.style.backgroundPosition = "0 0";
55
69
  handle.spriteElement.style.backgroundSize = "contain";
56
- handle.spriteElement.style.width = "128px";
57
- handle.spriteElement.style.height = "128px";
58
70
  const definition = Object.values(spec.sprites).find((candidate) => typeof candidate === "object" && "url" in candidate && candidate.url === sprite.url);
59
- if (typeof definition === "object" && "width" in definition && definition.width !== void 0) handle.spriteElement.style.width = `${definition.width}px`;
60
- if (typeof definition === "object" && "height" in definition && definition.height !== void 0) handle.spriteElement.style.height = `${definition.height}px`;
61
- handle.element.style.width = handle.spriteElement.style.width;
62
- handle.element.style.height = handle.spriteElement.style.height;
71
+ if (typeof definition === "object" && "width" in definition && definition.width !== void 0) width = definition.width;
72
+ if (typeof definition === "object" && "height" in definition && definition.height !== void 0) height = definition.height;
63
73
  }
74
+ handle.spriteElement.style.width = `${width}px`;
75
+ handle.spriteElement.style.height = `${height}px`;
76
+ handle.element.style.width = `${width}px`;
77
+ handle.element.style.height = `${height}px`;
78
+ const anchorX = state.lookRight ? width - state.anchorX : state.anchorX;
79
+ handle.element.style.transform = `translate3d(${state.x - anchorX}px, ${state.y - state.anchorY}px, 0)`;
64
80
  }
65
81
  /** Removes one mascot node and releases its temporary image URL. */
66
82
  removeMascot(handle) {
@@ -76,6 +92,14 @@ var DomManager = class {
76
92
  /** Removes every mascot element and releases its temporary image URL. */
77
93
  destroy() {
78
94
  for (const handle of [...this.handles]) this.removeMascot(handle);
95
+ for (const restore of this.restoreContainerStyles.splice(0).reverse()) restore();
96
+ }
97
+ applyContainerStyle(property, value) {
98
+ const previous = this.container.style[property];
99
+ this.container.style[property] = value;
100
+ this.restoreContainerStyles.push(() => {
101
+ if (this.container.style[property] === value) this.container.style[property] = previous;
102
+ });
79
103
  }
80
104
  };
81
105
 
@@ -88,6 +112,9 @@ var actionTypeNames = {
88
112
  Animate: "Animate",
89
113
  Move: "Move",
90
114
  Embedded: "Embedded",
115
+ Composite: "Sequence",
116
+ Fixed: "Animate",
117
+ Pause: "Stay",
91
118
  \u8907\u5408: "Sequence",
92
119
  \u9078\u629E: "Select",
93
120
  \u53C2\u7167: "Reference",
@@ -137,12 +164,13 @@ function actionProperty(element, ...names) {
137
164
  function parseAnimation(element) {
138
165
  const poses = directChildren(element, "Pose", "\u30DD\u30FC\u30BA").map((pose) => ({
139
166
  sprite: attribute(pose, "Image", "\u753B\u50CF") ?? "/shime1.png",
140
- anchor: parsePoint(attribute(pose, "Anchor", "\u57FA\u6E96\u5EA7\u6A19"), { x: 64, y: 128 }),
167
+ anchor: parsePoint(attribute(pose, "ImageAnchor", "Anchor", "\u57FA\u6E96\u5EA7\u6A19"), { x: 64, y: 128 }),
141
168
  velocity: parsePoint(attribute(pose, "Velocity", "\u79FB\u52D5\u901F\u5EA6")),
142
169
  duration: Number(attribute(pose, "Duration", "\u9577\u3055") ?? 1)
143
170
  }));
144
171
  const condition = attribute(element, "Condition", "\u6761\u4EF6");
145
- return { poses, ...condition !== void 0 && { condition } };
172
+ const turn = (attribute(element, "IsTurn", "Turn") ?? "false").toLowerCase() === "true";
173
+ return { poses, ...condition !== void 0 && { condition }, ...turn && { turn } };
146
174
  }
147
175
  function parseActionElement(element) {
148
176
  const isReference = element.localName === "ActionReference" || element.localName === "\u52D5\u4F5C\u53C2\u7167";
@@ -166,20 +194,26 @@ function parseActionElement(element) {
166
194
  };
167
195
  const properties = [
168
196
  ["duration", actionProperty(element, "Duration", "\u9577\u3055")],
169
- ["gap", actionProperty(element, "Gap", "\u9593\u9694")],
197
+ ["gap", actionProperty(element, "Gap", "\u9593\u9694", "\u305A\u308C")],
170
198
  ["targetX", actionProperty(element, "TargetX", "\u76EE\u7684\u5730X")],
171
199
  ["targetY", actionProperty(element, "TargetY", "\u76EE\u7684\u5730Y")],
172
- ["velocity", actionProperty(element, "Velocity", "\u901F\u5EA6")],
200
+ ["velocity", actionProperty(element, "VelocityParam", "Velocity", "\u901F\u5EA6")],
173
201
  ["x", actionProperty(element, "X", "\u5909\u4F4DX")],
174
202
  ["y", actionProperty(element, "Y", "\u5909\u4F4DY")],
203
+ ["offsetX", actionProperty(element, "OffsetX", "\u7AEFX")],
204
+ ["offsetY", actionProperty(element, "OffsetY", "\u7AEFY")],
205
+ ["offsetType", actionProperty(element, "OffsetType")],
175
206
  ["initialVx", actionProperty(element, "InitialVX", "InitialVx", "\u521D\u901FX")],
176
207
  ["initialVy", actionProperty(element, "InitialVY", "InitialVy", "\u521D\u901FY")],
177
- ["resistanceX", actionProperty(element, "ResistanceX", "\u7A7A\u6C17\u62B5\u6297X")],
178
- ["resistanceY", actionProperty(element, "ResistanceY", "\u7A7A\u6C17\u62B5\u6297Y")],
208
+ ["resistanceX", actionProperty(element, "RegistanceX", "ResistanceX", "\u7A7A\u6C17\u62B5\u6297X")],
209
+ ["resistanceY", actionProperty(element, "RegistanceY", "ResistanceY", "\u7A7A\u6C17\u62B5\u6297Y")],
179
210
  ["gravity", actionProperty(element, "Gravity", "\u91CD\u529B")],
180
- ["bornX", actionProperty(element, "BornX", "\u8A95\u751FX")],
181
- ["bornY", actionProperty(element, "BornY", "\u8A95\u751FY")],
182
- ["bornBehavior", actionProperty(element, "BornBehavior", "\u8A95\u751F\u6642\u306E\u884C\u52D5")],
211
+ ["bornX", actionProperty(element, "BornX", "\u8A95\u751FX", "\u751F\u307E\u308C\u308B\u5834\u6240X")],
212
+ ["bornY", actionProperty(element, "BornY", "\u8A95\u751FY", "\u751F\u307E\u308C\u308B\u5834\u6240Y")],
213
+ ["bornBehavior", actionProperty(element, "BornBehavior", "BornBehaviour", "\u8A95\u751F\u6642\u306E\u884C\u52D5", "\u751F\u307E\u308C\u305F\u6642\u306E\u884C\u52D5")],
214
+ ["bornMascot", actionProperty(element, "BornMascot")],
215
+ ["bornCount", actionProperty(element, "BornCount")],
216
+ ["bornInterval", actionProperty(element, "BornInterval")],
183
217
  ["ieOffsetX", actionProperty(element, "IEOffsetX", "IE\u306E\u7AEFX")],
184
218
  ["ieOffsetY", actionProperty(element, "IEOffsetY", "IE\u306E\u7AEFY")],
185
219
  ["lookRight", actionProperty(element, "LookRight", "\u53F3\u5411\u304D")]
@@ -195,18 +229,33 @@ function parseActionsXml(xml) {
195
229
  const roots = lists.length ? lists : [document2.documentElement];
196
230
  return roots.flatMap((list) => directChildren(list, "Action", "\u52D5\u4F5C").map(parseActionElement));
197
231
  }
232
+ function parseNextBehaviors(element, inheritedConditions) {
233
+ const behaviors = [];
234
+ for (const child of Array.from(element.children)) {
235
+ if (child.localName === "Condition" || child.localName === "\u6761\u4EF6") {
236
+ const condition = attribute(child, "Condition", "\u6761\u4EF6");
237
+ behaviors.push(...parseNextBehaviors(child, [...inheritedConditions, ...condition ? [condition] : []]));
238
+ } else if (["Behavior", "\u884C\u52D5", "BehaviorReference", "BehaviorReferance", "\u884C\u52D5\u53C2\u7167"].includes(child.localName)) {
239
+ behaviors.push(parseBehaviorElement(child, inheritedConditions, 0));
240
+ }
241
+ }
242
+ return behaviors;
243
+ }
198
244
  function parseBehaviorElement(element, inheritedConditions, groupIndex) {
199
245
  const condition = attribute(element, "Condition", "\u6761\u4EF6");
200
246
  const conditions = [...inheritedConditions, ...condition ? [condition] : []];
201
247
  const nextList = directChildren(element, "NextBehaviorList", "NextBehavior", "\u6B21\u306E\u884C\u52D5\u30EA\u30B9\u30C8")[0];
202
- const nextBehaviors = nextList ? directChildren(nextList, "Behavior", "\u884C\u52D5", "BehaviorReference", "BehaviorReferance", "\u884C\u52D5\u53C2\u7167").map((child) => parseBehaviorElement(child, conditions, 0)) : [];
248
+ const nextBehaviors = nextList ? parseNextBehaviors(nextList, []) : [];
203
249
  const reference = element.localName === "BehaviorReference" || element.localName === "BehaviorReferance" || element.localName === "\u884C\u52D5\u53C2\u7167";
250
+ const actionName = attribute(element, "Action", "\u52D5\u4F5C");
204
251
  return {
205
252
  type: reference ? "Reference" : "Behavior",
206
253
  name: attribute(element, "Name", "\u540D\u524D") ?? "",
207
254
  frequency: Number(attribute(element, "Frequency", "\u983B\u5EA6") ?? 0),
208
255
  conditions,
209
256
  nextBehaviors,
257
+ ...nextList && { nextAdditive: (attribute(nextList, "Add", "\u8FFD\u52A0") ?? "true").toLowerCase() === "true" },
258
+ ...actionName !== void 0 && { actionName },
210
259
  groupIndex,
211
260
  hidden: (attribute(element, "Hidden", "\u975E\u8868\u793A") ?? "false").toLowerCase() === "true"
212
261
  };
@@ -312,7 +361,7 @@ var functions = {
312
361
  };
313
362
  var constants = { E: Math.E, PI: Math.PI };
314
363
  function normalizeExpression(source) {
315
- return source.trim().replace(/^(?:#|\$)\{/, "").replace(/\}$/, "").replace(/Math\.(random|min|max|abs|floor|ceil|round|sqrt|pow|sin|cos|tan|asin|acos|atan|sinh|cosh|tanh|asinh|acosh|atanh|cbrt|log|log2|log10|exp|expm1|log1p|trunc|sign|hypot|atan2)/g, "$1").replace(/Math\.(PI|E)\b/g, "$1").replace(/Mascot\./gi, "mascot.").replace(/TargetX|目的地X/gi, "targetX").replace(/TargetY|目的地Y/gi, "targetY").replace(/FootX|足X/gi, "footX").replace(/FootY|足Y/gi, "footY").replace(/MaxCount/gi, "maxCount").replace(/Gap/gi, "gap").replace(/\band\b/gi, "&&").replace(/\bor\b/gi, "||").replace(/\bnot\b/gi, "!");
364
+ return source.trim().replace(/^(?:#|\$)\{/, "").replace(/\}$/, "").replace(/Math\.(random|min|max|abs|floor|ceil|round|sqrt|pow|sin|cos|tan|asin|acos|atan|sinh|cosh|tanh|asinh|acosh|atanh|cbrt|log|log2|log10|exp|expm1|log1p|trunc|sign|hypot|atan2)/g, "$1").replace(/Math\.(PI|E)\b/g, "$1").replace(/Mascot\./gi, "mascot.").replace(/TargetX|目的地X/gi, "targetX").replace(/TargetY|目的地Y/gi, "targetY").replace(/FootX|足X/gi, "footX").replace(/FootY|足Y/gi, "footY").replace(/VelocityX|速度X/gi, "velocityX").replace(/VelocityY|速度Y/gi, "velocityY").replace(/MaxCount/gi, "maxCount").replace(/Gap|ずれ/gi, "gap").replace(/\band\b/gi, "&&").replace(/\bor\b/gi, "||").replace(/\bnot\b/gi, "!");
316
365
  }
317
366
  function tokenize(source) {
318
367
  const tokens = [];
@@ -513,7 +562,7 @@ function evaluateNode(node, scope) {
513
562
  }
514
563
  }
515
564
  var expressionCache = /* @__PURE__ */ new Map();
516
- function evaluateExpression(expression, environment, fallback) {
565
+ function evaluateExpression(expression, environment, fallback, random = Math.random) {
517
566
  if (expression === void 0) return fallback;
518
567
  if (typeof expression !== "string") return expression;
519
568
  try {
@@ -523,7 +572,7 @@ function evaluateExpression(expression, environment, fallback) {
523
572
  ast = new Parser(tokenize(normalized)).parse();
524
573
  expressionCache.set(normalized, ast);
525
574
  }
526
- const result = evaluateNode(ast, environment);
575
+ const result = evaluateNode(ast, { ...environment, random: (maximum = 1) => random() * Number(maximum) });
527
576
  if (typeof fallback === "boolean") return Boolean(result);
528
577
  const numericResult = Number(result);
529
578
  return Number.isNaN(numericResult) ? fallback : numericResult;
@@ -531,8 +580,8 @@ function evaluateExpression(expression, environment, fallback) {
531
580
  return fallback;
532
581
  }
533
582
  }
534
- function conditionsMatch(conditions, environment) {
535
- return conditions.every((condition) => evaluateExpression(condition, environment, false));
583
+ function conditionsMatch(conditions, environment, random = Math.random) {
584
+ return conditions.every((condition) => evaluateExpression(condition, environment, false, random));
536
585
  }
537
586
  function selectWeighted(items, weight, random = Math.random) {
538
587
  const weighted = items.map((item) => ({ item, weight: Math.max(0, weight(item)) }));
@@ -554,61 +603,71 @@ var BehaviorController = class {
554
603
  spec;
555
604
  random;
556
605
  previous;
606
+ fallbackSelected = false;
557
607
  /** Selects an initial behavior, honoring an explicit requested name when possible. */
558
608
  selectInitial(environment, requestedName) {
609
+ this.fallbackSelected = false;
559
610
  if (requestedName) {
560
611
  const requested = this.spec.behaviors.find((behavior) => behavior.name === requestedName);
561
- if (requested && conditionsMatch(requested.conditions, environment)) return this.previous = this.resolve(requested);
612
+ if (requested) return this.previous = this.resolve(requested);
562
613
  }
563
- const fall = this.findFallBehavior();
564
- if (fall && !this.isOnAnyBoundary(environment)) return this.previous = fall;
565
614
  return this.previous = this.choose(this.spec.behaviors, environment);
566
615
  }
567
616
  /** Selects the weighted transition following the current behavior. */
568
617
  selectNext(environment) {
569
- const pool = this.previous?.nextBehaviors.length ? this.previous.nextBehaviors : this.spec.behaviors;
570
- return this.previous = this.choose(pool, environment) ?? this.findFallBehavior();
618
+ const next = this.previous?.nextBehaviors ?? [];
619
+ const pool = this.previous && this.previous.nextAdditive === false ? next : [...this.spec.behaviors, ...next];
620
+ const selected = this.choose(pool, environment);
621
+ this.fallbackSelected = selected === void 0;
622
+ return this.previous = selected ?? this.findFallBehavior();
623
+ }
624
+ /** Whether the most recent transition had no effective weighted candidate. */
625
+ usedFallback() {
626
+ return this.fallbackSelected;
571
627
  }
572
628
  /** Replaces selection history so an external interaction can force a behavior. */
573
629
  force(name) {
630
+ this.fallbackSelected = false;
574
631
  const behavior = this.spec.behaviors.find((candidate) => candidate.name === name);
575
632
  return this.previous = behavior ? this.resolve(behavior) : void 0;
576
633
  }
577
634
  choose(pool, environment) {
578
- const applicable = pool.filter((behavior) => conditionsMatch(behavior.conditions, environment));
635
+ const applicable = pool.filter((behavior) => conditionsMatch(behavior.conditions, environment, this.random));
579
636
  const chosen = selectWeighted(applicable, (behavior) => behavior.frequency, this.random);
580
637
  return chosen ? this.resolve(chosen) : void 0;
581
638
  }
582
639
  resolve(behavior) {
583
640
  if (behavior.type !== "Reference") return behavior;
584
641
  const target = this.spec.behaviors.find((candidate) => candidate.type === "Behavior" && candidate.name === behavior.name);
585
- return target ? { ...target, ...behavior, type: "Behavior", nextBehaviors: target.nextBehaviors } : { ...behavior, type: "Behavior" };
642
+ return target ? {
643
+ ...target,
644
+ ...behavior,
645
+ type: "Behavior",
646
+ nextBehaviors: target.nextBehaviors,
647
+ ...behavior.actionName !== void 0 ? { actionName: behavior.actionName } : target.actionName !== void 0 ? { actionName: target.actionName } : {},
648
+ ...target.nextAdditive !== void 0 && { nextAdditive: target.nextAdditive }
649
+ } : { ...behavior, type: "Behavior" };
586
650
  }
587
651
  findFallBehavior() {
588
652
  return this.spec.behaviors.find((behavior) => behavior.name === "Fall" || behavior.name === "\u843D\u4E0B\u3059\u308B");
589
653
  }
590
- isOnAnyBoundary(environment) {
591
- const anchor = environment.mascot.anchor;
592
- const area = environment.mascot.environment.workArea;
593
- const activeIE = environment.mascot.environment.activeIE;
594
- return area.topBorder.isOn(anchor) || area.leftBorder.isOn(anchor) || area.rightBorder.isOn(anchor) || area.bottomBorder.isOn(anchor) || activeIE.visible && (activeIE.topBorder.isOn(anchor) || activeIE.leftBorder.isOn(anchor) || activeIE.rightBorder.isOn(anchor) || activeIE.bottomBorder.isOn(anchor));
595
- }
596
654
  };
597
655
 
598
656
  // src/physics.ts
657
+ var BORDER_TOLERANCE = 0.999999;
599
658
  function clamp(value, minimum, maximum) {
600
659
  return Math.min(Math.max(value, minimum), maximum);
601
660
  }
602
- function isOnTop(point, rectangle, tolerance = 1) {
661
+ function isOnTop(point, rectangle, tolerance = BORDER_TOLERANCE) {
603
662
  return point.x >= rectangle.x - tolerance && point.x <= rectangle.x + rectangle.width + tolerance && Math.abs(point.y - rectangle.y) <= tolerance;
604
663
  }
605
- function isOnBottom(point, rectangle, tolerance = 1) {
664
+ function isOnBottom(point, rectangle, tolerance = BORDER_TOLERANCE) {
606
665
  return point.x >= rectangle.x - tolerance && point.x <= rectangle.x + rectangle.width + tolerance && Math.abs(point.y - rectangle.y - rectangle.height) <= tolerance;
607
666
  }
608
- function isOnLeft(point, rectangle, tolerance = 1) {
667
+ function isOnLeft(point, rectangle, tolerance = BORDER_TOLERANCE) {
609
668
  return point.y >= rectangle.y - tolerance && point.y <= rectangle.y + rectangle.height + tolerance && Math.abs(point.x - rectangle.x) <= tolerance;
610
669
  }
611
- function isOnRight(point, rectangle, tolerance = 1) {
670
+ function isOnRight(point, rectangle, tolerance = BORDER_TOLERANCE) {
612
671
  return point.y >= rectangle.y - tolerance && point.y <= rectangle.y + rectangle.height + tolerance && Math.abs(point.x - rectangle.x - rectangle.width) <= tolerance;
613
672
  }
614
673
  function isOnBorder(state, bounds, border, platform) {
@@ -617,29 +676,46 @@ function isOnBorder(state, bounds, border, platform) {
617
676
  if (border === "Ceiling") return isOnTop(state, bounds) || platform !== void 0 && isOnBottom(state, platform);
618
677
  return isOnLeft(state, bounds) || isOnRight(state, bounds) || platform !== void 0 && (isOnLeft(state, platform) || isOnRight(state, platform));
619
678
  }
620
- function applyGravity(state, bounds, frameScale, gravity, resistanceX = 0.05, resistanceY = 0.01, platform) {
621
- const previousY = state.y;
622
- const nextX = clamp(state.x + state.vx * frameScale, bounds.x, bounds.x + bounds.width);
623
- const nextY = clamp(state.y + state.vy * frameScale, bounds.y, bounds.y + bounds.height);
624
- state.x = nextX;
625
- state.y = nextY;
626
- state.vx *= Math.max(0, 1 - resistanceX * frameScale);
627
- state.vy = state.vy * Math.max(0, 1 - resistanceY * frameScale) + gravity * frameScale;
628
- if (platform && nextY >= previousY && previousY <= platform.y && nextY >= platform.y && nextX >= platform.x && nextX <= platform.x + platform.width) {
629
- state.y = platform.y;
630
- state.vy = 0;
631
- return true;
632
- }
633
- if (state.y >= bounds.y + bounds.height) {
634
- state.y = bounds.y + bounds.height;
635
- state.vy = 0;
636
- return true;
637
- }
638
- if (state.x <= bounds.x || state.x >= bounds.x + bounds.width) {
639
- state.vx = 0;
640
- return true;
679
+ function isOnFloor(point, bounds, platforms = []) {
680
+ return platforms.some((platform) => isOnTop(point, platform)) || isOnBottom(point, bounds);
681
+ }
682
+ function isOnWall(point, bounds, lookRight, platforms = []) {
683
+ return lookRight ? platforms.some((platform) => isOnLeft(point, platform)) || isOnRight(point, bounds) : platforms.some((platform) => isOnRight(point, platform)) || isOnLeft(point, bounds);
684
+ }
685
+ function applyGravity(state, bounds, frameScale, gravity, resistanceX = 0.05, resistanceY = 0.1, platform) {
686
+ const platforms = platform ? [platform] : [];
687
+ const steps = Math.max(1, Math.round(frameScale));
688
+ let stopped = false;
689
+ for (let frame = 0; frame < steps && !stopped; frame += 1) {
690
+ state.vx -= state.vx * resistanceX;
691
+ state.vy = state.vy - state.vy * resistanceY + gravity;
692
+ const dx = Math.trunc(state.vx);
693
+ const dy = Math.trunc(state.vy);
694
+ const divisions = Math.max(1, Math.abs(dx), Math.abs(dy));
695
+ const start = { x: state.x, y: state.y };
696
+ for (let index = 0; index <= divisions; index += 1) {
697
+ const x = start.x + Math.trunc(dx * index / divisions);
698
+ const y = start.y + Math.trunc(dy * index / divisions);
699
+ state.x = x;
700
+ state.y = y;
701
+ if (dy > 0) {
702
+ for (let offset = -80; offset <= 0; offset += 1) {
703
+ state.y = y + offset;
704
+ if (isOnFloor(state, bounds, platforms)) {
705
+ stopped = true;
706
+ break;
707
+ }
708
+ }
709
+ if (stopped) break;
710
+ state.y = y;
711
+ }
712
+ if (isOnWall(state, bounds, state.lookRight, platforms)) {
713
+ stopped = true;
714
+ break;
715
+ }
716
+ }
641
717
  }
642
- return false;
718
+ return stopped;
643
719
  }
644
720
  function moveToward(state, target, speed, frameScale) {
645
721
  const dx = target.x - state.x;
@@ -656,251 +732,832 @@ function moveToward(state, target, speed, frameScale) {
656
732
  }
657
733
 
658
734
  // src/action.ts
659
- function numeric(value, environment) {
660
- return value === void 0 ? void 0 : evaluateExpression(value, environment, 0);
735
+ function expressionIsPerFrame(value) {
736
+ return typeof value === "string" && value.trimStart().startsWith("#{");
661
737
  }
662
- function evaluateAction(definition, environment) {
663
- const gap = numeric(definition.gap, environment) ?? 0;
664
- const scopedEnvironment = { ...environment, gap };
665
- const targetX = numeric(definition.targetX, scopedEnvironment);
666
- const initialVx = numeric(definition.initialVx, scopedEnvironment);
667
- let lookRight = environment.mascot.lookRight;
668
- if (definition.borderType === "Wall") {
669
- const { activeIE, workArea } = environment.mascot.environment;
670
- lookRight = workArea.rightBorder.isOn(environment.mascot.anchor) || activeIE.visible && activeIE.leftBorder.isOn(environment.mascot.anchor);
671
- } else if (definition.type === "Move" || definition.embedType === "Jump" || definition.embedType === "WalkWithIE") {
672
- if (targetX !== void 0) lookRight = targetX > environment.mascot.anchor.x;
673
- } else if (definition.embedType === "Fall" || definition.embedType === "FallWithIE") {
674
- if (initialVx !== void 0 && initialVx !== 0) lookRight = initialVx > 0;
675
- } else if (definition.embedType === "Look") {
676
- lookRight = definition.lookRight === void 0 ? !environment.mascot.lookRight : typeof definition.lookRight === "boolean" ? definition.lookRight : evaluateExpression(definition.lookRight, scopedEnvironment, environment.mascot.lookRight);
677
- }
678
- const duration = numeric(definition.duration, scopedEnvironment);
679
- const targetY = numeric(definition.targetY, scopedEnvironment);
680
- const velocity = numeric(definition.velocity, scopedEnvironment);
681
- const x = numeric(definition.x, scopedEnvironment);
682
- const y = numeric(definition.y, scopedEnvironment);
683
- const initialVy = numeric(definition.initialVy, scopedEnvironment);
684
- const resistanceX = numeric(definition.resistanceX, scopedEnvironment);
685
- const resistanceY = numeric(definition.resistanceY, scopedEnvironment);
686
- const gravity = numeric(definition.gravity, scopedEnvironment);
687
- const bornX = numeric(definition.bornX, scopedEnvironment);
688
- const bornY = numeric(definition.bornY, scopedEnvironment);
689
- return {
690
- definition,
691
- lookRight,
692
- ...duration !== void 0 && { duration },
693
- ...targetX !== void 0 && { targetX },
694
- ...targetY !== void 0 && { targetY },
695
- ...velocity !== void 0 && { velocity },
696
- ...x !== void 0 && { x },
697
- ...y !== void 0 && { y },
698
- ...initialVx !== void 0 && { initialVx },
699
- ...initialVy !== void 0 && { initialVy },
700
- ...resistanceX !== void 0 && { resistanceX },
701
- ...resistanceY !== void 0 && { resistanceY },
702
- ...gravity !== void 0 && { gravity },
703
- ...bornX !== void 0 && { bornX },
704
- ...bornY !== void 0 && { bornY }
705
- };
738
+ var ActionValues = class {
739
+ constructor(random) {
740
+ this.random = random;
741
+ }
742
+ random;
743
+ actionCache = /* @__PURE__ */ new Map();
744
+ frameCache = /* @__PURE__ */ new Map();
745
+ init() {
746
+ this.actionCache.clear();
747
+ this.frameCache.clear();
748
+ }
749
+ initFrame() {
750
+ this.frameCache.clear();
751
+ }
752
+ number(key, value, environment, fallback) {
753
+ if (value === void 0) return fallback;
754
+ if (typeof value === "number") return value;
755
+ const cache = expressionIsPerFrame(value) ? this.frameCache : this.actionCache;
756
+ const cached = cache.get(key);
757
+ if (typeof cached === "number") return cached;
758
+ const result = evaluateExpression(value, environment, fallback, this.random);
759
+ cache.set(key, result);
760
+ return result;
761
+ }
762
+ boolean(key, value, environment, fallback) {
763
+ if (value === void 0) return fallback;
764
+ if (typeof value === "boolean") return value;
765
+ const cache = expressionIsPerFrame(value) ? this.frameCache : this.actionCache;
766
+ const cached = cache.get(key);
767
+ if (typeof cached === "boolean") return cached;
768
+ const result = evaluateExpression(value, environment, fallback, this.random);
769
+ cache.set(key, result);
770
+ return result;
771
+ }
772
+ };
773
+ var RuntimeBase = class {
774
+ constructor(definition, random) {
775
+ this.definition = definition;
776
+ this.values = new ActionValues(random);
777
+ }
778
+ definition;
779
+ time = 0;
780
+ values;
781
+ init(context) {
782
+ this.time = 0;
783
+ this.values.init();
784
+ this.onInit(context);
785
+ }
786
+ hasNext(context) {
787
+ return this.baseHasNext(context) && this.hasMore(context);
788
+ }
789
+ step(context) {
790
+ this.values.initFrame();
791
+ const result = this.tick(context);
792
+ this.time += 1;
793
+ return result;
794
+ }
795
+ onInit(_context) {
796
+ }
797
+ hasMore(_context) {
798
+ return true;
799
+ }
800
+ baseHasNext(context) {
801
+ const condition = this.values.boolean("condition", this.definition.condition, context.environment, true);
802
+ const duration = Math.trunc(this.values.number("duration", this.definition.duration, context.environment, Number.POSITIVE_INFINITY));
803
+ return condition && this.time < duration;
804
+ }
805
+ };
806
+ var TrackedBorder = class {
807
+ constructor(side, source, context) {
808
+ this.side = side;
809
+ this.source = source;
810
+ this.previous = this.rectangle(context);
811
+ }
812
+ side;
813
+ source;
814
+ previous;
815
+ move(point, context) {
816
+ const current = this.rectangle(context);
817
+ const previous = this.previous;
818
+ this.previous = current;
819
+ if (!current || !previous) return point;
820
+ if (this.side === "left" || this.side === "right") {
821
+ if (previous.height === 0) return point;
822
+ const next2 = {
823
+ x: point.x + this.coordinate(current) - this.coordinate(previous),
824
+ y: Math.trunc((point.y - previous.y) * current.height / previous.height + current.y)
825
+ };
826
+ return Math.abs(next2.x - point.x) >= 80 || Math.abs(next2.y - point.y) >= 80 ? point : next2;
827
+ }
828
+ if (previous.width === 0) return point;
829
+ const next = {
830
+ // FloorCeiling.java performs integer division before applying the
831
+ // mascot's relative offset along a resized border.
832
+ x: (point.x - previous.x) * Math.trunc(current.width / previous.width) + current.x,
833
+ y: point.y + this.coordinate(current) - this.coordinate(previous)
834
+ };
835
+ return Math.abs(next.x - point.x) >= 80 || next.y - point.y > 20 || next.y - point.y < -80 ? point : next;
836
+ }
837
+ isOn(point, context) {
838
+ const rectangle = this.rectangle(context);
839
+ if (!rectangle) return false;
840
+ switch (this.side) {
841
+ case "top":
842
+ return isOnTop(point, rectangle);
843
+ case "bottom":
844
+ return isOnBottom(point, rectangle);
845
+ case "left":
846
+ return isOnLeft(point, rectangle);
847
+ case "right":
848
+ return isOnRight(point, rectangle);
849
+ }
850
+ }
851
+ coordinate(rectangle) {
852
+ switch (this.side) {
853
+ case "top":
854
+ return rectangle.y;
855
+ case "bottom":
856
+ return rectangle.y + rectangle.height;
857
+ case "left":
858
+ return rectangle.x;
859
+ case "right":
860
+ return rectangle.x + rectangle.width;
861
+ }
862
+ }
863
+ rectangle(context) {
864
+ if (this.source === "work-area") return context.bounds;
865
+ if (!this.source) return void 0;
866
+ return context.platforms.find((platform) => platform.element === this.source);
867
+ }
868
+ };
869
+ function selectBorder(type, state, context) {
870
+ if (type === "Floor") {
871
+ const platform2 = context.platforms.find((candidate) => isOnTop(state, candidate));
872
+ if (platform2) return new TrackedBorder("top", platform2.element, context);
873
+ if (isOnBottom(state, context.bounds)) return new TrackedBorder("bottom", "work-area", context);
874
+ return new TrackedBorder("bottom", void 0, context);
875
+ }
876
+ if (type === "Ceiling") {
877
+ const platform2 = context.platforms.find((candidate) => isOnBottom(state, candidate));
878
+ if (platform2) return new TrackedBorder("bottom", platform2.element, context);
879
+ if (isOnTop(state, context.bounds)) return new TrackedBorder("top", "work-area", context);
880
+ return new TrackedBorder("top", void 0, context);
881
+ }
882
+ if (state.lookRight) {
883
+ const platform2 = context.platforms.find((candidate) => isOnLeft(state, candidate));
884
+ if (platform2) return new TrackedBorder("left", platform2.element, context);
885
+ if (isOnRight(state, context.bounds)) return new TrackedBorder("right", "work-area", context);
886
+ return new TrackedBorder("right", void 0, context);
887
+ }
888
+ const platform = context.platforms.find((candidate) => isOnRight(state, candidate));
889
+ if (platform) return new TrackedBorder("right", platform.element, context);
890
+ if (isOnLeft(state, context.bounds)) return new TrackedBorder("left", "work-area", context);
891
+ return new TrackedBorder("left", void 0, context);
706
892
  }
707
- var SequenceRuntime = class {
708
- constructor(definitions, factory, loop) {
709
- this.definitions = definitions;
710
- this.factory = factory;
711
- this.loop = loop;
893
+ var AnimatedRuntime = class extends RuntimeBase {
894
+ constructor(definition, state, random) {
895
+ super(definition, random);
896
+ this.state = state;
712
897
  }
713
- definitions;
714
- factory;
715
- loop;
716
- index = 0;
717
- child;
718
- tick(deltaMs, environment, bounds) {
719
- for (let guard = 0; guard < 32; guard += 1) {
720
- const definition = this.definitions[this.index];
721
- if (!definition) {
722
- if (!this.loop || this.definitions.length === 0) return true;
723
- this.index = 0;
724
- continue;
725
- }
726
- this.child ??= this.factory(definition, environment);
727
- if (!this.child.tick(deltaMs, environment, bounds)) return false;
728
- this.child = void 0;
729
- this.index += 1;
730
- deltaMs = 0;
898
+ state;
899
+ border;
900
+ onInit(context) {
901
+ this.border = this.definition.borderType ? selectBorder(this.definition.borderType, this.state, context) : void 0;
902
+ }
903
+ animation(context, turn) {
904
+ const scoped = this.scopedEnvironment(context.environment);
905
+ return this.definition.animations?.find((animation, index) => (turn === void 0 || Boolean(animation.turn) === turn) && this.values.boolean(`animation-${index}`, animation.condition, scoped, true));
906
+ }
907
+ animationDuration(context, turn) {
908
+ return this.animation(context, turn)?.poses.reduce((sum, pose) => sum + Math.max(0, pose.duration), 0) ?? 0;
909
+ }
910
+ applyBorder(context) {
911
+ if (!this.border) return "running";
912
+ const moved = this.border.move(this.state, context);
913
+ this.state.x = moved.x;
914
+ this.state.y = moved.y;
915
+ return this.border.isOn(this.state, context) ? "running" : "lost-ground";
916
+ }
917
+ applyAnimation(context, turn) {
918
+ const animation = this.animation(context, turn);
919
+ const pose = animation && poseAt(animation, this.time);
920
+ if (pose) applyPose(this.state, pose);
921
+ }
922
+ scopedEnvironment(environment) {
923
+ const gap = this.values.number("gap", this.definition.gap, environment, 0);
924
+ const withGap = { ...environment, gap };
925
+ const targetX = this.definition.targetX === void 0 ? void 0 : this.values.number("targetX", this.definition.targetX, withGap, 0);
926
+ const targetY = this.definition.targetY === void 0 ? void 0 : this.values.number("targetY", this.definition.targetY, withGap, 0);
927
+ return { ...withGap, ...targetX !== void 0 && { targetX }, ...targetY !== void 0 && { targetY } };
928
+ }
929
+ };
930
+ function poseAt(animation, time) {
931
+ const duration = animation.poses.reduce((sum, pose) => sum + Math.max(0, pose.duration), 0);
932
+ if (duration <= 0) return void 0;
933
+ let cursor = time % duration;
934
+ for (const pose of animation.poses) {
935
+ cursor -= Math.max(0, pose.duration);
936
+ if (cursor < 0) return pose;
937
+ }
938
+ return animation.poses.at(-1);
939
+ }
940
+ function applyPose(state, pose) {
941
+ state.sprite = pose.sprite;
942
+ state.anchorX = pose.anchor.x;
943
+ state.anchorY = pose.anchor.y;
944
+ state.x += (state.lookRight ? -1 : 1) * pose.velocity.x;
945
+ state.y += pose.velocity.y;
946
+ }
947
+ var StayRuntime = class extends AnimatedRuntime {
948
+ tick(context) {
949
+ const border = this.applyBorder(context);
950
+ if (border === "lost-ground") return border;
951
+ this.applyAnimation(context);
952
+ return "running";
953
+ }
954
+ };
955
+ var AnimateRuntime = class extends StayRuntime {
956
+ hasMore(context) {
957
+ return this.time < this.animationDuration(context);
958
+ }
959
+ };
960
+ var MoveRuntime = class extends AnimatedRuntime {
961
+ turning = false;
962
+ hasTurningAnimation = false;
963
+ onInit(context) {
964
+ super.onInit(context);
965
+ this.turning = false;
966
+ this.hasTurningAnimation = this.definition.animations?.some((animation) => animation.turn) ?? false;
967
+ }
968
+ hasMore(context) {
969
+ const scoped = this.scopedEnvironment(context.environment);
970
+ const targetX = this.targetX(scoped);
971
+ const targetY = this.targetY(scoped);
972
+ const reached = targetX !== void 0 && this.state.x === targetX || targetY !== void 0 && this.state.y === targetY;
973
+ return !reached || this.turning;
974
+ }
975
+ tick(context) {
976
+ const border = this.applyBorder(context);
977
+ if (border === "lost-ground") return border;
978
+ const scoped = this.scopedEnvironment(context.environment);
979
+ const targetX = this.targetX(scoped);
980
+ const targetY = this.targetY(scoped);
981
+ let down = false;
982
+ if (targetX !== void 0 && this.state.x !== targetX) {
983
+ const nextLookRight = this.state.x < targetX;
984
+ this.turning = this.hasTurningAnimation && (this.turning || nextLookRight !== this.state.lookRight);
985
+ this.state.lookRight = nextLookRight;
731
986
  }
732
- return false;
987
+ if (targetY !== void 0) down = this.state.y < targetY;
988
+ if (this.turning && this.time >= this.animationDuration(context, true)) this.turning = false;
989
+ this.applyAnimation(context, this.turning);
990
+ if (targetX !== void 0 && (this.state.lookRight && this.state.x >= targetX || !this.state.lookRight && this.state.x <= targetX)) this.state.x = targetX;
991
+ if (targetY !== void 0 && (down && this.state.y >= targetY || !down && this.state.y <= targetY)) this.state.y = targetY;
992
+ return "running";
993
+ }
994
+ targetX(environment) {
995
+ return this.definition.targetX === void 0 ? void 0 : Math.trunc(this.values.number("targetX", this.definition.targetX, environment, 0));
996
+ }
997
+ targetY(environment) {
998
+ return this.definition.targetY === void 0 ? void 0 : Math.trunc(this.values.number("targetY", this.definition.targetY, environment, 0));
999
+ }
1000
+ };
1001
+ var MoveWithTurnRuntime = class extends MoveRuntime {
1002
+ onInit(context) {
1003
+ super.onInit(context);
1004
+ this.hasTurningAnimation = (this.definition.animations?.length ?? 0) >= 2;
1005
+ }
1006
+ animation(context, turn) {
1007
+ const animations = this.definition.animations ?? [];
1008
+ if (turn) return animations.at(-1);
1009
+ const scoped = this.scopedEnvironment(context.environment);
1010
+ return animations.slice(0, -1).find((candidate, index) => this.values.boolean(`animation-${index}`, candidate.condition, scoped, true));
733
1011
  }
734
1012
  };
735
- var CompleteRuntime = class {
1013
+ var TurnRuntime = class extends AnimatedRuntime {
1014
+ turning = false;
1015
+ hasMore(context) {
1016
+ const desired = this.values.boolean("lookRight", this.definition.lookRight, context.environment, !this.state.lookRight);
1017
+ this.turning ||= desired !== this.state.lookRight;
1018
+ return this.turning && this.time < this.animationDuration(context);
1019
+ }
1020
+ tick(context) {
1021
+ this.state.lookRight = this.values.boolean("lookRight", this.definition.lookRight, context.environment, !this.state.lookRight);
1022
+ const border = this.applyBorder(context);
1023
+ if (border === "lost-ground") return border;
1024
+ this.applyAnimation(context);
1025
+ return "running";
1026
+ }
1027
+ };
1028
+ var InstantRuntime = class extends RuntimeBase {
1029
+ constructor(definition, state, random, operation) {
1030
+ super(definition, random);
1031
+ this.state = state;
1032
+ this.operation = operation;
1033
+ }
1034
+ state;
1035
+ operation;
1036
+ onInit(context) {
1037
+ if (!this.baseHasNext(context)) return;
1038
+ if (this.operation === "look") {
1039
+ this.state.lookRight = this.values.boolean("lookRight", this.definition.lookRight, context.environment, !this.state.lookRight);
1040
+ } else if (this.operation === "offset") {
1041
+ this.state.x += Math.trunc(this.values.number("x", this.definition.x, context.environment, 0));
1042
+ this.state.y += Math.trunc(this.values.number("y", this.definition.y, context.environment, 0));
1043
+ }
1044
+ }
1045
+ hasMore() {
1046
+ return false;
1047
+ }
736
1048
  tick() {
737
- return true;
1049
+ return "running";
738
1050
  }
739
1051
  };
740
- var LeafRuntime = class {
741
- constructor(action, state, options, callbacks, environment) {
742
- this.action = action;
1052
+ var JumpRuntime = class extends RuntimeBase {
1053
+ constructor(definition, state, random) {
1054
+ super(definition, random);
743
1055
  this.state = state;
744
- this.options = options;
745
- this.callbacks = callbacks;
746
- const animationEnvironment = { ...environment, ...action.targetX !== void 0 && { targetX: action.targetX }, ...action.targetY !== void 0 && { targetY: action.targetY } };
747
- this.animation = action.definition.animations?.find((animation) => !animation.condition || evaluateExpression(animation.condition, animationEnvironment, false)) ?? action.definition.animations?.find((animation) => !animation.condition) ?? action.definition.animations?.[0];
748
- this.poseDuration = this.animation?.poses.reduce((sum, pose) => sum + Math.max(0, pose.duration), 0) ?? 0;
749
1056
  }
750
- action;
751
1057
  state;
752
- options;
753
- callbacks;
754
- elapsedMs = 0;
755
- started = false;
756
- spawned = false;
757
- animation;
758
- poseDuration;
759
- tick(deltaMs, environment, bounds) {
760
- const frameScale = deltaMs / this.options.frameDuration;
761
- if (!this.started) {
762
- this.started = true;
763
- this.state.lookRight = this.action.lookRight;
764
- this.initialize();
1058
+ hasMore(context) {
1059
+ return this.distance(context).distance !== 0;
1060
+ }
1061
+ tick(context) {
1062
+ const { targetX, targetY, distanceX, distanceY, distance } = this.distance(context);
1063
+ this.state.lookRight = this.state.x < targetX;
1064
+ const velocity = this.values.number("velocity", this.definition.velocity, context.environment, 20);
1065
+ if (distance !== 0) {
1066
+ this.state.vx = velocity * distanceX / distance;
1067
+ this.state.vy = velocity * distanceY / distance;
1068
+ this.state.x += Math.trunc(this.state.vx);
1069
+ this.state.y += Math.trunc(this.state.vy);
1070
+ const environment = { ...context.environment, targetX, targetY };
1071
+ const animation = this.definition.animations?.find((candidate, index) => this.values.boolean(`animation-${index}`, candidate.condition, environment, true));
1072
+ const pose = animation && poseAt(animation, this.time);
1073
+ if (pose) applyPose(this.state, pose);
765
1074
  }
766
- this.elapsedMs += deltaMs;
767
- const pose = this.currentPose();
768
- if (pose) this.applyPose(pose, frameScale, bounds);
769
- const { definition } = this.action;
770
- if (definition.type === "Embedded") return this.tickEmbedded(frameScale, environment, bounds);
771
- if (definition.type === "Move" && (this.action.targetX !== void 0 || this.action.targetY !== void 0)) {
772
- const reachedX = this.action.targetX === void 0 || Math.abs(this.state.x - this.action.targetX) < 0.5;
773
- const reachedY = this.action.targetY === void 0 || Math.abs(this.state.y - this.action.targetY) < 0.5;
774
- return reachedX && reachedY;
1075
+ if (distance <= velocity) {
1076
+ this.state.x = targetX;
1077
+ this.state.y = targetY;
775
1078
  }
776
- return this.elapsedMs >= this.durationMs();
1079
+ return "running";
777
1080
  }
778
- initialize() {
779
- const type = this.action.definition.embedType;
780
- if (type === "Fall" || type === "FallWithIE") {
781
- this.state.vx = this.action.initialVx ?? this.state.vx;
782
- this.state.vy = this.action.initialVy ?? this.state.vy;
783
- }
784
- if (type === "Offset") {
785
- this.state.x += this.action.x ?? 0;
786
- this.state.y += this.action.y ?? 0;
1081
+ distance(context) {
1082
+ const targetX = Math.trunc(this.values.number("targetX", this.definition.targetX, context.environment, 0));
1083
+ const targetY = Math.trunc(this.values.number("targetY", this.definition.targetY, context.environment, 0));
1084
+ const distanceX = targetX - this.state.x;
1085
+ const distanceY = targetY - this.state.y - Math.abs(distanceX) / 2;
1086
+ return { targetX, targetY, distanceX, distanceY, distance: Math.hypot(distanceX, distanceY) };
1087
+ }
1088
+ };
1089
+ var FallRuntime = class extends RuntimeBase {
1090
+ constructor(definition, state, random, defaultGravity) {
1091
+ super(definition, random);
1092
+ this.state = state;
1093
+ this.defaultGravity = defaultGravity;
1094
+ }
1095
+ state;
1096
+ defaultGravity;
1097
+ modX = 0;
1098
+ modY = 0;
1099
+ onInit(context) {
1100
+ this.modX = 0;
1101
+ this.modY = 0;
1102
+ this.state.vx = Math.trunc(this.values.number("initialVx", this.definition.initialVx, context.environment, 0));
1103
+ this.state.vy = Math.trunc(this.values.number("initialVy", this.definition.initialVy, context.environment, 0));
1104
+ }
1105
+ hasMore(context) {
1106
+ return !isOnFloor(this.state, context.bounds, context.platforms) && !isOnWall(this.state, context.bounds, this.state.lookRight, context.platforms);
1107
+ }
1108
+ tick(context) {
1109
+ if (this.state.vx !== 0) this.state.lookRight = this.state.vx > 0;
1110
+ const resistanceX = this.values.number("resistanceX", this.definition.resistanceX, context.environment, 0.05);
1111
+ const resistanceY = this.values.number("resistanceY", this.definition.resistanceY, context.environment, 0.1);
1112
+ const gravity = this.values.number("gravity", this.definition.gravity, context.environment, this.defaultGravity);
1113
+ this.state.vx -= this.state.vx * resistanceX;
1114
+ this.state.vy = this.state.vy - this.state.vy * resistanceY + gravity;
1115
+ this.modX += this.state.vx % 1;
1116
+ this.modY += this.state.vy % 1;
1117
+ const dx = Math.trunc(this.state.vx) + Math.trunc(this.modX);
1118
+ const dy = Math.trunc(this.state.vy) + Math.trunc(this.modY);
1119
+ this.modX %= 1;
1120
+ this.modY %= 1;
1121
+ const divisions = Math.max(1, Math.abs(dx), Math.abs(dy));
1122
+ const start = { x: this.state.x, y: this.state.y };
1123
+ let stopped = false;
1124
+ for (let index = 0; index <= divisions; index += 1) {
1125
+ const x = start.x + Math.trunc(dx * index / divisions);
1126
+ const y = start.y + Math.trunc(dy * index / divisions);
1127
+ this.state.x = x;
1128
+ this.state.y = y;
1129
+ if (dy > 0) {
1130
+ for (let offset = -80; offset <= 0; offset += 1) {
1131
+ this.state.y = y + offset;
1132
+ if (isOnFloor(this.state, context.bounds, context.platforms)) {
1133
+ stopped = true;
1134
+ break;
1135
+ }
1136
+ }
1137
+ if (stopped) break;
1138
+ this.state.y = y;
1139
+ }
1140
+ if (isOnWall(this.state, context.bounds, this.state.lookRight, context.platforms)) break;
787
1141
  }
788
- if (type === "Reboot") {
789
- this.state.vx = 0;
790
- this.state.vy = 0;
1142
+ const fallEnvironment = { ...context.environment, velocityX: this.state.vx, velocityY: this.state.vy };
1143
+ const animation = this.definition.animations?.find((candidate, index) => this.values.boolean(`animation-${index}`, candidate.condition, fallEnvironment, true));
1144
+ const pose = animation && poseAt(animation, this.time);
1145
+ if (pose) applyPose(this.state, pose);
1146
+ return "running";
1147
+ }
1148
+ };
1149
+ function matchingActivePlatform(context) {
1150
+ const active = context.environment.mascot.environment.activeIE;
1151
+ if (!active.visible) return void 0;
1152
+ return context.platforms.find((platform) => Math.abs(platform.x - active.x) < 1e-3 && Math.abs(platform.y - active.y) < 1e-3 && Math.abs(platform.width - active.width) < 1e-3 && Math.abs(platform.height - active.height) < 1e-3);
1153
+ }
1154
+ function carryRelation(state, platform, offsetX, offsetY) {
1155
+ const grip = {
1156
+ x: state.x + (state.lookRight ? -offsetX : offsetX),
1157
+ y: state.y + offsetY
1158
+ };
1159
+ return isOnBottom(grip, platform) && (state.lookRight ? isOnLeft(grip, platform) : isOnRight(grip, platform));
1160
+ }
1161
+ function carriedPlatformPosition(state, platform, offsetX, offsetY) {
1162
+ return state.lookRight ? { x: state.x - offsetX, y: state.y + offsetY - platform.height } : { x: state.x + offsetX - platform.width, y: state.y + offsetY - platform.height };
1163
+ }
1164
+ var CarryFallRuntime = class extends FallRuntime {
1165
+ constructor(definition, state, random, gravity, callbacks) {
1166
+ super(definition, state, random, gravity);
1167
+ this.callbacks = callbacks;
1168
+ }
1169
+ callbacks;
1170
+ element;
1171
+ onInit(context) {
1172
+ super.onInit(context);
1173
+ this.element = matchingActivePlatform(context)?.element;
1174
+ }
1175
+ tick(context) {
1176
+ const platform = context.platforms.find((candidate) => candidate.element === this.element);
1177
+ const offsetX = Math.trunc(this.values.number("ieOffsetX", this.definition.ieOffsetX, context.environment, 0));
1178
+ const offsetY = Math.trunc(this.values.number("ieOffsetY", this.definition.ieOffsetY, context.environment, 0));
1179
+ if (!platform || !carryRelation(this.state, platform, offsetX, offsetY)) return "lost-ground";
1180
+ const result = super.tick(context);
1181
+ this.callbacks.movePlatform?.(platform.element, carriedPlatformPosition(this.state, platform, offsetX, offsetY));
1182
+ return result;
1183
+ }
1184
+ };
1185
+ var CarryMoveRuntime = class extends MoveRuntime {
1186
+ constructor(definition, state, random, callbacks) {
1187
+ super(definition, state, random);
1188
+ this.callbacks = callbacks;
1189
+ }
1190
+ callbacks;
1191
+ element;
1192
+ onInit(context) {
1193
+ super.onInit(context);
1194
+ this.element = matchingActivePlatform(context)?.element;
1195
+ }
1196
+ tick(context) {
1197
+ const platform = context.platforms.find((candidate) => candidate.element === this.element);
1198
+ const offsetX = Math.trunc(this.values.number("ieOffsetX", this.definition.ieOffsetX, context.environment, 0));
1199
+ const offsetY = Math.trunc(this.values.number("ieOffsetY", this.definition.ieOffsetY, context.environment, 0));
1200
+ if (!platform || !carryRelation(this.state, platform, offsetX, offsetY)) return "lost-ground";
1201
+ const result = super.tick(context);
1202
+ this.callbacks.movePlatform?.(platform.element, carriedPlatformPosition(this.state, platform, offsetX, offsetY));
1203
+ return result;
1204
+ }
1205
+ };
1206
+ var ThrowPlatformRuntime = class extends AnimateRuntime {
1207
+ constructor(definition, state, random, callbacks) {
1208
+ super(definition, state, random);
1209
+ this.callbacks = callbacks;
1210
+ }
1211
+ callbacks;
1212
+ element;
1213
+ onInit(context) {
1214
+ super.onInit(context);
1215
+ this.element = matchingActivePlatform(context)?.element;
1216
+ }
1217
+ tick(context) {
1218
+ const result = super.tick(context);
1219
+ const platform = context.platforms.find((candidate) => candidate.element === this.element);
1220
+ if (platform) {
1221
+ const vx = Math.trunc(this.values.number("initialVx", this.definition.initialVx, context.environment, 32));
1222
+ const vy = Math.trunc(this.values.number("initialVy", this.definition.initialVy, context.environment, -10));
1223
+ const gravity = this.values.number("gravity", this.definition.gravity, context.environment, 0.5);
1224
+ this.callbacks.movePlatform?.(platform.element, {
1225
+ x: platform.x + (this.state.lookRight ? vx : -vx),
1226
+ y: platform.y + vy + Math.trunc(this.time * gravity)
1227
+ });
791
1228
  }
1229
+ return result;
792
1230
  }
793
- tickEmbedded(frameScale, environment, bounds) {
794
- switch (this.action.definition.embedType) {
795
- case "Fall":
796
- case "FallWithIE":
797
- case "Thrown":
798
- return applyGravity(
799
- this.state,
800
- bounds,
801
- frameScale,
802
- this.action.gravity ?? this.options.gravity,
803
- this.action.resistanceX,
804
- this.action.resistanceY,
805
- environment.mascot.environment.activeIE.visible ? environment.mascot.environment.activeIE : void 0
806
- );
807
- case "Jump": {
808
- const target = { x: this.action.targetX ?? this.state.x, y: this.action.targetY ?? this.state.y };
809
- return moveToward(this.state, target, this.action.velocity ?? 20, frameScale);
810
- }
811
- case "Breed":
812
- if (!this.spawned && this.elapsedMs >= this.durationMs()) {
813
- this.spawned = true;
814
- const child = { x: this.state.x + (this.action.bornX ?? 0), y: this.state.y + (this.action.bornY ?? 0) };
815
- this.callbacks.spawn(this.action.definition.bornBehavior ? { ...child, behaviorName: this.action.definition.bornBehavior } : child);
816
- return true;
817
- }
818
- return false;
819
- case "Exit":
820
- this.callbacks.remove();
821
- return true;
822
- case "Reboot":
823
- this.state.x = bounds.x + 128 + Math.max(0, bounds.width - 256) * Math.random();
824
- this.state.y = bounds.y + 128 + Math.max(0, bounds.height - 256) * Math.random();
825
- return true;
826
- case "Offset":
827
- case "Look":
828
- return true;
829
- case "Dragged":
830
- return false;
831
- default:
832
- return this.elapsedMs >= this.durationMs();
1231
+ };
1232
+ function breed(definition, state, values, context, callbacks) {
1233
+ const bornX = Math.trunc(values.number("bornX", definition.bornX, context.environment, 0));
1234
+ const bornY = Math.trunc(values.number("bornY", definition.bornY, context.environment, 0));
1235
+ const count = Math.trunc(values.number("bornCount", definition.bornCount, context.environment, 1));
1236
+ if (count < 1) throw new RangeError("BornCount must be positive");
1237
+ for (let index = 0; index < count; index += 1) {
1238
+ callbacks.spawn({
1239
+ x: state.x + (state.lookRight ? -bornX : bornX),
1240
+ y: state.y + bornY,
1241
+ lookRight: state.lookRight,
1242
+ ...definition.bornBehavior && { behaviorName: definition.bornBehavior }
1243
+ }, definition.bornMascot);
1244
+ }
1245
+ }
1246
+ var BreedRuntime = class extends AnimateRuntime {
1247
+ constructor(definition, state, random, callbacks) {
1248
+ super(definition, state, random);
1249
+ this.callbacks = callbacks;
1250
+ }
1251
+ callbacks;
1252
+ spawned = false;
1253
+ tick(context) {
1254
+ const result = super.tick(context);
1255
+ if (result === "lost-ground") return result;
1256
+ const duration = this.animationDuration(context);
1257
+ if (!this.spawned && this.time === duration - 1) {
1258
+ this.spawned = true;
1259
+ breed(this.definition, this.state, this.values, context, this.callbacks);
833
1260
  }
1261
+ return result;
1262
+ }
1263
+ };
1264
+ var BreedMoveRuntime = class extends MoveRuntime {
1265
+ constructor(definition, state, random, callbacks) {
1266
+ super(definition, state, random);
1267
+ this.callbacks = callbacks;
1268
+ }
1269
+ callbacks;
1270
+ tick(context) {
1271
+ const result = super.tick(context);
1272
+ if (result === "lost-ground") return result;
1273
+ const interval = Math.trunc(this.values.number("bornInterval", this.definition.bornInterval, context.environment, 1));
1274
+ if (interval < 1) throw new RangeError("BornInterval must be positive");
1275
+ if (this.time % interval === 0 && !this.turning) breed(this.definition, this.state, this.values, context, this.callbacks);
1276
+ return result;
1277
+ }
1278
+ };
1279
+ var BreedJumpRuntime = class extends JumpRuntime {
1280
+ constructor(definition, breedState, random, callbacks) {
1281
+ super(definition, breedState, random);
1282
+ this.breedState = breedState;
1283
+ this.callbacks = callbacks;
834
1284
  }
835
- currentPose() {
836
- if (!this.animation?.poses.length || this.poseDuration <= 0) return void 0;
837
- let cursor = this.elapsedMs / this.options.frameDuration % this.poseDuration;
838
- for (const pose of this.animation.poses) {
839
- cursor -= pose.duration;
840
- if (cursor < 0) return pose;
1285
+ breedState;
1286
+ callbacks;
1287
+ tick(context) {
1288
+ const result = super.tick(context);
1289
+ const interval = Math.trunc(this.values.number("bornInterval", this.definition.bornInterval, context.environment, 1));
1290
+ if (interval < 1) throw new RangeError("BornInterval must be positive");
1291
+ if (this.time % interval === 0) breed(this.definition, this.breedState, this.values, context, this.callbacks);
1292
+ return result;
1293
+ }
1294
+ };
1295
+ var DraggedRuntime = class extends RuntimeBase {
1296
+ constructor(definition, state, random, spec) {
1297
+ super(definition, random);
1298
+ this.state = state;
1299
+ this.spec = spec;
1300
+ }
1301
+ state;
1302
+ spec;
1303
+ footX = 0;
1304
+ footDx = 0;
1305
+ timeToRegist = 250;
1306
+ onInit(context) {
1307
+ this.footDx = 0;
1308
+ this.timeToRegist = 250;
1309
+ this.footX = context.environment.mascot.environment.cursor.x + this.offsetX(context);
1310
+ }
1311
+ hasMore() {
1312
+ return this.time < this.timeToRegist;
1313
+ }
1314
+ tick(context) {
1315
+ this.state.lookRight = false;
1316
+ this.state.dragging = true;
1317
+ const cursor = context.environment.mascot.environment.cursor;
1318
+ const offsetX = this.offsetX(context);
1319
+ const offsetY = this.offsetY(context);
1320
+ if (Math.abs(cursor.x - this.state.x + offsetX) >= 5) this.time = 0;
1321
+ this.footDx = (this.footDx + (cursor.x - this.footX) * 0.1) * 0.8;
1322
+ this.footX += this.footDx;
1323
+ const environment = { ...context.environment, footX: this.footX };
1324
+ const animation = this.definition.animations?.find((candidate, index) => this.values.boolean(`animation-${index}`, candidate.condition, environment, true));
1325
+ const pose = animation && poseAt(animation, this.time);
1326
+ if (pose) applyPose(this.state, pose);
1327
+ this.state.x = cursor.x + offsetX;
1328
+ this.state.y = cursor.y + offsetY;
1329
+ if (this.time === this.timeToRegist - 1 && this.values.number(`regist-${this.time}`, "#{Math.random()}", environment, 0) >= 0.1) this.timeToRegist += 1;
1330
+ return "running";
1331
+ }
1332
+ offsetX(context) {
1333
+ const offset = Math.trunc(this.values.number("offsetX", this.definition.offsetX, context.environment, 0));
1334
+ return this.definition.offsetType === "Origin" ? -offset + this.spriteCenter().x : offset;
1335
+ }
1336
+ offsetY(context) {
1337
+ const offset = Math.trunc(this.values.number("offsetY", this.definition.offsetY, context.environment, 120));
1338
+ return this.definition.offsetType === "Origin" ? -offset + this.spriteCenter().y : offset;
1339
+ }
1340
+ spriteCenter() {
1341
+ const sprite = this.spec.sprites[this.state.sprite];
1342
+ const width = typeof sprite === "object" ? sprite.width ?? 128 : 128;
1343
+ return { x: this.state.lookRight ? width - this.state.anchorX : this.state.anchorX, y: this.state.anchorY };
1344
+ }
1345
+ };
1346
+ var RegistRuntime = class extends AnimatedRuntime {
1347
+ constructor(definition, state, random, spec) {
1348
+ super(definition, state, random);
1349
+ this.spec = spec;
1350
+ }
1351
+ spec;
1352
+ hasMore(context) {
1353
+ const cursor = context.environment.mascot.environment.cursor;
1354
+ const rawOffset = Math.trunc(this.values.number("offsetX", this.definition.offsetX, context.environment, 0));
1355
+ const sprite = this.spec.sprites[this.state.sprite];
1356
+ const width = typeof sprite === "object" ? sprite.width ?? 128 : 128;
1357
+ const centerX = this.state.lookRight ? width - this.state.anchorX : this.state.anchorX;
1358
+ const offsetX = this.definition.offsetType === "Origin" ? -rawOffset + centerX : rawOffset;
1359
+ return Math.abs(cursor.x - this.state.x + offsetX) < 5;
1360
+ }
1361
+ tick(context) {
1362
+ this.state.dragging = true;
1363
+ this.applyAnimation(context);
1364
+ if (this.time + 1 >= this.animationDuration(context)) {
1365
+ this.state.lookRight = this.values.number(`look-${this.time}`, "#{Math.random()}", context.environment, 0) < 0.5;
1366
+ return "lost-ground";
841
1367
  }
842
- return this.animation.poses.at(-1);
1368
+ return "running";
843
1369
  }
844
- applyPose(pose, frameScale, bounds) {
845
- this.state.sprite = pose.sprite;
846
- this.state.anchorX = pose.anchor.x;
847
- this.state.anchorY = pose.anchor.y;
848
- const direction = this.state.lookRight ? -1 : 1;
849
- this.state.x = clamp(this.state.x + pose.velocity.x * direction * frameScale, bounds.x, bounds.x + bounds.width);
850
- this.state.y = clamp(this.state.y + pose.velocity.y * frameScale, bounds.y, bounds.y + bounds.height);
1370
+ };
1371
+ var SelfDestructRuntime = class extends AnimateRuntime {
1372
+ constructor(definition, state, random, callbacks) {
1373
+ super(definition, state, random);
1374
+ this.callbacks = callbacks;
1375
+ }
1376
+ callbacks;
1377
+ tick(context) {
1378
+ const result = super.tick(context);
1379
+ if (this.time === this.animationDuration(context) - 1) this.callbacks.remove();
1380
+ return result;
1381
+ }
1382
+ };
1383
+ var ComplexRuntime = class extends RuntimeBase {
1384
+ constructor(definition, random, factory, selectOnly) {
1385
+ super(definition, random);
1386
+ this.factory = factory;
1387
+ this.selectOnly = selectOnly;
851
1388
  }
852
- durationMs() {
853
- const frames = this.action.duration ?? this.poseDuration;
854
- return Math.max(this.options.frameDuration, frames * this.options.frameDuration);
1389
+ factory;
1390
+ selectOnly;
1391
+ index = 0;
1392
+ child;
1393
+ selectionMade = false;
1394
+ onInit(context) {
1395
+ this.index = 0;
1396
+ this.child = void 0;
1397
+ this.selectionMade = false;
1398
+ if (this.baseHasNext(context)) this.seek(context);
1399
+ }
1400
+ hasMore(context) {
1401
+ if (!this.selectOnly) this.seek(context);
1402
+ return this.child?.hasNext(context) ?? false;
1403
+ }
1404
+ tick(context) {
1405
+ return this.child?.hasNext(context) ? this.child.step(context) : "running";
1406
+ }
1407
+ seek(context) {
1408
+ const definitions = this.definition.actions ?? [];
1409
+ if (definitions.length === 0) return;
1410
+ for (let guard = 0; guard <= definitions.length; guard += 1) {
1411
+ if (this.child?.hasNext(context)) {
1412
+ this.selectionMade = true;
1413
+ return;
1414
+ }
1415
+ if (this.selectOnly && this.selectionMade) {
1416
+ this.child = void 0;
1417
+ return;
1418
+ }
1419
+ if (this.index >= definitions.length) {
1420
+ if (this.definition.loop !== true) {
1421
+ this.child = void 0;
1422
+ return;
1423
+ }
1424
+ this.index = 0;
1425
+ }
1426
+ const definition = definitions[this.index++];
1427
+ if (!definition) {
1428
+ this.child = void 0;
1429
+ return;
1430
+ }
1431
+ this.child = this.factory(definition, context);
1432
+ this.child.init(context);
1433
+ }
1434
+ this.child = void 0;
855
1435
  }
856
1436
  };
857
1437
  var ActionExecutor = class {
858
- /** Creates an executor bound to one mascot's mutable internal state. */
859
1438
  constructor(spec, state, options, callbacks) {
860
1439
  this.spec = spec;
861
1440
  this.state = state;
862
1441
  this.options = options;
863
1442
  this.callbacks = callbacks;
1443
+ this.random = options.random ?? Math.random;
864
1444
  }
865
1445
  spec;
866
1446
  state;
867
1447
  options;
868
1448
  callbacks;
869
1449
  runtime;
1450
+ accumulator = 0;
1451
+ random;
870
1452
  /** Starts the action whose name matches a selected behavior. */
871
- start(actionName, environment) {
1453
+ start(actionName, environment, _preserveLookRight = false, bounds = environment.mascot.environment.workArea, platforms = []) {
872
1454
  const definition = this.spec.actions.find((action) => action.name === actionName);
873
- this.runtime = definition ? this.createRuntime(definition, environment, /* @__PURE__ */ new Set()) : void 0;
874
- return this.runtime !== void 0;
1455
+ this.accumulator = 0;
1456
+ if (!definition) {
1457
+ this.runtime = void 0;
1458
+ return false;
1459
+ }
1460
+ const context = { environment, bounds, platforms };
1461
+ this.runtime = this.createRuntime(definition, context, /* @__PURE__ */ new Set());
1462
+ this.runtime.init(context);
1463
+ return true;
875
1464
  }
876
- /** Advances the current action and returns true when it has completed. */
877
- tick(deltaMs, environment, bounds) {
878
- return this.runtime?.tick(deltaMs, environment, bounds) ?? true;
1465
+ /** Advances by elapsed milliseconds and reports completion. */
1466
+ tick(deltaMs, environment, bounds, platforms = []) {
1467
+ this.accumulator += Math.max(0, deltaMs);
1468
+ let result = this.runtime?.hasNext({ environment, bounds, platforms }) ? "running" : "complete";
1469
+ while (this.accumulator >= this.options.frameDuration && result === "running") {
1470
+ this.accumulator -= this.options.frameDuration;
1471
+ result = this.step(environment, bounds, platforms);
1472
+ }
1473
+ return result !== "running";
1474
+ }
1475
+ /** Advances exactly one legacy frame. */
1476
+ step(environment, bounds, platforms = []) {
1477
+ const context = { environment, bounds, platforms };
1478
+ if (!this.runtime?.hasNext(context)) return "complete";
1479
+ const result = this.runtime.step(context);
1480
+ if (result === "lost-ground") return result;
1481
+ return this.runtime.hasNext(context) ? "running" : "complete";
1482
+ }
1483
+ /** Returns whether the current action can execute another legacy frame. */
1484
+ hasNext(environment, bounds, platforms = []) {
1485
+ return this.runtime?.hasNext({ environment, bounds, platforms }) ?? false;
1486
+ }
1487
+ /** Retained for source compatibility with the previous collision API. */
1488
+ consumeViewportWallCollision() {
1489
+ return false;
879
1490
  }
880
1491
  /** Cancels the current action tree. */
881
1492
  cancel() {
882
1493
  this.runtime = void 0;
1494
+ this.accumulator = 0;
883
1495
  }
884
- createRuntime(definition, environment, references) {
885
- if (definition.condition && !evaluateExpression(definition.condition, environment, false)) return new CompleteRuntime();
886
- const bounds = environment.mascot.environment.workArea;
887
- const activeIE = environment.mascot.environment.activeIE;
888
- if (!isOnBorder(this.state, bounds, definition.borderType, activeIE.visible ? activeIE : void 0)) return new CompleteRuntime();
1496
+ createRuntime(definition, context, references) {
889
1497
  if (definition.type === "Reference") {
890
- if (!definition.name || references.has(definition.name)) return new CompleteRuntime();
1498
+ if (!definition.name || references.has(definition.name)) return new InstantRuntime(definition, this.state, this.random, "noop");
891
1499
  const referenced = this.spec.actions.find((action) => action.name === definition.name);
892
- if (!referenced) return new CompleteRuntime();
893
- const nextReferences = new Set(references).add(definition.name);
894
- return this.createRuntime({ ...referenced, ...definition, type: referenced.type }, environment, nextReferences);
1500
+ if (!referenced) return new InstantRuntime(definition, this.state, this.random, "noop");
1501
+ return this.createRuntime({ ...referenced, ...definition, type: referenced.type, name: definition.name }, context, new Set(references).add(definition.name));
895
1502
  }
896
- if (definition.type === "Sequence") {
897
- return new SequenceRuntime(definition.actions ?? [], (child, nextEnvironment) => this.createRuntime(child, nextEnvironment, new Set(references)), definition.loop === true);
1503
+ if (definition.type === "Sequence" || definition.type === "Select") {
1504
+ return new ComplexRuntime(definition, this.random, (child, nextContext) => this.createRuntime(child, nextContext, new Set(references)), definition.type === "Select");
898
1505
  }
899
- if (definition.type === "Select") {
900
- const child = definition.actions?.find((candidate) => !candidate.condition || evaluateExpression(candidate.condition, environment, false));
901
- return child ? this.createRuntime(child, environment, new Set(references)) : new CompleteRuntime();
1506
+ if (definition.type === "Stay") return new StayRuntime(definition, this.state, this.random);
1507
+ if (definition.type === "Animate") return new AnimateRuntime(definition, this.state, this.random);
1508
+ if (definition.type === "Move") return new MoveRuntime(definition, this.state, this.random);
1509
+ switch (definition.embedType) {
1510
+ case "Fall":
1511
+ return new FallRuntime(definition, this.state, this.random, this.options.gravity);
1512
+ case "FallWithIE":
1513
+ return new CarryFallRuntime(definition, this.state, this.random, this.options.gravity, this.callbacks);
1514
+ case "Jump":
1515
+ case "ComplexJump":
1516
+ case "ScanJump":
1517
+ case "BroadcastJump":
1518
+ return new JumpRuntime(definition, this.state, this.random);
1519
+ case "WalkWithIE":
1520
+ return new CarryMoveRuntime(definition, this.state, this.random, this.callbacks);
1521
+ case "MoveWithTurn":
1522
+ return new MoveWithTurnRuntime(definition, this.state, this.random);
1523
+ case "ComplexMove":
1524
+ case "ScanMove":
1525
+ case "BroadcastMove":
1526
+ return new MoveRuntime(definition, this.state, this.random);
1527
+ case "Turn":
1528
+ return new TurnRuntime(definition, this.state, this.random);
1529
+ case "Look":
1530
+ return new InstantRuntime(definition, this.state, this.random, "look");
1531
+ case "Offset":
1532
+ return new InstantRuntime(definition, this.state, this.random, "offset");
1533
+ case "Mute":
1534
+ case "Reboot":
1535
+ return new InstantRuntime(definition, this.state, this.random, "noop");
1536
+ case "Breed":
1537
+ return new BreedRuntime(definition, this.state, this.random, this.callbacks);
1538
+ case "BreedMove":
1539
+ return new BreedMoveRuntime(definition, this.state, this.random, this.callbacks);
1540
+ case "BreedJump":
1541
+ return new BreedJumpRuntime(definition, this.state, this.random, this.callbacks);
1542
+ case "ThrowIE":
1543
+ return new ThrowPlatformRuntime(definition, this.state, this.random, this.callbacks);
1544
+ case "SelfDestruct":
1545
+ case "Exit":
1546
+ return new SelfDestructRuntime(definition, this.state, this.random, this.callbacks);
1547
+ case "Dragged":
1548
+ return new DraggedRuntime(definition, this.state, this.random, this.spec);
1549
+ case "Regist":
1550
+ return new RegistRuntime(definition, this.state, this.random, this.spec);
1551
+ case "Broadcast":
1552
+ case "Interact":
1553
+ case "ScanInteract":
1554
+ case "Transform":
1555
+ return new AnimateRuntime(definition, this.state, this.random);
1556
+ case "BroadcastStay":
1557
+ return new StayRuntime(definition, this.state, this.random);
1558
+ default:
1559
+ return new StayRuntime(definition, this.state, this.random);
902
1560
  }
903
- return new LeafRuntime(evaluateAction(definition, environment), this.state, this.options, this.callbacks, environment);
904
1561
  }
905
1562
  };
906
1563
 
@@ -929,7 +1586,6 @@ function environmentRectangle(bounds) {
929
1586
  };
930
1587
  }
931
1588
  var PLATFORM_NEARBY_DISTANCE = 400;
932
- var PLATFORM_EDGE_TOLERANCE = 16;
933
1589
  function distanceToRectangle(point, rectangle) {
934
1590
  const dx = Math.max(rectangle.x - point.x, 0, point.x - rectangle.x - rectangle.width);
935
1591
  const dy = Math.max(rectangle.y - point.y, 0, point.y - rectangle.y - rectangle.height);
@@ -958,10 +1614,13 @@ var Mascot = class {
958
1614
  dragging: false
959
1615
  };
960
1616
  this.domHandle = dom.createMascot(spec, id, options.mascotClassName || void 0);
1617
+ this.frameDuration = options.frameDuration;
1618
+ this.random = options.random ?? Math.random;
961
1619
  this.behavior = new BehaviorController(spec, options.random);
962
1620
  this.actions = new ActionExecutor(spec, this.state, options, {
963
- spawn: (position) => this.callbacks.spawn(this.spec.id, position),
964
- remove: () => this.callbacks.remove(this.id)
1621
+ spawn: (position, characterId) => this.callbacks.spawn(characterId ?? this.spec.id, position),
1622
+ remove: () => this.callbacks.remove(this.id),
1623
+ ...this.callbacks.movePlatform && { movePlatform: this.callbacks.movePlatform }
965
1624
  });
966
1625
  this.installPointerHandlers();
967
1626
  }
@@ -983,44 +1642,19 @@ var Mascot = class {
983
1642
  lastPointer;
984
1643
  activePlatformElement;
985
1644
  platforms = [];
1645
+ accumulatedMs = 0;
1646
+ frameDuration;
1647
+ random;
986
1648
  /** Advances behavior, animation, physics, and rendering by one clock tick. */
987
1649
  tick(deltaMs, bounds, platforms = []) {
988
1650
  if (this.destroyed) return;
989
1651
  this.platforms = platforms;
990
- const environment = this.createEnvironment(bounds, platforms);
991
- if (this.state.dragging) {
992
- this.dom.render(this.domHandle, this.spec, this.state);
993
- return;
994
- }
995
1652
  try {
996
- for (let guard = 0; guard < 8; guard += 1) {
997
- if (!this.currentBehavior) {
998
- this.currentBehavior = this.behavior.selectInitial(environment, this.state.behaviorName);
999
- let started = this.startBehavior(environment);
1000
- if (!started) {
1001
- this.currentBehavior = this.findFallBehavior();
1002
- started = this.startBehavior(environment);
1003
- }
1004
- if (!started) break;
1005
- }
1006
- const activeIE = environment.mascot.environment.activeIE;
1007
- const wasOnPlatformTop = activeIE.visible && activeIE.topBorder.isOn(this.state);
1008
- const completed = this.actions.tick(deltaMs, environment, bounds);
1009
- const remainedNearPlatform = this.state.x >= activeIE.left - PLATFORM_EDGE_TOLERANCE && this.state.x <= activeIE.right + PLATFORM_EDGE_TOLERANCE;
1010
- if (wasOnPlatformTop && !remainedNearPlatform && !platforms.some((platform) => isOnTop(this.state, platform))) {
1011
- this.actions.cancel();
1012
- this.currentBehavior = this.findFallBehavior();
1013
- if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms));
1014
- break;
1015
- }
1016
- if (!completed) break;
1017
- if (this.destroyed) return;
1018
- this.currentBehavior = this.behavior.selectNext(this.createEnvironment(bounds, platforms));
1019
- if (!this.currentBehavior || !this.startBehavior(this.createEnvironment(bounds, platforms))) {
1020
- this.currentBehavior = this.findFallBehavior();
1021
- if (!this.currentBehavior || !this.startBehavior(this.createEnvironment(bounds, platforms))) break;
1022
- }
1023
- deltaMs = 0;
1653
+ this.ensureBehavior(bounds, platforms, true);
1654
+ this.accumulatedMs += Math.max(0, deltaMs);
1655
+ while (this.accumulatedMs >= this.frameDuration && !this.destroyed) {
1656
+ this.accumulatedMs -= this.frameDuration;
1657
+ this.legacyTick(bounds, platforms);
1024
1658
  }
1025
1659
  } catch (error) {
1026
1660
  this.callbacks.error(error instanceof Error ? error : new Error(String(error)));
@@ -1041,14 +1675,68 @@ var Mascot = class {
1041
1675
  for (const dispose of this.disposers.splice(0)) dispose();
1042
1676
  this.dom.removeMascot(this.domHandle);
1043
1677
  }
1044
- startBehavior(environment) {
1678
+ startBehavior(environment, bounds, platforms) {
1045
1679
  if (!this.currentBehavior) return false;
1046
1680
  this.state.behaviorName = this.currentBehavior.name;
1047
- return this.actions.start(this.currentBehavior.name, environment);
1681
+ return this.actions.start(this.currentBehavior.actionName ?? this.currentBehavior.name, environment, false, bounds, platforms);
1682
+ }
1683
+ ensureBehavior(bounds, platforms, initial) {
1684
+ for (let guard = 0; guard < 32 && !this.destroyed; guard += 1) {
1685
+ const environment = this.createEnvironment(bounds, platforms);
1686
+ if (!this.currentBehavior) {
1687
+ this.currentBehavior = initial ? this.behavior.selectInitial(environment, this.state.behaviorName) : this.selectNextBehavior(environment, bounds);
1688
+ initial = false;
1689
+ if (!this.currentBehavior) this.currentBehavior = this.findFallBehavior();
1690
+ if (!this.currentBehavior || !this.startBehavior(environment, bounds, platforms)) return;
1691
+ }
1692
+ if (this.actions.hasNext(environment, bounds, platforms)) return;
1693
+ this.currentBehavior = this.selectNextBehavior(environment, bounds) ?? this.findFallBehavior();
1694
+ if (!this.currentBehavior) return;
1695
+ if (!this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms)) return;
1696
+ }
1697
+ }
1698
+ legacyTick(bounds, platforms) {
1699
+ this.ensureBehavior(bounds, platforms, false);
1700
+ if (!this.currentBehavior) return;
1701
+ const result = this.actions.step(this.createEnvironment(bounds, platforms), bounds, platforms);
1702
+ if (this.destroyed) return;
1703
+ if (result === "lost-ground") {
1704
+ this.state.dragging = false;
1705
+ this.actions.cancel();
1706
+ this.currentBehavior = this.findFallBehavior();
1707
+ if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms);
1708
+ } else if (result === "complete") {
1709
+ this.currentBehavior = this.selectNextBehavior(this.createEnvironment(bounds, platforms), bounds);
1710
+ if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms);
1711
+ this.ensureBehavior(bounds, platforms, false);
1712
+ } else if (this.isOutsideVisibleBounds(bounds)) {
1713
+ this.state.x = Math.trunc(bounds.x + this.random() * bounds.width);
1714
+ this.state.y = bounds.y - 256;
1715
+ this.actions.cancel();
1716
+ this.currentBehavior = this.findFallBehavior();
1717
+ if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms);
1718
+ }
1719
+ }
1720
+ isOutsideVisibleBounds(bounds) {
1721
+ const sprite = this.spec.sprites[this.state.sprite];
1722
+ const width = typeof sprite === "object" && "width" in sprite && sprite.width !== void 0 ? sprite.width : 128;
1723
+ const height = typeof sprite === "object" && "height" in sprite && sprite.height !== void 0 ? sprite.height : 128;
1724
+ const anchorX = this.state.lookRight ? width - this.state.anchorX : this.state.anchorX;
1725
+ const left = this.state.x - anchorX;
1726
+ const top = this.state.y - this.state.anchorY;
1727
+ return left + width <= bounds.x || bounds.x + bounds.width <= left || bounds.y + bounds.height <= top;
1048
1728
  }
1049
1729
  findFallBehavior() {
1050
1730
  return this.behavior.force("Fall") ?? this.behavior.force("\u843D\u4E0B\u3059\u308B");
1051
1731
  }
1732
+ selectNextBehavior(environment, bounds) {
1733
+ const selected = this.behavior.selectNext(environment);
1734
+ if (this.behavior.usedFallback()) {
1735
+ this.state.x = Math.trunc(bounds.x + this.random() * bounds.width);
1736
+ this.state.y = bounds.y - 256;
1737
+ }
1738
+ return selected;
1739
+ }
1052
1740
  createEnvironment(bounds, platforms = this.platforms) {
1053
1741
  const workArea = environmentRectangle(bounds);
1054
1742
  const inactive = environmentRectangle({ x: -100, y: -100, width: 0, height: 0 });
@@ -1063,10 +1751,10 @@ var Mascot = class {
1063
1751
  lookRight: this.state.lookRight,
1064
1752
  environment: {
1065
1753
  cursor: this.callbacks.pointer(),
1066
- screen: { width: window.innerWidth, height: window.innerHeight },
1754
+ screen: workArea,
1067
1755
  workArea,
1068
- floor: workArea.bottomBorder,
1069
- ceiling: workArea.topBorder,
1756
+ floor: edge((point) => isOnFloor(point, bounds, platforms)),
1757
+ ceiling: edge((point) => isOnTop(point, bounds) || platforms.some((candidate) => isOnBottom(point, candidate))),
1070
1758
  activeIE
1071
1759
  }
1072
1760
  }
@@ -1093,6 +1781,7 @@ var Mascot = class {
1093
1781
  }
1094
1782
  installPointerHandlers() {
1095
1783
  const element = this.domHandle.spriteElement;
1784
+ const document2 = element.ownerDocument;
1096
1785
  const listen = (target, type, listener) => {
1097
1786
  target.addEventListener(type, listener);
1098
1787
  this.disposers.push(() => target.removeEventListener(type, listener));
@@ -1101,29 +1790,29 @@ var Mascot = class {
1101
1790
  const pointerEvent = event;
1102
1791
  if (pointerEvent.button !== 0) return;
1103
1792
  event.preventDefault();
1104
- const point = { x: pointerEvent.clientX, y: pointerEvent.clientY };
1793
+ const point = this.dom.toLocalPoint(pointerEvent.clientX, pointerEvent.clientY);
1105
1794
  this.pointerId = pointerEvent.pointerId;
1106
1795
  this.pointerDown = point;
1107
1796
  this.lastPointer = point;
1108
1797
  this.dragOffset = { x: this.state.x - point.x, y: this.state.y - point.y };
1109
1798
  this.state.dragging = true;
1110
- this.actions.cancel();
1111
1799
  this.currentBehavior = this.behavior.force("Dragged") ?? this.behavior.force("\u30C9\u30E9\u30C3\u30B0\u3055\u308C\u308B");
1112
- if (this.currentBehavior) this.state.behaviorName = this.currentBehavior.name;
1800
+ if (this.currentBehavior) this.startBehavior(this.createEnvironment(this.dom.getBounds()), this.dom.getBounds(), this.platforms);
1113
1801
  element.setPointerCapture?.(pointerEvent.pointerId);
1114
1802
  });
1115
- listen(document, "pointermove", (event) => {
1803
+ listen(document2, "pointermove", (event) => {
1116
1804
  const pointerEvent = event;
1117
1805
  if (!this.state.dragging || pointerEvent.pointerId !== this.pointerId) return;
1118
- const point = { x: pointerEvent.clientX, y: pointerEvent.clientY };
1806
+ const point = this.dom.toLocalPoint(pointerEvent.clientX, pointerEvent.clientY);
1119
1807
  const previous = this.lastPointer ?? point;
1808
+ const bounds = this.dom.getBounds();
1120
1809
  this.state.vx = (point.x - previous.x) * 0.8;
1121
1810
  this.state.vy = (point.y - previous.y) * 0.8;
1122
1811
  this.state.x = point.x + this.dragOffset.x;
1123
1812
  this.state.y = point.y + this.dragOffset.y;
1124
1813
  this.lastPointer = point;
1125
1814
  });
1126
- listen(document, "pointerup", (event) => {
1815
+ listen(document2, "pointerup", (event) => {
1127
1816
  const pointerEvent = event;
1128
1817
  if (!this.state.dragging || pointerEvent.pointerId !== this.pointerId) return;
1129
1818
  this.state.dragging = false;
@@ -1131,8 +1820,8 @@ var Mascot = class {
1131
1820
  this.pointerId = void 0;
1132
1821
  this.currentBehavior = this.behavior.force("Thrown") ?? this.behavior.force("\u6295\u3052\u3089\u308C\u308B") ?? this.findFallBehavior();
1133
1822
  if (this.currentBehavior) {
1134
- this.state.behaviorName = this.currentBehavior.name;
1135
- this.actions.start(this.currentBehavior.name, this.createEnvironment(this.dom.getBounds()));
1823
+ const bounds = this.dom.getBounds();
1824
+ this.startBehavior(this.createEnvironment(bounds), bounds, this.platforms);
1136
1825
  }
1137
1826
  if (moved < 4) this.callbacks.click(this.snapshot());
1138
1827
  });
@@ -1140,21 +1829,23 @@ var Mascot = class {
1140
1829
  };
1141
1830
 
1142
1831
  // src/platform.ts
1143
- function resolvePlatformElements(source, document2, excludedRoot) {
1832
+ function resolvePlatformElements(source, root, excludedRoot) {
1144
1833
  let elements;
1145
1834
  if (typeof source === "string") {
1146
1835
  try {
1147
- elements = [...document2.querySelectorAll(source)];
1836
+ elements = [...root.querySelectorAll(source)];
1148
1837
  } catch {
1149
1838
  return [];
1150
1839
  }
1151
1840
  } else {
1152
1841
  elements = source;
1153
1842
  }
1843
+ const document2 = root.nodeType === 9 ? root : root.ownerDocument;
1844
+ if (!document2) return [];
1154
1845
  const HTMLElementConstructor = document2.defaultView?.HTMLElement;
1155
1846
  if (!HTMLElementConstructor) return [];
1156
1847
  return [...new Set(elements)].filter(
1157
- (element) => element instanceof HTMLElementConstructor && element.isConnected && (!excludedRoot || !excludedRoot.contains(element))
1848
+ (element) => element instanceof HTMLElementConstructor && element.isConnected && root.contains(element) && (!excludedRoot || !excludedRoot.contains(element))
1158
1849
  );
1159
1850
  }
1160
1851
  function readPlatformRectangles(elements, workAreaRectangle) {
@@ -1282,21 +1973,26 @@ var ShimejiEngine = class {
1282
1973
  destroyed = false;
1283
1974
  initialized = false;
1284
1975
  platformSource;
1285
- /** Starts the clock and global listeners. Calling this method more than once is harmless. */
1976
+ additionalPlatformElements = [];
1977
+ movedPlatforms = /* @__PURE__ */ new Map();
1978
+ platformRectangles = /* @__PURE__ */ new Map();
1979
+ /** Starts the clock and container-aware listeners. Calling this method more than once is harmless. */
1286
1980
  initialize() {
1287
1981
  this.assertAlive();
1288
1982
  if (this.initialized) return;
1289
1983
  this.initialized = true;
1290
- this.listen(document, "pointermove", (event) => {
1984
+ const document2 = this.container.ownerDocument;
1985
+ const view = document2.defaultView;
1986
+ if (!view) throw new Error("ShimejiEngine requires a container connected to a window");
1987
+ this.listen(document2, "pointermove", (event) => {
1291
1988
  const pointerEvent = event;
1292
- const x = pointerEvent.clientX;
1293
- const y = pointerEvent.clientY;
1989
+ const { x, y } = this.dom.toLocalPoint(pointerEvent.clientX, pointerEvent.clientY);
1294
1990
  this.pointer = { x, y, dx: x - this.pointer.x, dy: y - this.pointer.y };
1295
1991
  });
1296
- this.listen(window, "resize", () => this.renderAll());
1297
- const maintenance = window.setInterval(() => this.dom.ensureMounted(), 2e3);
1992
+ this.listen(view, "resize", () => this.renderAll());
1993
+ const maintenance = view.setInterval(() => this.dom.ensureMounted(), 2e3);
1298
1994
  this.intervals.add(maintenance);
1299
- this.animationFrame = requestAnimationFrame(this.onAnimationFrame);
1995
+ this.animationFrame = view.requestAnimationFrame(this.onAnimationFrame);
1300
1996
  }
1301
1997
  /** Registers or replaces a parsed or legacy character specification. */
1302
1998
  registerCharacter(spec) {
@@ -1326,8 +2022,8 @@ var ShimejiEngine = class {
1326
2022
  const random = this.options.random ?? Math.random;
1327
2023
  const spawnOptions = {
1328
2024
  ...position,
1329
- x: position.x ?? bounds.x + random() * bounds.width,
1330
- y: position.y ?? bounds.y
2025
+ x: position.x ?? Math.trunc(bounds.x + random() * bounds.width),
2026
+ y: position.y ?? bounds.y + 2
1331
2027
  };
1332
2028
  const id = `shimeji-${this.nextMascotId++}`;
1333
2029
  const mascot = new Mascot(id, spec, this.dom, this.options, spawnOptions, {
@@ -1339,6 +2035,7 @@ var ShimejiEngine = class {
1339
2035
  remove: (mascotId) => {
1340
2036
  if (!this.destroyed) this.remove(mascotId);
1341
2037
  },
2038
+ movePlatform: (element, point) => this.movePlatform(element, point),
1342
2039
  click: (state2) => this.events.emit("click", state2),
1343
2040
  error: (error) => this.events.emit("error", error)
1344
2041
  });
@@ -1375,22 +2072,27 @@ var ShimejiEngine = class {
1375
2072
  this.assertAlive();
1376
2073
  return this.events.on(event, listener);
1377
2074
  }
1378
- /** Replaces the DOM elements (or selector) exposed to mascots as platforms. */
1379
- setPlatforms(platforms) {
2075
+ /** Replaces the primary platform source and any additional registered elements. */
2076
+ setPlatforms(platforms, additionalPlatforms = []) {
1380
2077
  this.assertAlive();
1381
2078
  this.platformSource = platforms;
2079
+ this.additionalPlatformElements = additionalPlatforms;
1382
2080
  }
1383
2081
  /** Stops animation and timers, removes listeners and DOM, and revokes all object URLs. */
1384
2082
  destroy() {
1385
2083
  if (this.destroyed) return;
1386
2084
  for (const mascot of this.mascots.values()) mascot.destroy();
1387
2085
  this.mascots.clear();
1388
- if (this.animationFrame !== void 0) cancelAnimationFrame(this.animationFrame);
2086
+ const view = this.container.ownerDocument.defaultView;
2087
+ if (this.animationFrame !== void 0) view?.cancelAnimationFrame(this.animationFrame);
1389
2088
  this.animationFrame = void 0;
1390
- for (const interval of this.intervals) window.clearInterval(interval);
2089
+ for (const interval of this.intervals) view?.clearInterval(interval);
1391
2090
  this.intervals.clear();
1392
2091
  for (const dispose of this.disposers.splice(0)) dispose();
1393
2092
  this.dom.destroy();
2093
+ for (const [element, movement] of this.movedPlatforms) element.style.transform = movement.originalTransform;
2094
+ this.movedPlatforms.clear();
2095
+ this.platformRectangles.clear();
1394
2096
  this.sprites.destroy();
1395
2097
  this.specs.clear();
1396
2098
  this.events.clear();
@@ -1409,7 +2111,7 @@ var ShimejiEngine = class {
1409
2111
  const { bounds, platforms } = this.readFrameGeometry();
1410
2112
  for (const mascot of [...this.mascots.values()]) mascot.tick(delta, bounds, platforms);
1411
2113
  this.emitState();
1412
- this.animationFrame = requestAnimationFrame(this.onAnimationFrame);
2114
+ this.animationFrame = this.container.ownerDocument.defaultView?.requestAnimationFrame(this.onAnimationFrame);
1413
2115
  };
1414
2116
  renderAll() {
1415
2117
  const { bounds, platforms } = this.readFrameGeometry();
@@ -1417,8 +2119,26 @@ var ShimejiEngine = class {
1417
2119
  }
1418
2120
  readFrameGeometry() {
1419
2121
  const bounds = this.dom.getBounds();
1420
- const elements = resolvePlatformElements(this.platformSource, this.container.ownerDocument).filter((element) => !this.dom.owns(element));
1421
- return { bounds, platforms: readPlatformRectangles(elements, { left: 0, top: 0 }) };
2122
+ const elements = [.../* @__PURE__ */ new Set([
2123
+ ...resolvePlatformElements(this.platformSource, this.container),
2124
+ ...resolvePlatformElements(this.additionalPlatformElements, this.container)
2125
+ ])].filter((element) => !this.dom.owns(element));
2126
+ const platforms = readPlatformRectangles(elements, this.container.getBoundingClientRect());
2127
+ this.platformRectangles.clear();
2128
+ for (const platform of platforms) this.platformRectangles.set(platform.element, platform);
2129
+ return { bounds, platforms };
2130
+ }
2131
+ movePlatform(element, point) {
2132
+ const rectangle = this.platformRectangles.get(element);
2133
+ if (!rectangle) return;
2134
+ const movement = this.movedPlatforms.get(element) ?? { originalTransform: element.style.transform, x: 0, y: 0 };
2135
+ movement.x += point.x - rectangle.x;
2136
+ movement.y += point.y - rectangle.y;
2137
+ const translate = `translate(${movement.x}px, ${movement.y}px)`;
2138
+ element.style.transform = movement.originalTransform ? `${movement.originalTransform} ${translate}` : translate;
2139
+ rectangle.x = point.x;
2140
+ rectangle.y = point.y;
2141
+ this.movedPlatforms.set(element, movement);
1422
2142
  }
1423
2143
  emitState() {
1424
2144
  this.events.emit("statechange", this.getState());