fable/val Public Public: anyone can read this; nobody can change it.

forked from benjivers/val

workbenchthis machine
Files c8ea83f25aa0be359368817293ebc3f7f1d27dd4
1
/**
2
 * The bot's aim (`engine/aim.ts`), once through a whole bot to prove the
3
 * reaction gate is actually wired to the trigger and the cone to the barrel.
4
 */
5
6
import { expect, test } from "bun:test";
7
import { acquire, crossRate, emptyTrack, REACTION_MAX, REACTION_MIN } from "./engine/aim.ts";
8
import { botThink } from "./bots.ts";
9
import { newMatch, RUN_SPEED } from "./match.ts";
10
import type { MatchState, Player } from "./state.ts";
11
12
const rolls = (...values: number[]) => {
13
  let at = 0;
14
  return () => values[at++ % values.length]!;
15
};
16
17
const facing = (state: MatchState): { bot: Player; prey: Player } => {
18
  const bot = state.players.find((p) => p.side === "def")!;
19
  const prey = state.players.find((p) => p.side === "atk")!;
20
  // Nose to nose in the open, with nothing between them.
21
  bot.x = 1200;
22
  bot.y = 640;
23
  prey.x = 1200 + 200;
24
  prey.y = 640;
25
  bot.angle = 0;
26
  bot.input.angle = 0;
27
  bot.weapon = "vandal";
28
  bot.ammo = 25;
29
  return { bot, prey };
30
};
31
32
const live = (): MatchState => {
33
  const state = newMatch(4242);
34
  state.phase = "live";
35
  state.clock = 100;
36
  return state;
37
};
38
39
test("a bot that has just seen somebody does not shoot for a reaction", () => {
40
  const state = live();
41
  const { bot } = facing(state);
42
  let firstFire = -1;
43
  for (let i = 0; i < 30 && firstFire < 0; i++) {
44
    botThink(state, bot, 1 / 30);
45
    state.now += 1 / 30;
46
    if (bot.input.fire) firstFire = i / 30;
47
  }
48
  expect(firstFire).toBeGreaterThanOrEqual(REACTION_MIN);
49
  expect(firstFire).toBeLessThanOrEqual(REACTION_MAX + 0.15);
50
});
51
52
test("a bot that has been tracking you is far tighter than one that just swung on", () => {
53
  const state = live();
54
  const { bot, prey } = facing(state);
55
  const truth = () => Math.atan2(prey.y - bot.y, prey.x - bot.x);
56
  const spread: number[] = [];
57
  for (let i = 0; i < 120; i++) {
58
    botThink(state, bot, 1 / 30);
59
    state.now += 1 / 30;
60
    bot.angle = bot.input.angle; // the match loop's job, done by hand
61
    spread.push(Math.abs(bot.angle - truth()));
62
  }
63
  const swing = Math.max(...spread.slice(0, 15));
64
  const held = Math.max(...spread.slice(80));
65
  expect(swing).toBeGreaterThan(held * 2);
66
  expect(held).toBeLessThan(0.03);
67
});
68
69
test("a strafing target keeps a settled bot honest", () => {
70
  const still = live();
71
  const a = facing(still);
72
  const strafing = live();
73
  const b = facing(strafing);
74
  let stillWorst = 0;
75
  let strafeWorst = 0;
76
  for (let i = 0; i < 180; i++) {
77
    botThink(still, a.bot, 1 / 30);
78
    botThink(strafing, b.bot, 1 / 30);
79
    still.now += 1 / 30;
80
    strafing.now += 1 / 30;
81
    a.bot.angle = a.bot.input.angle;
82
    b.bot.angle = b.bot.input.angle;
83
    // One stands in the open; the other runs across the sight line.
84
    b.prey.y = 640 + Math.sin(i / 8) * 90;
85
    if (i > 60) {
86
      stillWorst = Math.max(stillWorst, Math.abs(a.bot.angle - Math.atan2(a.prey.y - a.bot.y, a.prey.x - a.bot.x)));
87
      strafeWorst = Math.max(strafeWorst, Math.abs(b.bot.angle - Math.atan2(b.prey.y - b.bot.y, b.prey.x - b.bot.x)));
88
    }
89
  }
90
  expect(strafeWorst).toBeGreaterThan(stillWorst);
91
});
92
93
test("the aim is still deterministic in the state: one seed, one round", () => {
94
  const play = (): number[] => {
95
    const state = live();
96
    const { bot, prey } = facing(state);
97
    const out: number[] = [];
98
    for (let i = 0; i < 60; i++) {
99
      botThink(state, bot, 1 / 30);
100
      state.now += 1 / 30;
101
      bot.angle = bot.input.angle;
102
      prey.x = 1400 + i;
103
      out.push(bot.angle, bot.input.fire ? 1 : 0);
104
    }
105
    return out;
106
  };
107
  expect(play()).toEqual(play());
108
});
109
110
test("a full run of speed is what the crossing term is measured against", () => {
111
  expect(RUN_SPEED).toBeGreaterThan(0);
112
  const track = emptyTrack();
113
  acquire(track, "t", 0, { x: 400, y: 0 }, rolls(0, 0.5));
114
  const rate = crossRate(track, { x: 0, y: 0 }, { x: 400, y: RUN_SPEED / 30 }, 1 / 30);
115
  expect(rate / RUN_SPEED).toBeCloseTo(1, 3);
116
});
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts