@vibemancer/core 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/chunk-W7HWJFQ4.js +10599 -0
- package/dist/chunk-W7HWJFQ4.js.map +1 -0
- package/dist/index-browser.d.ts +1300 -1051
- package/dist/index-browser.js +55 -17
- package/dist/index.d.ts +53 -3
- package/dist/index.js +200 -54
- package/dist/index.js.map +1 -1
- package/package.json +21 -15
- package/src/bots/Hero.ts +867 -0
- package/src/bots/berserker/01_Stormchaser.ts +162 -120
- package/src/bots/berserker/02_Stormcaller.ts +160 -116
- package/src/bots/berserker/03_Stormforger.ts +162 -129
- package/src/bots/caster/01_Flamecaller.ts +126 -84
- package/src/bots/caster/02_Pyromancer.ts +148 -101
- package/src/bots/caster/03_Infernalist.ts +180 -160
- package/src/bots/defensive/01_Turtle.ts +48 -44
- package/src/bots/defensive/02_Sentinel.ts +57 -53
- package/src/bots/defensive/03_Golem.ts +85 -97
- package/src/bots/duelist/01_Battlemage.ts +157 -122
- package/src/bots/duelist/02_Warmage.ts +166 -121
- package/src/bots/duelist/03_Archmage.ts +198 -178
- package/src/bots/homing/01_Bonemancer.ts +20 -32
- package/src/bots/homing/02_Lich.ts +145 -93
- package/src/bots/homing/03_Archlich.ts +129 -60
- package/src/bots/kiter/01_Spellspinner.ts +145 -107
- package/src/bots/kiter/02_Spellweaver.ts +153 -105
- package/src/bots/kiter/03_Spellbinder.ts +168 -123
- package/src/bots/melee/01_Shadowblade.ts +131 -75
- package/src/bots/melee/02_Nightblade.ts +174 -130
- package/src/bots/melee/03_Voidblade.ts +266 -168
- package/src/bots/registry.ts +44 -37
- package/src/bots/shared.ts +185 -25
- package/src/bots/sniper/01_Spellshot.ts +151 -98
- package/src/bots/sniper/02_Spelltracer.ts +173 -128
- package/src/bots/sniper/03_Spellseeker.ts +177 -152
- package/src/bots/standalone/Critter.ts +46 -52
- package/src/bots/standalone/Doombringer.ts +36 -40
- package/src/bots/standalone/Hogger.ts +120 -89
- package/src/bots/standalone/Rookie.ts +21 -23
- package/src/bots/standalone/TargetDummy.ts +5 -6
- package/src/bots/test/cheater.ts +91 -85
- package/src/bots/test/crasher.ts +26 -28
- package/src/engine/bundle-fight.ts +242 -0
- package/src/engine/hooks-runtime.ts +58 -18
- package/src/engine/manual-match.ts +33 -25
- package/src/engine/missile-templates.ts +25 -43
- package/src/engine/optimizer.ts +7 -7
- package/src/engine/params-runtime.ts +3 -3
- package/src/engine/physics.ts +33 -23
- package/src/engine/sandbox-browser.ts +8 -7
- package/src/engine/sandbox-compile.ts +1 -1
- package/src/engine/sandbox-harness.ts +299 -367
- package/src/engine/sandbox.ts +3 -3
- package/src/engine/simulation.ts +291 -76
- package/src/engine/spells.ts +9 -12
- package/src/engine-version.ts +1 -1
- package/src/hooks/action-builders.ts +76 -21
- package/src/hooks/index.ts +17 -9
- package/src/hooks/state-hooks.ts +61 -25
- package/src/hooks/threat-analysis.ts +44 -20
- package/src/hooks/types.ts +62 -5
- package/src/index.ts +2 -0
- package/src/rules.ts +209 -63
- package/src/stats.ts +2 -2
- package/src/testing.ts +6 -30
- package/src/trace.ts +63 -3
- package/src/types.ts +127 -14
- package/src/utils/angles.ts +1 -0
- package/src/utils/combat.ts +98 -6
- package/src/utils/spatial.ts +3 -3
- package/dist/chunk-74FFJCFF.js +0 -9140
- package/dist/chunk-74FFJCFF.js.map +0 -1
- package/src/hooks/bot-wrapper.ts +0 -84
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {FinalAction} from '../../hooks/index.js';
|
|
2
|
+
import {usePosition, useEnemy, useStatus, useBlinkCooldown, useThreats, useLastMissileConfig, useCastingSpell, idle, missile, move, shield, blink, cancel, getMissileContext, turnToward, flyStraight} from '../../hooks/index.js';
|
|
2
3
|
import {angleTo} from '../../utils/angles.js';
|
|
3
|
-
import {
|
|
4
|
+
import {RULES} from '../../rules.js';
|
|
4
5
|
import {getMissileCastTime} from '../../utils/combat.js';
|
|
5
6
|
import {
|
|
6
|
-
getThreats,
|
|
7
7
|
getMostUrgentThreat,
|
|
8
8
|
getBlinkToCenter,
|
|
9
|
+
keepOffWalls,
|
|
10
|
+
getLavaEvade,
|
|
9
11
|
} from '../shared.js';
|
|
10
12
|
import {useParam} from '../../engine/params-runtime.js';
|
|
11
13
|
|
|
@@ -44,9 +46,18 @@ import {useParam} from '../../engine/params-runtime.js';
|
|
|
44
46
|
// Range = 5 × 180 = 900 units.
|
|
45
47
|
const MISSILE_CONFIG = {damage: 12, speed: 5, turnRate: 1, duration: 180};
|
|
46
48
|
|
|
47
|
-
export
|
|
49
|
+
export function Turtle(): FinalAction
|
|
48
50
|
{
|
|
49
|
-
const
|
|
51
|
+
const position = usePosition();
|
|
52
|
+
const enemy = useEnemy();
|
|
53
|
+
const status = useStatus();
|
|
54
|
+
const blinkCooldown = useBlinkCooldown();
|
|
55
|
+
const threats = useThreats();
|
|
56
|
+
const lastMissileConfig = useLastMissileConfig();
|
|
57
|
+
const castingSpell = useCastingSpell();
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
const shieldBuffer = useParam('shieldBuffer', 4.3, {
|
|
50
61
|
range: 5,
|
|
51
62
|
min: 0,
|
|
52
63
|
steps: 5,
|
|
@@ -56,21 +67,22 @@ export const Turtle: WizardFunction = ({state}) =>
|
|
|
56
67
|
min: 0,
|
|
57
68
|
steps: 5,
|
|
58
69
|
});
|
|
59
|
-
const ticksToReshield = SHIELD_CAST_TIME + shieldBuffer;
|
|
60
|
-
|
|
61
|
-
const enemy = state.enemies[0];
|
|
62
|
-
if (!enemy) return {move: {x: 0, y: 0}};
|
|
70
|
+
const ticksToReshield = RULES.SHIELD_CAST_TIME + shieldBuffer;
|
|
63
71
|
|
|
64
|
-
const threats = getThreats(state);
|
|
65
72
|
const urgentThreat = getMostUrgentThreat(threats);
|
|
66
73
|
|
|
74
|
+
// Lava-evade: a knockback-lethal hit near a wall must be dodged/blinked, not
|
|
75
|
+
// shielded — knockback ignores shields. Highest priority.
|
|
76
|
+
const lavaEvade = getLavaEvade(position, enemy.position, threats, blinkCooldown);
|
|
77
|
+
if (lavaEvade) return lavaEvade.action === 'blink' ? blink(lavaEvade.x, lavaEvade.y) : move(lavaEvade.x, lavaEvade.y);
|
|
78
|
+
|
|
67
79
|
const shouldStartShield =
|
|
68
80
|
urgentThreat !== null &&
|
|
69
|
-
urgentThreat.ticksToImpact <= SHIELD_CAST_TIME + shieldBuffer;
|
|
81
|
+
urgentThreat.ticksToImpact <= RULES.SHIELD_CAST_TIME + shieldBuffer;
|
|
70
82
|
|
|
71
83
|
// === CHANNELING SHIELD ===
|
|
72
84
|
// Hold shield until the imminent missile(s) have actually impacted.
|
|
73
|
-
if (
|
|
85
|
+
if (status === 'channeling' && 'shield' === 'shield')
|
|
74
86
|
{
|
|
75
87
|
// Don't cancel while a missile is about to hit
|
|
76
88
|
const imminentThreat = threats.find(
|
|
@@ -78,74 +90,66 @@ export const Turtle: WizardFunction = ({state}) =>
|
|
|
78
90
|
);
|
|
79
91
|
if (imminentThreat)
|
|
80
92
|
{
|
|
81
|
-
return
|
|
93
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
// Safe to cancel if no next threat, or next threat is far enough to reshield
|
|
85
97
|
const nextThreat = threats.find((t) => t.willHit);
|
|
86
98
|
if (!nextThreat || nextThreat.ticksToImpact > ticksToReshield)
|
|
87
99
|
{
|
|
88
|
-
return
|
|
100
|
+
return cancel();
|
|
89
101
|
}
|
|
90
102
|
// Next threat too soon — keep channeling
|
|
91
|
-
return
|
|
103
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
// === CASTING SHIELD: let it finish ===
|
|
95
|
-
if (
|
|
107
|
+
if (status === 'casting' && castingSpell === 'shield')
|
|
96
108
|
{
|
|
97
|
-
return
|
|
109
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
// === NEED TO SHIELD NOW: cancel anything else ===
|
|
101
113
|
if (shouldStartShield)
|
|
102
114
|
{
|
|
103
|
-
if (
|
|
115
|
+
if (status === 'casting')
|
|
104
116
|
{
|
|
105
|
-
return
|
|
117
|
+
return cancel();
|
|
106
118
|
}
|
|
107
|
-
if (
|
|
119
|
+
if (status === 'idle')
|
|
108
120
|
{
|
|
109
|
-
return
|
|
121
|
+
return shield();
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
// === EMERGENCY BLINK: missile too close for shield, blink available ===
|
|
114
126
|
if (
|
|
115
|
-
|
|
127
|
+
status === 'idle' &&
|
|
116
128
|
urgentThreat &&
|
|
117
|
-
urgentThreat.ticksToImpact < SHIELD_CAST_TIME + shieldBuffer &&
|
|
129
|
+
urgentThreat.ticksToImpact < RULES.SHIELD_CAST_TIME + shieldBuffer &&
|
|
118
130
|
urgentThreat.ticksToImpact >= 10 &&
|
|
119
|
-
|
|
131
|
+
blinkCooldown === 0
|
|
120
132
|
)
|
|
121
133
|
{
|
|
122
|
-
return
|
|
123
|
-
move: {x: 0, y: 0},
|
|
124
|
-
startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
|
|
125
|
-
};
|
|
134
|
+
return blink(getBlinkToCenter(position).x, getBlinkToCenter(position).y);
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
// === ATTACK: one missile if safe window exists ===
|
|
129
138
|
// Dynamic safe window: actual cast time (with warmup) + GCD + shield + buffer
|
|
130
|
-
const castTime = getMissileCastTime(MISSILE_CONFIG,
|
|
131
|
-
const safeWindow = castTime + GCD_DURATION + SHIELD_CAST_TIME + shieldBuffer;
|
|
139
|
+
const castTime = getMissileCastTime(MISSILE_CONFIG, lastMissileConfig);
|
|
140
|
+
const safeWindow = castTime + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + shieldBuffer;
|
|
132
141
|
if (
|
|
133
|
-
|
|
142
|
+
status === 'idle' &&
|
|
134
143
|
(!urgentThreat || urgentThreat.ticksToImpact > safeWindow)
|
|
135
144
|
)
|
|
136
145
|
{
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
turnToward: worldState.enemies[0]?.position,
|
|
144
|
-
}),
|
|
145
|
-
direction: angleTo(state.position, enemy.position),
|
|
146
|
-
},
|
|
147
|
-
};
|
|
146
|
+
return missile(MISSILE_CONFIG, () =>
|
|
147
|
+
{
|
|
148
|
+
const ctx = getMissileContext();
|
|
149
|
+
const enemy = ctx.worldState.enemies[0];
|
|
150
|
+
return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
|
|
151
|
+
}, angleTo(position, enemy.position)).move(keepOffWalls(position).x, keepOffWalls(position).y);
|
|
148
152
|
}
|
|
149
153
|
|
|
150
|
-
return
|
|
151
|
-
}
|
|
154
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
155
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {FinalAction} from '../../hooks/index.js';
|
|
2
|
+
import {usePosition, useEnemy, useStatus, useBlinkCooldown, useThreats, useLastMissileConfig, useCastingSpell, idle, missile, move, shield, blink, cancel, getMissileContext, turnToward, flyStraight} from '../../hooks/index.js';
|
|
2
3
|
import {angleTo} from '../../utils/angles.js';
|
|
3
|
-
import {
|
|
4
|
+
import {RULES} from '../../rules.js';
|
|
4
5
|
import {getMissileCastTime} from '../../utils/combat.js';
|
|
5
6
|
import {
|
|
6
|
-
getThreats,
|
|
7
7
|
getMostUrgentThreat,
|
|
8
8
|
getBlinkToCenter,
|
|
9
|
+
keepOffWalls,
|
|
10
|
+
getLavaEvade,
|
|
9
11
|
} from '../shared.js';
|
|
10
12
|
import {useParam} from '../../engine/params-runtime.js';
|
|
11
13
|
|
|
@@ -24,8 +26,17 @@ import {useParam} from '../../engine/params-runtime.js';
|
|
|
24
26
|
*
|
|
25
27
|
* TIER: 2 (enhanced Turtle)
|
|
26
28
|
*/
|
|
27
|
-
export
|
|
29
|
+
export function Sentinel(): FinalAction
|
|
28
30
|
{
|
|
31
|
+
const position = usePosition();
|
|
32
|
+
const enemy = useEnemy();
|
|
33
|
+
const status = useStatus();
|
|
34
|
+
const blinkCooldown = useBlinkCooldown();
|
|
35
|
+
const threats = useThreats();
|
|
36
|
+
const lastMissileConfig = useLastMissileConfig();
|
|
37
|
+
const castingSpell = useCastingSpell();
|
|
38
|
+
|
|
39
|
+
|
|
29
40
|
const shieldHoldTicks = useParam('shieldHoldTicks', 2, {
|
|
30
41
|
range: 5,
|
|
31
42
|
min: 0,
|
|
@@ -33,102 +44,95 @@ export const Sentinel: WizardFunction = ({state}) =>
|
|
|
33
44
|
steps: 5,
|
|
34
45
|
});
|
|
35
46
|
|
|
36
|
-
const enemy = state.enemies[0];
|
|
37
47
|
|
|
38
|
-
const threats = getThreats(state);
|
|
39
48
|
const urgentThreat = getMostUrgentThreat(threats);
|
|
40
49
|
|
|
41
50
|
const SHIELD_BUFFER = 3;
|
|
42
51
|
const shouldStartShield =
|
|
43
52
|
urgentThreat !== null &&
|
|
44
|
-
urgentThreat.ticksToImpact <= SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
53
|
+
urgentThreat.ticksToImpact <= RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
45
54
|
|
|
46
|
-
const TICKS_TO_RESHIELD = SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
55
|
+
const TICKS_TO_RESHIELD = RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
47
56
|
const nextThreat = threats.filter((t) => t.willHit)[1] ?? null;
|
|
48
57
|
const safeToCancel =
|
|
49
58
|
!nextThreat || nextThreat.ticksToImpact > TICKS_TO_RESHIELD;
|
|
50
59
|
|
|
51
|
-
|
|
60
|
+
// Lava-evade: a knockback-lethal hit near a wall must be dodged/blinked, not
|
|
61
|
+
// shielded — knockback ignores shields. Highest priority.
|
|
62
|
+
const lavaEvade = getLavaEvade(position, enemy.position, threats, blinkCooldown);
|
|
63
|
+
if (lavaEvade) return lavaEvade.action === 'blink' ? blink(lavaEvade.x, lavaEvade.y) : move(lavaEvade.x, lavaEvade.y);
|
|
64
|
+
|
|
65
|
+
if (status === 'channeling' && 'shield' === 'shield')
|
|
52
66
|
{
|
|
53
67
|
const imminentThreat = threats.find(
|
|
54
68
|
(t) => t.willHit && t.ticksToImpact <= shieldHoldTicks,
|
|
55
69
|
);
|
|
56
|
-
if (imminentThreat)
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
if (imminentThreat)
|
|
71
|
+
{
|
|
72
|
+
const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle();
|
|
73
|
+
}
|
|
74
|
+
if (safeToCancel) return cancel();
|
|
75
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
if (
|
|
78
|
+
if (status === 'casting' && castingSpell === 'shield')
|
|
62
79
|
{
|
|
63
|
-
return
|
|
80
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
64
81
|
}
|
|
65
82
|
|
|
66
83
|
if (shouldStartShield)
|
|
67
84
|
{
|
|
68
|
-
if (
|
|
69
|
-
if (
|
|
85
|
+
if (status === 'casting') return cancel();
|
|
86
|
+
if (status === 'idle')
|
|
70
87
|
{
|
|
71
|
-
return
|
|
88
|
+
return shield();
|
|
72
89
|
}
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
if (
|
|
76
|
-
|
|
93
|
+
status === 'idle' &&
|
|
77
94
|
urgentThreat &&
|
|
78
|
-
urgentThreat.ticksToImpact < SHIELD_CAST_TIME + SHIELD_BUFFER &&
|
|
95
|
+
urgentThreat.ticksToImpact < RULES.SHIELD_CAST_TIME + SHIELD_BUFFER &&
|
|
79
96
|
urgentThreat.ticksToImpact >= 10 &&
|
|
80
|
-
|
|
97
|
+
blinkCooldown === 0
|
|
81
98
|
)
|
|
82
99
|
{
|
|
83
|
-
return
|
|
84
|
-
move: {x: 0, y: 0},
|
|
85
|
-
startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
|
|
86
|
-
};
|
|
100
|
+
return blink(getBlinkToCenter(position).x, getBlinkToCenter(position).y);
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
// Two-tier offense: big missile when safe, fast missile otherwise.
|
|
90
104
|
// Same speed/turnRate/duration → no warmup switching penalty.
|
|
91
|
-
if (
|
|
105
|
+
if (status === 'idle' && enemy)
|
|
92
106
|
{
|
|
93
107
|
const FAST_MISSILE = {damage: 12, speed: 5, turnRate: 1, duration: 180};
|
|
94
108
|
const BIG_MISSILE = {damage: 20, speed: 5, turnRate: 1, duration: 180};
|
|
95
109
|
|
|
96
110
|
const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
|
|
97
111
|
|
|
98
|
-
const bigCast = getMissileCastTime(BIG_MISSILE,
|
|
99
|
-
const bigWindow = bigCast + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
112
|
+
const bigCast = getMissileCastTime(BIG_MISSILE, lastMissileConfig);
|
|
113
|
+
const bigWindow = bigCast + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
100
114
|
if (!urgentThreat || nextThreatTime > bigWindow)
|
|
101
115
|
{
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
turnToward: worldState.enemies[0]?.position,
|
|
109
|
-
}),
|
|
110
|
-
direction: angleTo(state.position, enemy.position),
|
|
111
|
-
},
|
|
112
|
-
};
|
|
116
|
+
return missile(BIG_MISSILE, () =>
|
|
117
|
+
{
|
|
118
|
+
const ctx = getMissileContext();
|
|
119
|
+
const e = ctx.worldState.enemies[0];
|
|
120
|
+
return e ? turnToward(e.position.x, e.position.y) : flyStraight();
|
|
121
|
+
}, angleTo(position, enemy.position)).move(keepOffWalls(position).x, keepOffWalls(position).y);
|
|
113
122
|
}
|
|
114
123
|
|
|
115
|
-
const fastCast = getMissileCastTime(FAST_MISSILE,
|
|
116
|
-
const fastWindow = fastCast + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
124
|
+
const fastCast = getMissileCastTime(FAST_MISSILE, lastMissileConfig);
|
|
125
|
+
const fastWindow = fastCast + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
117
126
|
if (!urgentThreat || nextThreatTime > fastWindow)
|
|
118
127
|
{
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
turnToward: worldState.enemies[0]?.position,
|
|
126
|
-
}),
|
|
127
|
-
direction: angleTo(state.position, enemy.position),
|
|
128
|
-
},
|
|
129
|
-
};
|
|
128
|
+
return missile(FAST_MISSILE, () =>
|
|
129
|
+
{
|
|
130
|
+
const ctx = getMissileContext();
|
|
131
|
+
const e = ctx.worldState.enemies[0];
|
|
132
|
+
return e ? turnToward(e.position.x, e.position.y) : flyStraight();
|
|
133
|
+
}, angleTo(position, enemy.position)).move(keepOffWalls(position).x, keepOffWalls(position).y);
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
return
|
|
134
|
-
}
|
|
137
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
138
|
+
}
|