@vibemancer/core 0.1.0 → 0.1.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.
Files changed (50) hide show
  1. package/README.md +28 -28
  2. package/dist/{chunk-L7Z7OFXD.js → chunk-OL7ETV6V.js} +3 -3
  3. package/dist/chunk-OL7ETV6V.js.map +1 -0
  4. package/dist/index-browser.d.ts +1 -1
  5. package/dist/index-browser.js +1 -1
  6. package/dist/index.js +1 -1
  7. package/package.json +79 -78
  8. package/src/bots/berserker/01_Stormchaser.ts +457 -457
  9. package/src/bots/berserker/02_Stormcaller.ts +417 -417
  10. package/src/bots/berserker/03_Stormforger.ts +481 -481
  11. package/src/bots/caster/01_Flamecaller.ts +286 -286
  12. package/src/bots/caster/02_Pyromancer.ts +350 -350
  13. package/src/bots/caster/03_Infernalist.ts +492 -492
  14. package/src/bots/defensive/01_Turtle.ts +151 -151
  15. package/src/bots/defensive/02_Sentinel.ts +134 -134
  16. package/src/bots/defensive/03_Golem.ts +357 -357
  17. package/src/bots/duelist/01_Battlemage.ts +433 -433
  18. package/src/bots/duelist/02_Warmage.ts +438 -438
  19. package/src/bots/duelist/03_Archmage.ts +588 -588
  20. package/src/bots/homing/01_Bonemancer.ts +67 -67
  21. package/src/bots/homing/02_Lich.ts +356 -356
  22. package/src/bots/homing/03_Archlich.ts +220 -220
  23. package/src/bots/kiter/01_Spellspinner.ts +398 -398
  24. package/src/bots/kiter/02_Spellweaver.ts +378 -378
  25. package/src/bots/kiter/03_Spellbinder.ts +448 -448
  26. package/src/bots/melee/01_Shadowblade.ts +270 -270
  27. package/src/bots/melee/02_Nightblade.ts +437 -437
  28. package/src/bots/melee/03_Voidblade.ts +582 -582
  29. package/src/bots/sniper/01_Spellshot.ts +385 -385
  30. package/src/bots/sniper/02_Spelltracer.ts +441 -441
  31. package/src/bots/sniper/03_Spellseeker.ts +546 -546
  32. package/src/bots/standalone/Critter.ts +89 -89
  33. package/src/bots/standalone/Doombringer.ts +91 -91
  34. package/src/bots/standalone/Hogger.ts +228 -228
  35. package/src/bots/standalone/Rookie.ts +50 -50
  36. package/src/bots/standalone/TargetDummy.ts +21 -21
  37. package/src/bots/test/cheater.ts +405 -405
  38. package/src/bots/test/crasher.ts +81 -81
  39. package/src/engine/hooks-runtime.ts +394 -394
  40. package/src/engine/manual-match.ts +289 -289
  41. package/src/engine/missile-templates.ts +155 -155
  42. package/src/engine/physics.ts +143 -143
  43. package/src/engine/simulation.ts +828 -828
  44. package/src/engine/spells.ts +128 -128
  45. package/src/engine-version.ts +1 -1
  46. package/src/index.ts +23 -23
  47. package/src/rules.ts +254 -254
  48. package/src/types.ts +193 -193
  49. package/src/utils/index.ts +6 -6
  50. package/dist/chunk-L7Z7OFXD.js.map +0 -1
@@ -1,228 +1,228 @@
1
- import {WizardFunction, MissileAIFunction} from '../../types.js';
2
- import {angleTo} from '../../utils/angles.js';
3
- import {distanceTo} from '../../utils/distance.js';
4
- import {ARENA_SIZE} from '../../rules.js';
5
- import {
6
- getThreats,
7
- getMostUrgentThreat,
8
- getDodgeDirectionForMovement,
9
- getBlinkToCenter,
10
- } from '../shared.js';
11
- const WALL_BUFFER = 60;
12
- const WALL_PUSH = 30; // How hard to push away from walls
13
-
14
- /**
15
- * Homing AI that sometimes forgets what it's doing.
16
- */
17
- const ChaoticHoming: MissileAIFunction = ({worldState, random}) =>
18
- {
19
- const enemy = worldState.enemies[0];
20
- // 80% chance to actually track, 20% chance to just fly straight
21
- if (random() < 0.8)
22
- {
23
- return {turnToward: enemy?.position};
24
- }
25
- return {};
26
- };
27
-
28
- /**
29
- * Bot: Hogger
30
- *
31
- * BEHAVIOR: The elite critter. Chaotic and unpredictable but genuinely dangerous.
32
- * Randomly varies missile configs each cast (damage 7-15, speed 3-8, random homing),
33
- * moves erratically but still somewhat toward/away from the enemy, shields when
34
- * in real danger, and blinks unpredictably. The randomness makes Hogger hard to
35
- * predict — you never know if the next missile will be a slow tracker or a fast
36
- * snipe. Unlike Critter's pure randomness, Hogger has enough combat awareness
37
- * to actually win fights.
38
- *
39
- * NAMING RATIONALE: In WoW, Hogger is the iconic level 11 elite gnoll in Elwynn
40
- * Forest who infamously kills unprepared lowbies. He's technically a basic mob
41
- * but hits way harder than expected. This bot is the Critter that learned to
42
- * fight — still chaotic, still a bit dumb, but capable of ending you if you
43
- * underestimate it. "Hogger" is one of WoW's most recognizable references and
44
- * perfectly captures "deceptively dangerous chaos."
45
- *
46
- * STANDALONE — no tier progression. There's only one Hogger.
47
- */
48
- export const Hogger: WizardFunction = ({state, random}) =>
49
- {
50
- const enemy = state.enemies[0];
51
- if (!enemy)
52
- {
53
- return {move: {x: 0, y: 0}};
54
- }
55
-
56
- const distance = distanceTo(state.position, enemy.position);
57
- const threats = getThreats(state);
58
- const urgentThreat = getMostUrgentThreat(threats);
59
-
60
- // Erratic movement: mostly toward/away from enemy with random strafe
61
- const deltaX = enemy.position.x - state.position.x;
62
- const deltaY = enemy.position.y - state.position.y;
63
- const normalizedDistance = distance > 0 ? distance : 1;
64
-
65
- // Dodge if there's a real threat
66
- const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
67
-
68
- let moveX: number;
69
- let moveY: number;
70
-
71
- if (dodgeDirection)
72
- {
73
- // Actually dodge (Hogger isn't stupid)
74
- moveX = dodgeDirection.x * 100;
75
- moveY = dodgeDirection.y * 100;
76
- }
77
- else
78
- {
79
- // Random strafe component
80
- const perpX = -deltaY / normalizedDistance;
81
- const perpY = deltaX / normalizedDistance;
82
- const strafeIntensity = random() * 80 - 40; // -40 to 40
83
-
84
- // Approach/retreat: prefer medium range (250-400), but sometimes just wander
85
- let approachIntensity = 0;
86
- if (distance > 400)
87
- {
88
- approachIntensity = 40 + random() * 30; // approach
89
- }
90
- else if (distance < 200)
91
- {
92
- approachIntensity = -(30 + random() * 30); // retreat
93
- }
94
- else
95
- {
96
- approachIntensity = random() * 60 - 30; // random walk
97
- }
98
-
99
- moveX =
100
- perpX * strafeIntensity +
101
- (deltaX / normalizedDistance) * approachIntensity;
102
- moveY =
103
- perpY * strafeIntensity +
104
- (deltaY / normalizedDistance) * approachIntensity;
105
-
106
- // Wall avoidance (Hogger doesn't like corners)
107
- if (state.position.x < WALL_BUFFER) moveX += WALL_PUSH;
108
- if (state.position.x > ARENA_SIZE - WALL_BUFFER) moveX -= WALL_PUSH;
109
- if (state.position.y < WALL_BUFFER) moveY += WALL_PUSH;
110
- if (state.position.y > ARENA_SIZE - WALL_BUFFER) moveY -= WALL_PUSH;
111
- }
112
-
113
- const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
114
- if (magnitude > 100)
115
- {
116
- moveX = (moveX / magnitude) * 100;
117
- moveY = (moveY / magnitude) * 100;
118
- }
119
-
120
- const move = {x: moveX, y: moveY};
121
-
122
- // === DEFENSE: Shield if in real danger ===
123
- if (state.state === 'channeling')
124
- {
125
- if (!urgentThreat || urgentThreat.ticksToImpact > 120)
126
- {
127
- return {move, cancel: true};
128
- }
129
- return {move: {x: 0, y: 0}};
130
- }
131
-
132
- if (
133
- state.state === 'idle' &&
134
- urgentThreat &&
135
- !urgentThreat.bestDodgeDirection
136
- )
137
- {
138
- if (urgentThreat.canBlockInTime && urgentThreat.ticksToStartShield <= 3)
139
- {
140
- return {
141
- move: {x: 0, y: 0},
142
- startCast: {spell: 'shield'},
143
- };
144
- }
145
- else if (!urgentThreat.canBlockInTime && state.blinkCooldown === 0)
146
- {
147
- return {
148
- move: {x: 0, y: 0},
149
- startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
150
- };
151
- }
152
- }
153
-
154
- // === OFFENSE: Random blink (sometimes offensive, sometimes random) ===
155
- if (state.state === 'idle' && state.blinkCooldown === 0 && random() < 0.03)
156
- {
157
- // 60% chance blink toward enemy, 40% chance random blink
158
- if (random() < 0.6 && distance > 300)
159
- {
160
- return {
161
- move: {x: 0, y: 0},
162
- startCast: {spell: 'blink', target: enemy.position},
163
- };
164
- }
165
- else
166
- {
167
- return {
168
- move: {x: 0, y: 0},
169
- startCast: {
170
- spell: 'blink',
171
- target: {
172
- x: 100 + random() * 600,
173
- y: 100 + random() * 600,
174
- },
175
- },
176
- };
177
- }
178
- }
179
-
180
- // === OFFENSE: Chaotic missiles ===
181
- if (state.state === 'idle' && distance < 500)
182
- {
183
- // Random missile config each time
184
- const roll = random();
185
- let damage: number;
186
- let speed: number;
187
- let turnRate: number;
188
- let duration: number;
189
-
190
- if (roll < 0.3)
191
- {
192
- // Fast snipe
193
- damage = 8 + Math.floor(random() * 5); // 8-12
194
- speed = 6 + random() * 2; // 6-8
195
- turnRate = 0;
196
- duration = 80 + Math.floor(random() * 40); // 80-120
197
- }
198
- else if (roll < 0.7)
199
- {
200
- // Homing tracker
201
- damage = 7 + Math.floor(random() * 5); // 7-11
202
- speed = 3 + random() * 2; // 3-5
203
- turnRate = 1 + random() * 2; // 1-3
204
- duration = 150 + Math.floor(random() * 100); // 150-250
205
- }
206
- else
207
- {
208
- // Big slow bomb
209
- damage = 12 + Math.floor(random() * 4); // 12-15
210
- speed = 2 + random() * 2; // 2-4
211
- turnRate = 0.5 + random() * 1.5; // 0.5-2
212
- duration = 200 + Math.floor(random() * 100); // 200-300
213
- }
214
-
215
- return {
216
- move,
217
- startCast: {
218
- spell: 'missile',
219
- config: {damage, speed, turnRate, duration},
220
- missileAI: ChaoticHoming,
221
- direction:
222
- angleTo(state.position, enemy.position) + (random() * 20 - 10), // ±10° aim wobble
223
- },
224
- };
225
- }
226
-
227
- return {move};
228
- };
1
+ import {WizardFunction, MissileAIFunction} from '../../types.js';
2
+ import {angleTo} from '../../utils/angles.js';
3
+ import {distanceTo} from '../../utils/distance.js';
4
+ import {ARENA_SIZE} from '../../rules.js';
5
+ import {
6
+ getThreats,
7
+ getMostUrgentThreat,
8
+ getDodgeDirectionForMovement,
9
+ getBlinkToCenter,
10
+ } from '../shared.js';
11
+ const WALL_BUFFER = 60;
12
+ const WALL_PUSH = 30; // How hard to push away from walls
13
+
14
+ /**
15
+ * Homing AI that sometimes forgets what it's doing.
16
+ */
17
+ const ChaoticHoming: MissileAIFunction = ({worldState, random}) =>
18
+ {
19
+ const enemy = worldState.enemies[0];
20
+ // 80% chance to actually track, 20% chance to just fly straight
21
+ if (random() < 0.8)
22
+ {
23
+ return {turnToward: enemy?.position};
24
+ }
25
+ return {};
26
+ };
27
+
28
+ /**
29
+ * Bot: Hogger
30
+ *
31
+ * BEHAVIOR: The elite critter. Chaotic and unpredictable but genuinely dangerous.
32
+ * Randomly varies missile configs each cast (damage 7-15, speed 3-8, random homing),
33
+ * moves erratically but still somewhat toward/away from the enemy, shields when
34
+ * in real danger, and blinks unpredictably. The randomness makes Hogger hard to
35
+ * predict — you never know if the next missile will be a slow tracker or a fast
36
+ * snipe. Unlike Critter's pure randomness, Hogger has enough combat awareness
37
+ * to actually win fights.
38
+ *
39
+ * NAMING RATIONALE: In WoW, Hogger is the iconic level 11 elite gnoll in Elwynn
40
+ * Forest who infamously kills unprepared lowbies. He's technically a basic mob
41
+ * but hits way harder than expected. This bot is the Critter that learned to
42
+ * fight — still chaotic, still a bit dumb, but capable of ending you if you
43
+ * underestimate it. "Hogger" is one of WoW's most recognizable references and
44
+ * perfectly captures "deceptively dangerous chaos."
45
+ *
46
+ * STANDALONE — no tier progression. There's only one Hogger.
47
+ */
48
+ export const Hogger: WizardFunction = ({state, random}) =>
49
+ {
50
+ const enemy = state.enemies[0];
51
+ if (!enemy)
52
+ {
53
+ return {move: {x: 0, y: 0}};
54
+ }
55
+
56
+ const distance = distanceTo(state.position, enemy.position);
57
+ const threats = getThreats(state);
58
+ const urgentThreat = getMostUrgentThreat(threats);
59
+
60
+ // Erratic movement: mostly toward/away from enemy with random strafe
61
+ const deltaX = enemy.position.x - state.position.x;
62
+ const deltaY = enemy.position.y - state.position.y;
63
+ const normalizedDistance = distance > 0 ? distance : 1;
64
+
65
+ // Dodge if there's a real threat
66
+ const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
67
+
68
+ let moveX: number;
69
+ let moveY: number;
70
+
71
+ if (dodgeDirection)
72
+ {
73
+ // Actually dodge (Hogger isn't stupid)
74
+ moveX = dodgeDirection.x * 100;
75
+ moveY = dodgeDirection.y * 100;
76
+ }
77
+ else
78
+ {
79
+ // Random strafe component
80
+ const perpX = -deltaY / normalizedDistance;
81
+ const perpY = deltaX / normalizedDistance;
82
+ const strafeIntensity = random() * 80 - 40; // -40 to 40
83
+
84
+ // Approach/retreat: prefer medium range (250-400), but sometimes just wander
85
+ let approachIntensity = 0;
86
+ if (distance > 400)
87
+ {
88
+ approachIntensity = 40 + random() * 30; // approach
89
+ }
90
+ else if (distance < 200)
91
+ {
92
+ approachIntensity = -(30 + random() * 30); // retreat
93
+ }
94
+ else
95
+ {
96
+ approachIntensity = random() * 60 - 30; // random walk
97
+ }
98
+
99
+ moveX =
100
+ perpX * strafeIntensity +
101
+ (deltaX / normalizedDistance) * approachIntensity;
102
+ moveY =
103
+ perpY * strafeIntensity +
104
+ (deltaY / normalizedDistance) * approachIntensity;
105
+
106
+ // Wall avoidance (Hogger doesn't like corners)
107
+ if (state.position.x < WALL_BUFFER) moveX += WALL_PUSH;
108
+ if (state.position.x > ARENA_SIZE - WALL_BUFFER) moveX -= WALL_PUSH;
109
+ if (state.position.y < WALL_BUFFER) moveY += WALL_PUSH;
110
+ if (state.position.y > ARENA_SIZE - WALL_BUFFER) moveY -= WALL_PUSH;
111
+ }
112
+
113
+ const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
114
+ if (magnitude > 100)
115
+ {
116
+ moveX = (moveX / magnitude) * 100;
117
+ moveY = (moveY / magnitude) * 100;
118
+ }
119
+
120
+ const move = {x: moveX, y: moveY};
121
+
122
+ // === DEFENSE: Shield if in real danger ===
123
+ if (state.state === 'channeling')
124
+ {
125
+ if (!urgentThreat || urgentThreat.ticksToImpact > 120)
126
+ {
127
+ return {move, cancel: true};
128
+ }
129
+ return {move: {x: 0, y: 0}};
130
+ }
131
+
132
+ if (
133
+ state.state === 'idle' &&
134
+ urgentThreat &&
135
+ !urgentThreat.bestDodgeDirection
136
+ )
137
+ {
138
+ if (urgentThreat.canBlockInTime && urgentThreat.ticksToStartShield <= 3)
139
+ {
140
+ return {
141
+ move: {x: 0, y: 0},
142
+ startCast: {spell: 'shield'},
143
+ };
144
+ }
145
+ else if (!urgentThreat.canBlockInTime && state.blinkCooldown === 0)
146
+ {
147
+ return {
148
+ move: {x: 0, y: 0},
149
+ startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
150
+ };
151
+ }
152
+ }
153
+
154
+ // === OFFENSE: Random blink (sometimes offensive, sometimes random) ===
155
+ if (state.state === 'idle' && state.blinkCooldown === 0 && random() < 0.03)
156
+ {
157
+ // 60% chance blink toward enemy, 40% chance random blink
158
+ if (random() < 0.6 && distance > 300)
159
+ {
160
+ return {
161
+ move: {x: 0, y: 0},
162
+ startCast: {spell: 'blink', target: enemy.position},
163
+ };
164
+ }
165
+ else
166
+ {
167
+ return {
168
+ move: {x: 0, y: 0},
169
+ startCast: {
170
+ spell: 'blink',
171
+ target: {
172
+ x: 100 + random() * 600,
173
+ y: 100 + random() * 600,
174
+ },
175
+ },
176
+ };
177
+ }
178
+ }
179
+
180
+ // === OFFENSE: Chaotic missiles ===
181
+ if (state.state === 'idle' && distance < 500)
182
+ {
183
+ // Random missile config each time
184
+ const roll = random();
185
+ let damage: number;
186
+ let speed: number;
187
+ let turnRate: number;
188
+ let duration: number;
189
+
190
+ if (roll < 0.3)
191
+ {
192
+ // Fast snipe
193
+ damage = 8 + Math.floor(random() * 5); // 8-12
194
+ speed = 6 + random() * 2; // 6-8
195
+ turnRate = 0;
196
+ duration = 80 + Math.floor(random() * 40); // 80-120
197
+ }
198
+ else if (roll < 0.7)
199
+ {
200
+ // Homing tracker
201
+ damage = 7 + Math.floor(random() * 5); // 7-11
202
+ speed = 3 + random() * 2; // 3-5
203
+ turnRate = 1 + random() * 2; // 1-3
204
+ duration = 150 + Math.floor(random() * 100); // 150-250
205
+ }
206
+ else
207
+ {
208
+ // Big slow bomb
209
+ damage = 12 + Math.floor(random() * 4); // 12-15
210
+ speed = 2 + random() * 2; // 2-4
211
+ turnRate = 0.5 + random() * 1.5; // 0.5-2
212
+ duration = 200 + Math.floor(random() * 100); // 200-300
213
+ }
214
+
215
+ return {
216
+ move,
217
+ startCast: {
218
+ spell: 'missile',
219
+ config: {damage, speed, turnRate, duration},
220
+ missileAI: ChaoticHoming,
221
+ direction:
222
+ angleTo(state.position, enemy.position) + (random() * 20 - 10), // ±10° aim wobble
223
+ },
224
+ };
225
+ }
226
+
227
+ return {move};
228
+ };
@@ -1,50 +1,50 @@
1
- import {WizardFunction} from '../../types.js';
2
- import {angleTo} from '../../utils/angles.js';
3
-
4
- /**
5
- * Bot: Rookie
6
- *
7
- * BEHAVIOR: Stands perfectly still and fires straight (non-homing) missiles at
8
- * the enemy. No movement, no dodging, no shielding. Knows one spell and uses
9
- * it on cooldown. The wizard equivalent of an FPS player who stands in the open
10
- * and holds left-click.
11
- *
12
- * NAMING RATIONALE: "Rookie" is the universal term for a first-timer who barely
13
- * knows what they're doing. This bot is a day-one player who learned how to cast
14
- * missile and nothing else. No movement, no defense, just raw "I press the button."
15
- * We considered "Noob" (more accurate) but Rookie is less abrasive while conveying
16
- * the same thing — a beginner who doesn't know any better.
17
- *
18
- * STANDALONE — no tier progression. Rookies either learn to play a real class
19
- * or quit. This bot represents the rock-bottom of "at least it shoots."
20
- */
21
- export const Rookie: WizardFunction = ({state}) =>
22
- {
23
- const enemy = state.enemies[0];
24
- if (!enemy)
25
- {
26
- return {move: {x: 0, y: 0}};
27
- }
28
-
29
- if (state.state === 'idle')
30
- {
31
- return {
32
- move: {x: 0, y: 0},
33
- startCast: {
34
- spell: 'missile',
35
- config: {
36
- damage: 10,
37
- speed: 5,
38
- turnRate: 0,
39
- duration: 200,
40
- },
41
- missileAI: () => ({}),
42
- direction: angleTo(state.position, enemy.position),
43
- },
44
- };
45
- }
46
-
47
- return {
48
- move: {x: 0, y: 0},
49
- };
50
- };
1
+ import {WizardFunction} from '../../types.js';
2
+ import {angleTo} from '../../utils/angles.js';
3
+
4
+ /**
5
+ * Bot: Rookie
6
+ *
7
+ * BEHAVIOR: Stands perfectly still and fires straight (non-homing) missiles at
8
+ * the enemy. No movement, no dodging, no shielding. Knows one spell and uses
9
+ * it on cooldown. The wizard equivalent of an FPS player who stands in the open
10
+ * and holds left-click.
11
+ *
12
+ * NAMING RATIONALE: "Rookie" is the universal term for a first-timer who barely
13
+ * knows what they're doing. This bot is a day-one player who learned how to cast
14
+ * missile and nothing else. No movement, no defense, just raw "I press the button."
15
+ * We considered "Noob" (more accurate) but Rookie is less abrasive while conveying
16
+ * the same thing — a beginner who doesn't know any better.
17
+ *
18
+ * STANDALONE — no tier progression. Rookies either learn to play a real class
19
+ * or quit. This bot represents the rock-bottom of "at least it shoots."
20
+ */
21
+ export const Rookie: WizardFunction = ({state}) =>
22
+ {
23
+ const enemy = state.enemies[0];
24
+ if (!enemy)
25
+ {
26
+ return {move: {x: 0, y: 0}};
27
+ }
28
+
29
+ if (state.state === 'idle')
30
+ {
31
+ return {
32
+ move: {x: 0, y: 0},
33
+ startCast: {
34
+ spell: 'missile',
35
+ config: {
36
+ damage: 10,
37
+ speed: 5,
38
+ turnRate: 0,
39
+ duration: 200,
40
+ },
41
+ missileAI: () => ({}),
42
+ direction: angleTo(state.position, enemy.position),
43
+ },
44
+ };
45
+ }
46
+
47
+ return {
48
+ move: {x: 0, y: 0},
49
+ };
50
+ };
@@ -1,21 +1,21 @@
1
- import {WizardFunction} from '../../types.js';
2
-
3
- /**
4
- * Bot: TargetDummy
5
- *
6
- * BEHAVIOR: Does absolutely nothing. No movement, no spells, no AI.
7
- *
8
- * NAMING RATIONALE: "Target Dummy" is universal MMO player vocabulary for the
9
- * practice objects found in capital cities. Every WoW/FFXIV player has beaten
10
- * on a target dummy to test DPS rotations. That's exactly what this bot is —
11
- * a punching bag for testing missile mechanics and baseline damage output.
12
- * Nobody calls them "training dummies"; the player term is always "target dummy."
13
- *
14
- * STANDALONE — no tier progression. It's a test fixture, not a combatant.
15
- */
16
- export const TargetDummy: WizardFunction = () =>
17
- {
18
- return {
19
- move: {x: 0, y: 0},
20
- };
21
- };
1
+ import {WizardFunction} from '../../types.js';
2
+
3
+ /**
4
+ * Bot: TargetDummy
5
+ *
6
+ * BEHAVIOR: Does absolutely nothing. No movement, no spells, no AI.
7
+ *
8
+ * NAMING RATIONALE: "Target Dummy" is universal MMO player vocabulary for the
9
+ * practice objects found in capital cities. Every WoW/FFXIV player has beaten
10
+ * on a target dummy to test DPS rotations. That's exactly what this bot is —
11
+ * a punching bag for testing missile mechanics and baseline damage output.
12
+ * Nobody calls them "training dummies"; the player term is always "target dummy."
13
+ *
14
+ * STANDALONE — no tier progression. It's a test fixture, not a combatant.
15
+ */
16
+ export const TargetDummy: WizardFunction = (_props) =>
17
+ {
18
+ return {
19
+ move: {x: 0, y: 0},
20
+ };
21
+ };