@react-shimeji/core 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -701,21 +701,25 @@ var BehaviorController = class {
701
701
  };
702
702
 
703
703
  // src/physics.ts
704
- var BORDER_TOLERANCE = 0.999999;
704
+ var BORDER_TOLERANCE = 1 - Number.EPSILON / 2;
705
+ function isWithinSpan(value, minimum, maximum, tolerance) {
706
+ const distance = value < minimum ? minimum - value : value > maximum ? value - maximum : 0;
707
+ return distance <= tolerance;
708
+ }
705
709
  function clamp(value, minimum, maximum) {
706
710
  return Math.min(Math.max(value, minimum), maximum);
707
711
  }
708
712
  function isOnTop(point, rectangle, tolerance = BORDER_TOLERANCE) {
709
- return point.x >= rectangle.x - tolerance && point.x <= rectangle.x + rectangle.width + tolerance && Math.abs(point.y - rectangle.y) <= tolerance;
713
+ return isWithinSpan(point.x, rectangle.x, rectangle.x + rectangle.width, tolerance) && Math.abs(point.y - rectangle.y) <= tolerance;
710
714
  }
711
715
  function isOnBottom(point, rectangle, tolerance = BORDER_TOLERANCE) {
712
- return point.x >= rectangle.x - tolerance && point.x <= rectangle.x + rectangle.width + tolerance && Math.abs(point.y - rectangle.y - rectangle.height) <= tolerance;
716
+ return isWithinSpan(point.x, rectangle.x, rectangle.x + rectangle.width, tolerance) && Math.abs(point.y - rectangle.y - rectangle.height) <= tolerance;
713
717
  }
714
718
  function isOnLeft(point, rectangle, tolerance = BORDER_TOLERANCE) {
715
- return point.y >= rectangle.y - tolerance && point.y <= rectangle.y + rectangle.height + tolerance && Math.abs(point.x - rectangle.x) <= tolerance;
719
+ return isWithinSpan(point.y, rectangle.y, rectangle.y + rectangle.height, tolerance) && Math.abs(point.x - rectangle.x) <= tolerance;
716
720
  }
717
721
  function isOnRight(point, rectangle, tolerance = BORDER_TOLERANCE) {
718
- return point.y >= rectangle.y - tolerance && point.y <= rectangle.y + rectangle.height + tolerance && Math.abs(point.x - rectangle.x - rectangle.width) <= tolerance;
722
+ return isWithinSpan(point.y, rectangle.y, rectangle.y + rectangle.height, tolerance) && Math.abs(point.x - rectangle.x - rectangle.width) <= tolerance;
719
723
  }
720
724
  function isOnBorder(state, bounds, border, platform) {
721
725
  if (!border) return true;
@@ -1633,6 +1637,7 @@ function environmentRectangle(bounds) {
1633
1637
  };
1634
1638
  }
1635
1639
  var PLATFORM_NEARBY_DISTANCE = 400;
1640
+ var PLATFORM_EDGE_TOLERANCE = 2;
1636
1641
  function distanceToRectangle(point, rectangle) {
1637
1642
  const dx = Math.max(rectangle.x - point.x, 0, point.x - rectangle.x - rectangle.width);
1638
1643
  const dy = Math.max(rectangle.y - point.y, 0, point.y - rectangle.y - rectangle.height);
@@ -1663,6 +1668,7 @@ var Mascot = class {
1663
1668
  this.domHandle = dom.createMascot(spec, id, options.mascotClassName || void 0);
1664
1669
  this.frameDuration = options.frameDuration;
1665
1670
  this.random = options.random ?? Math.random;
1671
+ this.forceInitialFall = spawnOptions.behaviorName === void 0;
1666
1672
  this.behavior = new BehaviorController(spec, options.random);
1667
1673
  this.actions = new ActionExecutor(spec, this.state, options, {
1668
1674
  spawn: (position, characterId) => this.callbacks.spawn(characterId ?? this.spec.id, position),
@@ -1692,6 +1698,7 @@ var Mascot = class {
1692
1698
  accumulatedMs = 0;
1693
1699
  frameDuration;
1694
1700
  random;
1701
+ forceInitialFall;
1695
1702
  /** Advances behavior, animation, physics, and rendering by one clock tick. */
1696
1703
  tick(deltaMs, bounds, platforms = []) {
1697
1704
  if (this.destroyed) return;
@@ -1731,7 +1738,7 @@ var Mascot = class {
1731
1738
  for (let guard = 0; guard < 32 && !this.destroyed; guard += 1) {
1732
1739
  const environment = this.createEnvironment(bounds, platforms);
1733
1740
  if (!this.currentBehavior) {
1734
- this.currentBehavior = initial ? this.behavior.selectInitial(environment, this.state.behaviorName) : this.selectNextBehavior(environment, bounds);
1741
+ this.currentBehavior = initial ? this.forceInitialFall ? this.findFallBehavior() ?? this.behavior.selectInitial(environment) : this.behavior.selectInitial(environment, this.state.behaviorName) : this.selectNextBehavior(environment, bounds);
1735
1742
  initial = false;
1736
1743
  if (!this.currentBehavior) this.currentBehavior = this.findFallBehavior();
1737
1744
  if (!this.currentBehavior || !this.startBehavior(environment, bounds, platforms)) return;
@@ -1750,7 +1757,7 @@ var Mascot = class {
1750
1757
  if (result === "lost-ground") {
1751
1758
  this.state.dragging = false;
1752
1759
  this.actions.cancel();
1753
- this.currentBehavior = this.findFallBehavior();
1760
+ this.currentBehavior = this.selectEdgeBehavior(bounds, platforms);
1754
1761
  if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms);
1755
1762
  } else if (result === "complete") {
1756
1763
  this.currentBehavior = this.selectNextBehavior(this.createEnvironment(bounds, platforms), bounds);
@@ -1776,14 +1783,37 @@ var Mascot = class {
1776
1783
  findFallBehavior() {
1777
1784
  return this.behavior.force("Fall") ?? this.behavior.force("\u843D\u4E0B\u3059\u308B");
1778
1785
  }
1779
- selectNextBehavior(environment, bounds) {
1786
+ selectNextBehavior(environment, bounds, relocateOnFallback = true) {
1780
1787
  const selected = this.behavior.selectNext(environment);
1781
- if (this.behavior.usedFallback()) {
1788
+ if (relocateOnFallback && this.behavior.usedFallback()) {
1782
1789
  this.state.x = Math.trunc(bounds.x + this.random() * bounds.width);
1783
1790
  this.state.y = bounds.y - 256;
1784
1791
  }
1785
1792
  return selected;
1786
1793
  }
1794
+ selectEdgeBehavior(bounds, platforms) {
1795
+ const original = { x: this.state.x, y: this.state.y };
1796
+ const platform = platforms.find((candidate) => candidate.element === this.activePlatformElement);
1797
+ if (platform) {
1798
+ const left = platform.x;
1799
+ const right = platform.x + platform.width;
1800
+ const top = platform.y;
1801
+ const bottom = platform.y + platform.height;
1802
+ if (Math.abs(this.state.y - top) <= PLATFORM_EDGE_TOLERANCE || Math.abs(this.state.y - bottom) <= PLATFORM_EDGE_TOLERANCE) {
1803
+ if (this.state.x < left) this.state.x = left;
1804
+ else if (this.state.x > right) this.state.x = right;
1805
+ } else if (Math.abs(this.state.x - left) <= PLATFORM_EDGE_TOLERANCE || Math.abs(this.state.x - right) <= PLATFORM_EDGE_TOLERANCE) {
1806
+ if (this.state.y < top) this.state.y = top;
1807
+ else if (this.state.y > bottom) this.state.y = bottom;
1808
+ }
1809
+ }
1810
+ const selected = this.selectNextBehavior(this.createEnvironment(bounds, platforms), bounds, false);
1811
+ if (this.behavior.usedFallback() || selected?.name === "Fall" || selected?.name === "\u843D\u4E0B\u3059\u308B") {
1812
+ this.state.x = original.x;
1813
+ this.state.y = original.y;
1814
+ }
1815
+ return selected ?? this.findFallBehavior();
1816
+ }
1787
1817
  createEnvironment(bounds, platforms = this.platforms) {
1788
1818
  const workArea = environmentRectangle(bounds);
1789
1819
  const inactive = environmentRectangle({ x: -100, y: -100, width: 0, height: 0 });
@@ -1814,7 +1844,7 @@ var Mascot = class {
1814
1844
  }
1815
1845
  const anchor = this.state;
1816
1846
  const current = platforms.find((platform) => platform.element === this.activePlatformElement);
1817
- if (current && (isOnTop(anchor, current, 2) || isOnBottom(anchor, current, 2) || isOnLeft(anchor, current, 2) || isOnRight(anchor, current, 2))) return current;
1847
+ if (current && (isOnTop(anchor, current, PLATFORM_EDGE_TOLERANCE) || isOnBottom(anchor, current, PLATFORM_EDGE_TOLERANCE) || isOnLeft(anchor, current, PLATFORM_EDGE_TOLERANCE) || isOnRight(anchor, current, PLATFORM_EDGE_TOLERANCE))) return current;
1818
1848
  if (this.state.vy >= 0) {
1819
1849
  const landingPlatform = platforms.filter((platform) => anchor.x >= platform.x && anchor.x <= platform.x + platform.width && platform.y >= anchor.y - 1).sort((left, right) => left.y - right.y)[0];
1820
1850
  if (landingPlatform) {
@@ -2067,9 +2097,13 @@ var ShimejiEngine = class {
2067
2097
  if (!spec) throw new Error(`Character '${characterId}' is not registered`);
2068
2098
  const { bounds, platforms } = this.readFrameGeometry();
2069
2099
  const random = this.options.random ?? Math.random;
2100
+ const randomX = Math.trunc(bounds.x + random() * bounds.width);
2101
+ const spawnInset = Math.min(2, bounds.width / 2);
2070
2102
  const spawnOptions = {
2071
2103
  ...position,
2072
- x: position.x ?? Math.trunc(bounds.x + random() * bounds.width),
2104
+ // Fall treats the wall in the facing direction as ground. Keep implicit
2105
+ // spawns clear of both side-wall tolerances so even random() === 0 falls.
2106
+ x: position.x ?? Math.min(Math.max(randomX, bounds.x + spawnInset), bounds.x + bounds.width - spawnInset),
2073
2107
  y: position.y ?? bounds.y + 2
2074
2108
  };
2075
2109
  const id = `shimeji-${this.nextMascotId++}`;