@vibemancer/core 1.0.11 → 1.0.13

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.
@@ -213,7 +213,7 @@ function currentRuleset() {
213
213
  }
214
214
 
215
215
  // src/engine-version.ts
216
- var ENGINE_VERSION = 2983169471988242;
216
+ var ENGINE_VERSION = 4256327861353618;
217
217
  var EQUIVALENT_ENGINE_VERSIONS = [1688330189011034];
218
218
 
219
219
  // src/replay-compat.ts
@@ -516,23 +516,28 @@ function flyStraight() {
516
516
  _toMissileAction: () => ({})
517
517
  };
518
518
  }
519
+ function describeReturned(value) {
520
+ if (value === null) return "null";
521
+ if (value === void 0) return "undefined";
522
+ if (Array.isArray(value)) return "an array";
523
+ if (typeof value === "object") {
524
+ if ("then" in value && typeof value.then === "function") {
525
+ return "a Promise \u2014 your function is `async`, and it must not be: remove the async keyword";
526
+ }
527
+ return "a plain object";
528
+ }
529
+ return `a ${typeof value}`;
530
+ }
519
531
  function extractMissileAction(action) {
532
+ if (action === null || action === void 0 || typeof action._toMissileAction !== "function") {
533
+ throw new Error(
534
+ `Your missile AI returned ${describeReturned(action)}. Return turnToward(x, y), turnToAngle(degrees) or flyStraight(). The missile AI runs once per tick for every missile you have in flight, and it must return one of those every time.`
535
+ );
536
+ }
520
537
  return action._toMissileAction();
521
538
  }
522
539
  function extractAction(finalAction) {
523
540
  if (finalAction === null || finalAction === void 0 || typeof finalAction._toAction !== "function") {
524
- const describeReturned = (value) => {
525
- if (value === null) return "null";
526
- if (value === void 0) return "undefined";
527
- if (Array.isArray(value)) return "an array";
528
- if (typeof value === "object") {
529
- if ("then" in value && typeof value.then === "function") {
530
- return "a Promise \u2014 your bot function is `async`, and it must not be: remove the async keyword";
531
- }
532
- return "a plain object";
533
- }
534
- return `a ${typeof value}`;
535
- };
536
541
  const got = describeReturned(finalAction);
537
542
  throw new Error(
538
543
  `Your bot returned ${got}. Return one of: idle(), move(x, y), missile(config, ai, angle), shield(), blink(x, y), cancel() or aim(degrees). A bot must return an action every tick \u2014 returning nothing throws, it does not skip the tick.`
@@ -670,7 +675,8 @@ function startCast(wizard, spell, config) {
670
675
  wizard.castingSpell = spell;
671
676
  wizard.castProgress = 0;
672
677
  if (spell === "missile" && config) {
673
- const castTimeSec = calculateMissileCastTime(config, wizard.lastMissileConfig);
678
+ const validated = validateMissileConfig(config);
679
+ const castTimeSec = calculateMissileCastTime(validated, wizard.lastMissileConfig);
674
680
  wizard.castDuration = Math.max(1, Math.round(castTimeSec * TICKS_PER_SECOND));
675
681
  wizard.warmupMultiplier = calculateWarmupMultiplier(wizard.lastMissileConfig, config);
676
682
  } else if (spell === "shield") {
@@ -798,32 +804,33 @@ function createEntitySeed(matchSeed, entityId, tick2) {
798
804
  var MAX_MESSAGE_LENGTH = 1e3;
799
805
  var MAX_ERRORS_PER_MATCH = 500;
800
806
  function describeThrown(thrown) {
801
- return truncate(rawDescription(thrown));
807
+ const { text, alreadyBounded } = rawDescription(thrown);
808
+ return alreadyBounded ? text : truncate(text);
802
809
  }
803
810
  function rawDescription(thrown) {
804
811
  if (thrown instanceof Error) {
805
812
  const message = typeof thrown.message === "string" ? thrown.message : "";
806
813
  if (message.length > MAX_MESSAGE_LENGTH) {
807
- return truncate(message) + " [stack omitted: message too large]";
814
+ return { text: truncate(message) + " [stack omitted: message too large]", alreadyBounded: true };
808
815
  }
809
816
  const stack = typeof thrown.stack === "string" ? stripBundleCoordinates(thrown.stack.trim()) : "";
810
- if (stack) return stack;
811
- return message || `${thrown.name || "Error"} (thrown with no message)`;
817
+ if (stack) return { text: stack, alreadyBounded: false };
818
+ return { text: message || `${thrown.name || "Error"} (thrown with no message)`, alreadyBounded: false };
812
819
  }
813
- if (typeof thrown === "string") return thrown || "(empty string thrown)";
814
- if (thrown === null) return "null was thrown";
815
- if (thrown === void 0) return "undefined was thrown";
820
+ if (typeof thrown === "string") return { text: thrown || "(empty string thrown)", alreadyBounded: false };
821
+ if (thrown === null) return { text: "null was thrown", alreadyBounded: false };
822
+ if (thrown === void 0) return { text: "undefined was thrown", alreadyBounded: false };
816
823
  try {
817
824
  const json = JSON.stringify(thrown);
818
- if (typeof json === "string" && json !== "{}") return json;
825
+ if (typeof json === "string" && json !== "{}") return { text: json, alreadyBounded: false };
819
826
  } catch {
820
827
  }
821
828
  try {
822
829
  const text = String(thrown);
823
- if (text === "[object Object]") return describeOpaqueObject(thrown);
824
- return text;
830
+ if (text === "[object Object]") return { text: describeOpaqueObject(thrown), alreadyBounded: false };
831
+ return { text, alreadyBounded: false };
825
832
  } catch {
826
- return "(a value was thrown that could not be described)";
833
+ return { text: "(a value was thrown that could not be described)", alreadyBounded: false };
827
834
  }
828
835
  }
829
836
  function describeOpaqueObject(thrown) {
@@ -842,7 +849,7 @@ function describeOpaqueObject(thrown) {
842
849
  return `a non-serialisable ${name} was thrown${shape} (it could not be converted to text \u2014 usually a circular reference, or a getter that throws)`;
843
850
  }
844
851
  function stripBundleCoordinates(stack) {
845
- return stack.replace(/\s*\((?:[^()\s]*match-bundle\.js|<isolated-vm>)[^()]*\)/g, "");
852
+ return stack.replace(/\s*\((?:[^()\s]*match-bundle\.js|<isolated-vm>)[^()]*\)/g, "").replace(/(\s*at )(?:[^()\s]*match-bundle\.js|<isolated-vm>)[^\s)]*/g, "$1<your bot>");
846
853
  }
847
854
  function truncate(message) {
848
855
  if (message.length <= MAX_MESSAGE_LENGTH) return message;
@@ -940,6 +947,18 @@ function buildWizardContext(state, config, random) {
940
947
  lastHitTick: state.lastHitTick
941
948
  };
942
949
  }
950
+ function nonFiniteField(move2, action, wizard) {
951
+ const bad = (v) => typeof v === "number" && !Number.isFinite(v);
952
+ if (bad(move2?.x)) return { what: "move(x, y): x", value: String(move2.x) };
953
+ if (bad(move2?.y)) return { what: "move(x, y): y", value: String(move2.y) };
954
+ const target = action.startCast?.spell === "blink" ? action.startCast.target : void 0;
955
+ if (bad(target?.x)) return { what: "blink(x, y): x", value: String(target.x) };
956
+ if (bad(target?.y)) return { what: "blink(x, y): y", value: String(target.y) };
957
+ if (bad(wizard.blinkTarget?.x)) return { what: "blink(x, y): x", value: String(wizard.blinkTarget.x) };
958
+ if (bad(wizard.blinkTarget?.y)) return { what: "blink(x, y): y", value: String(wizard.blinkTarget.y) };
959
+ if (bad(action.aimDirection)) return { what: "aim(degrees)", value: String(action.aimDirection) };
960
+ return null;
961
+ }
943
962
  function tick(currentTick, wizard1AI, wizard2AI, config, wizards, projectiles, missileAIs, matchSeed, budgets) {
944
963
  const nextTick = currentTick + 1;
945
964
  const errors = [];
@@ -1083,6 +1102,17 @@ function tick(currentTick, wizard1AI, wizard2AI, config, wizards, projectiles, m
1083
1102
  }
1084
1103
  const oldPos = wizard.position;
1085
1104
  const move2 = action.move ?? { x: 0, y: 0 };
1105
+ if (!wizard.advised?.nonFinite) {
1106
+ const bad = nonFiniteField(move2, action, wizard);
1107
+ if (bad) {
1108
+ wizard.advised = { ...wizard.advised, nonFinite: true };
1109
+ errors.push({
1110
+ tick: nextTick,
1111
+ entityId: wizard.id,
1112
+ message: `${bad.what} is not a finite number (it was ${bad.value}). The engine ignored it to keep the match running \u2014 the value is treated as zero, so your wizard simply does not do what you asked. This is almost always a divide-by-zero or a subtraction of two equal positions somewhere in your maths. Reported once per match.`
1113
+ });
1114
+ }
1115
+ }
1086
1116
  wizard.position = moveWizard(wizard, move2, 1);
1087
1117
  if (wizard.knockbackDelay !== void 0 && wizard.knockbackDelay > 0) {
1088
1118
  wizard.knockbackDelay--;
@@ -1129,6 +1159,15 @@ function tick(currentTick, wizard1AI, wizard2AI, config, wizards, projectiles, m
1129
1159
  const castingSpell = action.startCast.spell;
1130
1160
  startCast(wizard, castingSpell, castingSpell === "missile" ? action.startCast.config : void 0);
1131
1161
  if (castingSpell === "missile") {
1162
+ if ((wizard.castDuration ?? 0) > MATCH_DURATION && !wizard.advised?.unfinishableCast) {
1163
+ wizard.advised = { ...wizard.advised, unfinishableCast: true };
1164
+ const config2 = action.startCast.config;
1165
+ errors.push({
1166
+ tick: nextTick,
1167
+ entityId: wizard.id,
1168
+ message: `This missile takes ${wizard.castDuration} ticks to cast, but a match is only ${MATCH_DURATION} ticks long, so the cast can never finish and this wizard will stand still for the rest of the match. The cost is dominated by turnRate (${config2?.turnRate ?? "?"}) \u2014 it is the most expensive field by far, and unlike the others it is not clamped to a sane maximum. Try a turnRate in the single digits, then use missile-calc or getMissileCastTime() to check the cast time before committing to a config.`
1169
+ });
1170
+ }
1132
1171
  wizard.missileConfig = action.startCast.config;
1133
1172
  wizard.missileAI = action.startCast.missileAI;
1134
1173
  if (action.startCast.direction !== void 0) {
@@ -2095,13 +2134,14 @@ function fitMissileForEscapingTarget(currentDistance, budgetTicks, options) {
2095
2134
  }
2096
2135
 
2097
2136
  // src/hooks/threat-analysis.ts
2137
+ var CANCEL_COST_TICKS = 1;
2098
2138
  var THREAT_RELEVANCE_DISTANCE = 500;
2099
- function analyzeThreats(myPos, projectiles, myProjectiles, ticksUntilReady) {
2139
+ function analyzeThreats(myPos, projectiles, myProjectiles, ticksUntilReady, options) {
2100
2140
  const myProjectileIds = new Set(myProjectiles.map((p) => p.id));
2101
2141
  const enemyProjectiles = projectiles.filter((p) => !myProjectileIds.has(p.id));
2102
2142
  const threats = [];
2103
2143
  for (const projectile of enemyProjectiles) {
2104
- const analysis = analyzeOneThreat(myPos, projectile, ticksUntilReady);
2144
+ const analysis = analyzeOneThreat(myPos, projectile, ticksUntilReady, options?.canCancelCurrentCast ?? false);
2105
2145
  const missileDistance = Math.sqrt(
2106
2146
  (projectile.position.x - myPos.x) ** 2 + (projectile.position.y - myPos.y) ** 2
2107
2147
  );
@@ -2112,7 +2152,7 @@ function analyzeThreats(myPos, projectiles, myProjectiles, ticksUntilReady) {
2112
2152
  threats.sort((a, b) => a.ticksToImpact - b.ticksToImpact);
2113
2153
  return threats;
2114
2154
  }
2115
- function analyzeOneThreat(targetPos, projectile, ticksUntilReady) {
2155
+ function analyzeOneThreat(targetPos, projectile, ticksUntilReady, canCancelCurrentCast) {
2116
2156
  const missileRadius = calculateMissileRadius(projectile.damage);
2117
2157
  const collisionDist = COLLISION_RADIUS + missileRadius;
2118
2158
  const dodgeCollisionDist = collisionDist + projectile.speed * 0.5;
@@ -2138,8 +2178,10 @@ function analyzeOneThreat(targetPos, projectile, ticksUntilReady) {
2138
2178
  ticksToImpact
2139
2179
  );
2140
2180
  }
2141
- const canBlockInTime = ticksToImpact > ticksUntilReady + RULES.SHIELD_CAST_TIME + 1;
2142
- const ticksToStartShield = Math.max(0, ticksToImpact - RULES.SHIELD_CAST_TIME - 1);
2181
+ const delayBeforeShield = canCancelCurrentCast ? CANCEL_COST_TICKS : ticksUntilReady;
2182
+ const ticksNeededToBlock = delayBeforeShield + RULES.SHIELD_CAST_TIME + 1;
2183
+ const canBlockInTime = ticksToImpact >= ticksNeededToBlock;
2184
+ const ticksToStartShield = Math.max(0, ticksToImpact - ticksNeededToBlock);
2143
2185
  return {
2144
2186
  id: projectile.id,
2145
2187
  projectile,
@@ -2414,9 +2456,13 @@ function useThreats() {
2414
2456
  ctx.position,
2415
2457
  ctx.projectiles,
2416
2458
  ctx.myProjectiles,
2417
- ticksUntilReady
2459
+ ticksUntilReady,
2460
+ // Mid-CAST you are one cancel() away from being able to shield, so the analysis
2461
+ // must not charge you the whole remainder of the cast. A GCD cannot be cancelled,
2462
+ // so it keeps the pessimistic reading.
2463
+ { canCancelCurrentCast: ctx.state === "casting" }
2418
2464
  );
2419
- }, [ctx.projectiles, ctx.myProjectiles, ctx.position.x, ctx.position.y, ticksUntilReady]);
2465
+ }, [ctx.projectiles, ctx.myProjectiles, ctx.position.x, ctx.position.y, ticksUntilReady, ctx.state]);
2420
2466
  }
2421
2467
  function useClosestThreat() {
2422
2468
  const threats = useThreats();
@@ -10898,4 +10944,4 @@ export {
10898
10944
  diagnoseTrace,
10899
10945
  formatDiagnosis
10900
10946
  };
10901
- //# sourceMappingURL=chunk-EO7JO2RZ.js.map
10947
+ //# sourceMappingURL=chunk-KGRG7TZS.js.map