@v12-engine/engine 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/dist/abilities/buffs.d.ts +83 -0
- package/dist/abilities/castTold.d.ts +12 -0
- package/dist/abilities/charges.d.ts +28 -0
- package/dist/abilities/dispel.d.ts +9 -0
- package/dist/abilities/effectFields.d.ts +10 -0
- package/dist/abilities/effects/buff.d.ts +8 -0
- package/dist/abilities/effects/core.d.ts +1 -0
- package/dist/abilities/effects/damage.d.ts +1 -0
- package/dist/abilities/effects/heal.d.ts +2 -0
- package/dist/abilities/effects/missile.d.ts +1 -0
- package/dist/abilities/effects/moves.d.ts +11 -0
- package/dist/abilities/effects/passives.d.ts +15 -0
- package/dist/abilities/effects/sight.d.ts +4 -0
- package/dist/abilities/effects/spawn.d.ts +2 -0
- package/dist/abilities/effects/timed.d.ts +4 -0
- package/dist/abilities/effects/trade.d.ts +1 -0
- package/dist/abilities/effects.d.ts +122 -0
- package/dist/abilities/land.d.ts +21 -0
- package/dist/abilities/scaled.d.ts +21 -0
- package/dist/abilities/text.d.ts +113 -0
- package/dist/ai/area.d.ts +53 -0
- package/dist/ai/brain.d.ts +528 -0
- package/dist/ai/budget.d.ts +67 -0
- package/dist/ai/checks.d.ts +115 -0
- package/dist/ai/computer.d.ts +166 -0
- package/dist/ai/defence.d.ts +108 -0
- package/dist/ai/define.d.ts +21 -0
- package/dist/ai/economy.d.ts +129 -0
- package/dist/ai/geometry.d.ts +35 -0
- package/dist/ai/goals.d.ts +360 -0
- package/dist/ai/index.d.ts +38 -0
- package/dist/ai/knowledge.d.ts +418 -0
- package/dist/ai/lanes.d.ts +232 -0
- package/dist/ai/melee/build.d.ts +61 -0
- package/dist/ai/melee/goals.d.ts +299 -0
- package/dist/ai/melee/heroes.d.ts +27 -0
- package/dist/ai/melee/index.d.ts +14 -0
- package/dist/ai/melee/production.d.ts +8 -0
- package/dist/ai/melee/surrender.d.ts +24 -0
- package/dist/ai/micro.d.ts +278 -0
- package/dist/ai/mission.d.ts +195 -0
- package/dist/ai/missionRules.d.ts +18 -0
- package/dist/ai/module.d.ts +116 -0
- package/dist/ai/options.d.ts +61 -0
- package/dist/ai/plans/build.d.ts +280 -0
- package/dist/ai/plans/placement.d.ts +82 -0
- package/dist/ai/plans/plans.d.ts +105 -0
- package/dist/ai/plans/production.d.ts +210 -0
- package/dist/ai/plans/purse.d.ts +73 -0
- package/dist/ai/plans/tech.d.ts +37 -0
- package/dist/ai/profiles.d.ts +204 -0
- package/dist/ai/reach.d.ts +4 -0
- package/dist/ai/report.d.ts +203 -0
- package/dist/ai/roles.d.ts +133 -0
- package/dist/ai/rules.d.ts +288 -0
- package/dist/ai/scouting.d.ts +98 -0
- package/dist/ai/settings.d.ts +73 -0
- package/dist/ai/squads.d.ts +429 -0
- package/dist/ai/strength.d.ts +167 -0
- package/dist/ai/task.d.ts +118 -0
- package/dist/ai/tasks/attack.d.ts +7 -0
- package/dist/ai/tasks/capture.d.ts +21 -0
- package/dist/ai/tasks/creep.d.ts +16 -0
- package/dist/ai/tasks/defend.d.ts +29 -0
- package/dist/ai/tasks/drop.d.ts +34 -0
- package/dist/ai/tasks/escort.d.ts +16 -0
- package/dist/ai/tasks/gather.d.ts +23 -0
- package/dist/ai/tasks/harass.d.ts +7 -0
- package/dist/ai/tasks/hold.d.ts +20 -0
- package/dist/ai/tasks/index.d.ts +32 -0
- package/dist/ai/tasks/move.d.ts +19 -0
- package/dist/ai/tasks/patrol.d.ts +8 -0
- package/dist/ai/tasks/retreat.d.ts +20 -0
- package/dist/ai/tasks/scout.d.ts +29 -0
- package/dist/ai/tasks/shared.d.ts +74 -0
- package/dist/ai/team.d.ts +155 -0
- package/dist/ai/threat.d.ts +91 -0
- package/dist/ai/view.d.ts +366 -0
- package/dist/ai/waves.d.ts +126 -0
- package/dist/ai.js +6239 -0
- package/dist/ai.js.map +1 -0
- package/dist/aiTesting.d.ts +179 -0
- package/dist/areas.d.ts +149 -0
- package/dist/attacks.d.ts +66 -0
- package/dist/attributes.d.ts +66 -0
- package/dist/brain-BvVdUhX8.js +7659 -0
- package/dist/brain-BvVdUhX8.js.map +1 -0
- package/dist/builds-C6LfgvEU.js +460 -0
- package/dist/builds-C6LfgvEU.js.map +1 -0
- package/dist/bytes.d.ts +6 -0
- package/dist/camps.d.ts +95 -0
- package/dist/capture.d.ts +131 -0
- package/dist/cellLayers.d.ts +75 -0
- package/dist/cinematics.d.ts +194 -0
- package/dist/clock.d.ts +32 -0
- package/dist/commands.d.ts +235 -0
- package/dist/components.d.ts +184 -0
- package/dist/computers.d.ts +108 -0
- package/dist/concealment.d.ts +61 -0
- package/dist/coreComponents-syuh2fPG.js +5149 -0
- package/dist/coreComponents-syuh2fPG.js.map +1 -0
- package/dist/coreComponents.d.ts +1 -0
- package/dist/dashing.d.ts +8 -0
- package/dist/debug.d.ts +36 -0
- package/dist/debug.js +3 -0
- package/dist/debugCommands-CzjrSm66.js +14739 -0
- package/dist/debugCommands-CzjrSm66.js.map +1 -0
- package/dist/debugCommands.d.ts +213 -0
- package/dist/debugging.d.ts +10 -0
- package/dist/definitions.d.ts +43 -0
- package/dist/defs.d.ts +1162 -0
- package/dist/denying.d.ts +81 -0
- package/dist/destructibles.d.ts +27 -0
- package/dist/determinism-BCymQwiE.js +518 -0
- package/dist/determinism-BCymQwiE.js.map +1 -0
- package/dist/determinism.d.ts +121 -0
- package/dist/dialog.d.ts +94 -0
- package/dist/effectRefs.d.ts +16 -0
- package/dist/effects.d.ts +80 -0
- package/dist/endings.d.ts +22 -0
- package/dist/engagement.d.ts +108 -0
- package/dist/engineEvents.d.ts +710 -0
- package/dist/entity.d.ts +409 -0
- package/dist/entityIndex.d.ts +50 -0
- package/dist/errors.d.ts +85 -0
- package/dist/events.d.ts +97 -0
- package/dist/exactMath.d.ts +63 -0
- package/dist/features.d.ts +10 -0
- package/dist/fields.d.ts +114 -0
- package/dist/fixture.d.ts +17 -0
- package/dist/fog.d.ts +110 -0
- package/dist/formation.d.ts +111 -0
- package/dist/friendlyFire.d.ts +26 -0
- package/dist/game-DiJEN-Ks.js +5800 -0
- package/dist/game-DiJEN-Ks.js.map +1 -0
- package/dist/game.d.ts +501 -0
- package/dist/gameData.d.ts +325 -0
- package/dist/gameFiles.d.ts +26 -0
- package/dist/gates.d.ts +28 -0
- package/dist/generate.d.ts +403 -0
- package/dist/generateWorker.d.ts +39 -0
- package/dist/grid.d.ts +53 -0
- package/dist/ground.d.ts +127 -0
- package/dist/groundSpeed.d.ts +22 -0
- package/dist/hero.d.ts +177 -0
- package/dist/home.d.ts +14 -0
- package/dist/hooks.d.ts +780 -0
- package/dist/hotReload.d.ts +76 -0
- package/dist/ids.d.ts +26 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.js +4701 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect-D9dWHmZ0.js +491 -0
- package/dist/inspect-D9dWHmZ0.js.map +1 -0
- package/dist/inspect.d.ts +176 -0
- package/dist/inventory.d.ts +18 -0
- package/dist/items.d.ts +91 -0
- package/dist/lanes.d.ts +213 -0
- package/dist/life.d.ts +53 -0
- package/dist/local-Dkovq42k.js +1379 -0
- package/dist/local-Dkovq42k.js.map +1 -0
- package/dist/log.d.ts +140 -0
- package/dist/manaBurn.d.ts +39 -0
- package/dist/map/file.d.ts +114 -0
- package/dist/map/images.d.ts +18 -0
- package/dist/map/look.d.ts +47 -0
- package/dist/map/pathProfile.d.ts +13 -0
- package/dist/map/resize.d.ts +54 -0
- package/dist/map/schema.d.ts +10 -0
- package/dist/map/terrain.d.ts +32 -0
- package/dist/map/types.d.ts +613 -0
- package/dist/map.d.ts +7 -0
- package/dist/mapCheck/cells.d.ts +19 -0
- package/dist/mapCheck/rule.d.ts +30 -0
- package/dist/mapCheck/rules/names.d.ts +3 -0
- package/dist/mapCheck/rules/objects.d.ts +3 -0
- package/dist/mapCheck/rules/paths.d.ts +3 -0
- package/dist/mapCheck/rules/players.d.ts +3 -0
- package/dist/mapCheck/rules/races.d.ts +3 -0
- package/dist/mapCheck/rules/random.d.ts +3 -0
- package/dist/mapCheck/rules/regions.d.ts +3 -0
- package/dist/mapCheck/rules/targets.d.ts +3 -0
- package/dist/mapCheck/rules/walking.d.ts +3 -0
- package/dist/mapCheck.d.ts +23 -0
- package/dist/mapFields.d.ts +364 -0
- package/dist/mapFile.d.ts +23 -0
- package/dist/mapMeasure.d.ts +95 -0
- package/dist/mapMerge.d.ts +19 -0
- package/dist/mapObjectives.d.ts +82 -0
- package/dist/mapObjects.d.ts +1 -0
- package/dist/math.d.ts +44 -0
- package/dist/messages.d.ts +150 -0
- package/dist/missionRules-C-bGxm0Q.js +523 -0
- package/dist/missionRules-C-bGxm0Q.js.map +1 -0
- package/dist/missionRules.d.ts +179 -0
- package/dist/mods.d.ts +146 -0
- package/dist/names.d.ts +93 -0
- package/dist/navigation/agentParams.d.ts +4 -0
- package/dist/navigation/air.d.ts +89 -0
- package/dist/navigation/builds.d.ts +140 -0
- package/dist/navigation/groundAreas.d.ts +23 -0
- package/dist/navigation/navigation.d.ts +127 -0
- package/dist/navigation/recast.d.ts +214 -0
- package/dist/navigation/sidePaths.d.ts +69 -0
- package/dist/navigation/sized.d.ts +1 -0
- package/dist/navigation-worker.js +11 -0
- package/dist/navigation-worker.js.map +1 -0
- package/dist/nearest.d.ts +8 -0
- package/dist/noise.d.ts +12 -0
- package/dist/objectives.d.ts +93 -0
- package/dist/orders/basic.d.ts +96 -0
- package/dist/orders/build.d.ts +91 -0
- package/dist/orders/cast.d.ts +105 -0
- package/dist/orders/facts.d.ts +14 -0
- package/dist/orders/harvest.d.ts +94 -0
- package/dist/orders/idle.d.ts +7 -0
- package/dist/orders/items.d.ts +14 -0
- package/dist/orders/queue.d.ts +24 -0
- package/dist/pack.schema.json +6621 -0
- package/dist/packs/director/index.d.ts +81 -0
- package/dist/packs/heroes/campaign.d.ts +137 -0
- package/dist/packs/heroes/carry.d.ts +59 -0
- package/dist/packs/heroes/commands.d.ts +6 -0
- package/dist/packs/heroes/hire.d.ts +9 -0
- package/dist/packs/heroes/index.d.ts +12 -0
- package/dist/packs/heroes/making.d.ts +16 -0
- package/dist/packs/heroes/respawn.d.ts +42 -0
- package/dist/packs/moba/index.d.ts +34 -0
- package/dist/packs/pack.d.ts +84 -0
- package/dist/packs-g5thvmGn.js +3199 -0
- package/dist/packs-g5thvmGn.js.map +1 -0
- package/dist/packs.d.ts +84 -0
- package/dist/paths.d.ts +51 -0
- package/dist/plain.d.ts +17 -0
- package/dist/player.d.ts +117 -0
- package/dist/portals.d.ts +66 -0
- package/dist/prefabs.d.ts +139 -0
- package/dist/presetBuildings.d.ts +52 -0
- package/dist/presetGround.d.ts +22 -0
- package/dist/profiles-B3NS9jwk.js +335 -0
- package/dist/profiles-B3NS9jwk.js.map +1 -0
- package/dist/races.d.ts +136 -0
- package/dist/randomGroups.d.ts +2 -0
- package/dist/registry.d.ts +464 -0
- package/dist/remains.d.ts +119 -0
- package/dist/replication.d.ts +103 -0
- package/dist/rng.d.ts +17 -0
- package/dist/rules.d.ts +395 -0
- package/dist/rulesCheck.d.ts +37 -0
- package/dist/runes.d.ts +41 -0
- package/dist/save.d.ts +277 -0
- package/dist/scenario.d.ts +56 -0
- package/dist/scheduler.d.ts +67 -0
- package/dist/score.d.ts +306 -0
- package/dist/sequences.d.ts +132 -0
- package/dist/server/doorman.d.ts +21 -0
- package/dist/server/gameServer.d.ts +346 -0
- package/dist/server/index.d.ts +24 -0
- package/dist/server/lobby.d.ts +1 -0
- package/dist/server/log.d.ts +13 -0
- package/dist/server/match.d.ts +24 -0
- package/dist/server/mode.d.ts +168 -0
- package/dist/server/navigation-worker.d.ts +1 -0
- package/dist/server/navigation.d.ts +11 -0
- package/dist/server/ops.d.ts +78 -0
- package/dist/server/packs.d.ts +15 -0
- package/dist/server/ratings.d.ts +62 -0
- package/dist/server/relay.d.ts +34 -0
- package/dist/server/rooms.d.ts +162 -0
- package/dist/server/seat.d.ts +2 -0
- package/dist/server/seatSettings.d.ts +15 -0
- package/dist/server/secret.d.ts +5 -0
- package/dist/server/start.d.ts +54 -0
- package/dist/server/websocket.d.ts +74 -0
- package/dist/server.js +2861 -0
- package/dist/server.js.map +1 -0
- package/dist/session/can.d.ts +20 -0
- package/dist/session/commandReplay.d.ts +92 -0
- package/dist/session/debug.d.ts +65 -0
- package/dist/session/delay.d.ts +15 -0
- package/dist/session/delta.d.ts +98 -0
- package/dist/session/entityState.d.ts +1 -0
- package/dist/session/local.d.ts +214 -0
- package/dist/session/memory.d.ts +27 -0
- package/dist/session/protocol.d.ts +1155 -0
- package/dist/session/remote.d.ts +530 -0
- package/dist/session/replay.d.ts +114 -0
- package/dist/session/seed.d.ts +7 -0
- package/dist/session/share.d.ts +78 -0
- package/dist/session/socket.d.ts +8 -0
- package/dist/session/wire.d.ts +153 -0
- package/dist/session/worker.d.ts +227 -0
- package/dist/shared.d.ts +171 -0
- package/dist/sides.d.ts +75 -0
- package/dist/sight.d.ts +27 -0
- package/dist/simulate-oXBiw1Ff.js +167 -0
- package/dist/simulate-oXBiw1Ff.js.map +1 -0
- package/dist/simulate-worker.d.ts +1 -0
- package/dist/simulate-worker.js +28 -0
- package/dist/simulate-worker.js.map +1 -0
- package/dist/simulate.d.ts +154 -0
- package/dist/smart.d.ts +9 -0
- package/dist/spatial.d.ts +62 -0
- package/dist/spawning.d.ts +33 -0
- package/dist/stances.d.ts +76 -0
- package/dist/start.d.ts +71 -0
- package/dist/stats.d.ts +122 -0
- package/dist/summons.d.ts +63 -0
- package/dist/systems/abilities.d.ts +3 -0
- package/dist/systems/combat.d.ts +71 -0
- package/dist/systems/combatTold.d.ts +14 -0
- package/dist/systems/construction.d.ts +34 -0
- package/dist/systems/economy.d.ts +21 -0
- package/dist/systems/fog.d.ts +19 -0
- package/dist/systems/life.d.ts +3 -0
- package/dist/systems/movement.d.ts +105 -0
- package/dist/systems/orders.d.ts +7 -0
- package/dist/systems/production.d.ts +49 -0
- package/dist/systems/productionQueue.d.ts +40 -0
- package/dist/systems/victory.d.ts +14 -0
- package/dist/talk.d.ts +65 -0
- package/dist/tech.d.ts +62 -0
- package/dist/techTree.d.ts +38 -0
- package/dist/terrain.d.ts +173 -0
- package/dist/terrainSave.d.ts +29 -0
- package/dist/testing.d.ts +58 -0
- package/dist/testing.js +822 -0
- package/dist/testing.js.map +1 -0
- package/dist/tiles.d.ts +34 -0
- package/dist/timers.d.ts +146 -0
- package/dist/tomes.d.ts +48 -0
- package/dist/trade.d.ts +28 -0
- package/dist/transport.d.ts +76 -0
- package/dist/tunables.d.ts +45 -0
- package/dist/upkeep.d.ts +16 -0
- package/dist/veterancy.d.ts +80 -0
- package/dist/walls.d.ts +86 -0
- package/dist/world.d.ts +1118 -0
- package/package.json +80 -5
- package/README.md +0 -3
package/dist/testing.js
ADDED
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
import { Et as Terrain, Qt as createMap, Ud as dist } from "./debugCommands-CzjrSm66.js";
|
|
2
|
+
import { i as createWorld, o as loadWorld } from "./game-DiJEN-Ks.js";
|
|
3
|
+
import { ct as saveWorld, it as encodeSave, rt as decodeSave } from "./coreComponents-syuh2fPG.js";
|
|
4
|
+
import { a as worldHash, i as worldFingerprint, n as firstDifference, o as worldState, s as withHeroes, t as checkDeterminism } from "./determinism-BCymQwiE.js";
|
|
5
|
+
import { t as LocalSession } from "./local-Dkovq42k.js";
|
|
6
|
+
import { S as checkBrain, t as Brain, wt as explainGoal } from "./brain-BvVdUhX8.js";
|
|
7
|
+
import { i as winRates, n as playGame, r as simulate, t as defineSimulation } from "./simulate-oXBiw1Ff.js";
|
|
8
|
+
//#region src/fixture.ts
|
|
9
|
+
var worker = {
|
|
10
|
+
id: "worker",
|
|
11
|
+
name: "Worker",
|
|
12
|
+
kind: "unit",
|
|
13
|
+
radius: .4,
|
|
14
|
+
stats: {
|
|
15
|
+
health: 100,
|
|
16
|
+
attackDamage: 5,
|
|
17
|
+
attackSpeed: 1,
|
|
18
|
+
attackRange: .5,
|
|
19
|
+
moveSpeed: 4
|
|
20
|
+
},
|
|
21
|
+
sight: 8,
|
|
22
|
+
attacks: [{
|
|
23
|
+
damagePoint: .3,
|
|
24
|
+
acquisitionRange: 0,
|
|
25
|
+
targets: ["unit", "building"]
|
|
26
|
+
}],
|
|
27
|
+
cost: { gold: 50 },
|
|
28
|
+
supply: 1,
|
|
29
|
+
buildTime: 10,
|
|
30
|
+
harvester: {
|
|
31
|
+
capacity: 10,
|
|
32
|
+
gatherTime: 1
|
|
33
|
+
},
|
|
34
|
+
builds: [
|
|
35
|
+
"hall",
|
|
36
|
+
"supply",
|
|
37
|
+
"barracks",
|
|
38
|
+
"tower",
|
|
39
|
+
"altar"
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
var soldier = {
|
|
43
|
+
id: "soldier",
|
|
44
|
+
name: "Soldier",
|
|
45
|
+
kind: "unit",
|
|
46
|
+
radius: .45,
|
|
47
|
+
stats: {
|
|
48
|
+
health: 200,
|
|
49
|
+
attackDamage: 10,
|
|
50
|
+
attackSpeed: 1,
|
|
51
|
+
attackRange: .5,
|
|
52
|
+
armor: 10,
|
|
53
|
+
moveSpeed: 4
|
|
54
|
+
},
|
|
55
|
+
sight: 10,
|
|
56
|
+
attacks: [{
|
|
57
|
+
damagePoint: .3,
|
|
58
|
+
acquisitionRange: 8
|
|
59
|
+
}],
|
|
60
|
+
cost: { gold: 100 },
|
|
61
|
+
supply: 2,
|
|
62
|
+
buildTime: 10,
|
|
63
|
+
abilities: ["guard"]
|
|
64
|
+
};
|
|
65
|
+
var archer = {
|
|
66
|
+
id: "archer",
|
|
67
|
+
name: "Archer",
|
|
68
|
+
kind: "unit",
|
|
69
|
+
radius: .45,
|
|
70
|
+
stats: {
|
|
71
|
+
health: 100,
|
|
72
|
+
attackDamage: 10,
|
|
73
|
+
attackSpeed: 1,
|
|
74
|
+
attackRange: 6,
|
|
75
|
+
moveSpeed: 4
|
|
76
|
+
},
|
|
77
|
+
sight: 10,
|
|
78
|
+
attacks: [{
|
|
79
|
+
damagePoint: .3,
|
|
80
|
+
acquisitionRange: 8,
|
|
81
|
+
projectileSpeed: 20,
|
|
82
|
+
targets: ["ground", "air"]
|
|
83
|
+
}],
|
|
84
|
+
cost: {
|
|
85
|
+
gold: 100,
|
|
86
|
+
lumber: 20
|
|
87
|
+
},
|
|
88
|
+
supply: 2,
|
|
89
|
+
buildTime: 10
|
|
90
|
+
};
|
|
91
|
+
var hero = {
|
|
92
|
+
id: "hero",
|
|
93
|
+
name: "Hero",
|
|
94
|
+
kind: "unit",
|
|
95
|
+
radius: .5,
|
|
96
|
+
stats: {
|
|
97
|
+
health: 300,
|
|
98
|
+
mana: 100,
|
|
99
|
+
manaRegen: 1,
|
|
100
|
+
attackDamage: 20,
|
|
101
|
+
attackSpeed: 1,
|
|
102
|
+
attackRange: .5,
|
|
103
|
+
armor: 10,
|
|
104
|
+
moveSpeed: 4
|
|
105
|
+
},
|
|
106
|
+
sight: 12,
|
|
107
|
+
attacks: [{
|
|
108
|
+
damagePoint: .3,
|
|
109
|
+
acquisitionRange: 8
|
|
110
|
+
}],
|
|
111
|
+
hero: {
|
|
112
|
+
growth: { stats: {
|
|
113
|
+
health: 20,
|
|
114
|
+
mana: 10,
|
|
115
|
+
attackDamage: 2
|
|
116
|
+
} },
|
|
117
|
+
learns: ["heal", "guard"]
|
|
118
|
+
},
|
|
119
|
+
inventory: 6,
|
|
120
|
+
cost: {
|
|
121
|
+
gold: 400,
|
|
122
|
+
lumber: 100
|
|
123
|
+
},
|
|
124
|
+
supply: 5,
|
|
125
|
+
buildTime: 40
|
|
126
|
+
};
|
|
127
|
+
var creep = {
|
|
128
|
+
id: "creep",
|
|
129
|
+
name: "Creep",
|
|
130
|
+
kind: "unit",
|
|
131
|
+
radius: .45,
|
|
132
|
+
stats: {
|
|
133
|
+
health: 200,
|
|
134
|
+
attackDamage: 10,
|
|
135
|
+
attackSpeed: 1,
|
|
136
|
+
attackRange: .5,
|
|
137
|
+
moveSpeed: 3
|
|
138
|
+
},
|
|
139
|
+
sight: 8,
|
|
140
|
+
attacks: [{
|
|
141
|
+
damagePoint: .3,
|
|
142
|
+
acquisitionRange: 5
|
|
143
|
+
}],
|
|
144
|
+
level: 1,
|
|
145
|
+
bounty: [10, 10],
|
|
146
|
+
xp: 20
|
|
147
|
+
};
|
|
148
|
+
var hall = {
|
|
149
|
+
id: "hall",
|
|
150
|
+
name: "Hall",
|
|
151
|
+
kind: "building",
|
|
152
|
+
radius: 2,
|
|
153
|
+
footprint: {
|
|
154
|
+
w: 4,
|
|
155
|
+
d: 4
|
|
156
|
+
},
|
|
157
|
+
stats: {
|
|
158
|
+
health: 1500,
|
|
159
|
+
armor: 10
|
|
160
|
+
},
|
|
161
|
+
sight: 12,
|
|
162
|
+
cost: {
|
|
163
|
+
gold: 400,
|
|
164
|
+
lumber: 200
|
|
165
|
+
},
|
|
166
|
+
buildTime: 40,
|
|
167
|
+
supplyProvided: 10,
|
|
168
|
+
trains: ["worker"],
|
|
169
|
+
dropOff: true,
|
|
170
|
+
upgradesTo: "keep"
|
|
171
|
+
};
|
|
172
|
+
var keep = {
|
|
173
|
+
...hall,
|
|
174
|
+
id: "keep",
|
|
175
|
+
name: "Keep",
|
|
176
|
+
stats: {
|
|
177
|
+
health: 2e3,
|
|
178
|
+
armor: 10
|
|
179
|
+
},
|
|
180
|
+
cost: {
|
|
181
|
+
gold: 300,
|
|
182
|
+
lumber: 200
|
|
183
|
+
},
|
|
184
|
+
provides: ["hall"],
|
|
185
|
+
upgradesTo: void 0
|
|
186
|
+
};
|
|
187
|
+
var supply = {
|
|
188
|
+
id: "supply",
|
|
189
|
+
name: "Supply",
|
|
190
|
+
kind: "building",
|
|
191
|
+
radius: 1,
|
|
192
|
+
footprint: {
|
|
193
|
+
w: 2,
|
|
194
|
+
d: 2
|
|
195
|
+
},
|
|
196
|
+
stats: {
|
|
197
|
+
health: 500,
|
|
198
|
+
armor: 10
|
|
199
|
+
},
|
|
200
|
+
sight: 6,
|
|
201
|
+
cost: { gold: 50 },
|
|
202
|
+
buildTime: 10,
|
|
203
|
+
supplyProvided: 10
|
|
204
|
+
};
|
|
205
|
+
var barracks = {
|
|
206
|
+
id: "barracks",
|
|
207
|
+
name: "Barracks",
|
|
208
|
+
kind: "building",
|
|
209
|
+
radius: 1.5,
|
|
210
|
+
footprint: {
|
|
211
|
+
w: 3,
|
|
212
|
+
d: 3
|
|
213
|
+
},
|
|
214
|
+
stats: {
|
|
215
|
+
health: 1e3,
|
|
216
|
+
armor: 10
|
|
217
|
+
},
|
|
218
|
+
sight: 9,
|
|
219
|
+
cost: {
|
|
220
|
+
gold: 150,
|
|
221
|
+
lumber: 50
|
|
222
|
+
},
|
|
223
|
+
buildTime: 20,
|
|
224
|
+
trains: ["soldier", "archer"],
|
|
225
|
+
researches: ["armour"]
|
|
226
|
+
};
|
|
227
|
+
var tower = {
|
|
228
|
+
id: "tower",
|
|
229
|
+
name: "Tower",
|
|
230
|
+
kind: "building",
|
|
231
|
+
radius: 1,
|
|
232
|
+
footprint: {
|
|
233
|
+
w: 2,
|
|
234
|
+
d: 2
|
|
235
|
+
},
|
|
236
|
+
stats: {
|
|
237
|
+
health: 500,
|
|
238
|
+
attackDamage: 20,
|
|
239
|
+
attackSpeed: 1,
|
|
240
|
+
attackRange: 8,
|
|
241
|
+
armor: 10
|
|
242
|
+
},
|
|
243
|
+
sight: 12,
|
|
244
|
+
attacks: [{
|
|
245
|
+
damagePoint: .3,
|
|
246
|
+
acquisitionRange: 8,
|
|
247
|
+
projectileSpeed: 20,
|
|
248
|
+
targets: ["ground", "air"]
|
|
249
|
+
}],
|
|
250
|
+
cost: {
|
|
251
|
+
gold: 100,
|
|
252
|
+
lumber: 50
|
|
253
|
+
},
|
|
254
|
+
buildTime: 20,
|
|
255
|
+
requires: ["barracks"]
|
|
256
|
+
};
|
|
257
|
+
var altar = {
|
|
258
|
+
id: "altar",
|
|
259
|
+
name: "Altar",
|
|
260
|
+
kind: "building",
|
|
261
|
+
radius: 1.5,
|
|
262
|
+
footprint: {
|
|
263
|
+
w: 3,
|
|
264
|
+
d: 3
|
|
265
|
+
},
|
|
266
|
+
stats: {
|
|
267
|
+
health: 1e3,
|
|
268
|
+
armor: 10
|
|
269
|
+
},
|
|
270
|
+
sight: 9,
|
|
271
|
+
cost: {
|
|
272
|
+
gold: 200,
|
|
273
|
+
lumber: 50
|
|
274
|
+
},
|
|
275
|
+
buildTime: 30,
|
|
276
|
+
trains: ["hero"],
|
|
277
|
+
revives: true
|
|
278
|
+
};
|
|
279
|
+
var mine = {
|
|
280
|
+
id: "mine",
|
|
281
|
+
name: "Mine",
|
|
282
|
+
kind: "resource",
|
|
283
|
+
radius: 2,
|
|
284
|
+
footprint: {
|
|
285
|
+
w: 4,
|
|
286
|
+
d: 4
|
|
287
|
+
},
|
|
288
|
+
stats: { health: 1 },
|
|
289
|
+
sight: 0,
|
|
290
|
+
invulnerable: true,
|
|
291
|
+
resource: {
|
|
292
|
+
kind: "gold",
|
|
293
|
+
amount: 1e4,
|
|
294
|
+
slots: 1,
|
|
295
|
+
entered: true
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var tree = {
|
|
299
|
+
id: "tree",
|
|
300
|
+
name: "Tree",
|
|
301
|
+
kind: "resource",
|
|
302
|
+
radius: .5,
|
|
303
|
+
footprint: {
|
|
304
|
+
w: 1,
|
|
305
|
+
d: 1
|
|
306
|
+
},
|
|
307
|
+
stats: { health: 1 },
|
|
308
|
+
sight: 0,
|
|
309
|
+
invulnerable: true,
|
|
310
|
+
fellable: true,
|
|
311
|
+
resource: {
|
|
312
|
+
kind: "lumber",
|
|
313
|
+
amount: 50,
|
|
314
|
+
slots: 1,
|
|
315
|
+
gatherTime: 5
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
var armour = {
|
|
319
|
+
id: "armour",
|
|
320
|
+
name: "Armour",
|
|
321
|
+
description: "+10 armor for soldiers and archers",
|
|
322
|
+
cost: {
|
|
323
|
+
gold: 100,
|
|
324
|
+
lumber: 50
|
|
325
|
+
},
|
|
326
|
+
researchTime: 20,
|
|
327
|
+
maxLevel: 2,
|
|
328
|
+
effects: [{
|
|
329
|
+
units: ["soldier", "archer"],
|
|
330
|
+
stats: { armor: 10 }
|
|
331
|
+
}]
|
|
332
|
+
};
|
|
333
|
+
var guarded = {
|
|
334
|
+
id: "guarded",
|
|
335
|
+
name: "Guarded",
|
|
336
|
+
description: "+20 armor",
|
|
337
|
+
duration: 10,
|
|
338
|
+
stats: { armor: 20 }
|
|
339
|
+
};
|
|
340
|
+
var abilities = [
|
|
341
|
+
{
|
|
342
|
+
id: "heal",
|
|
343
|
+
name: "Heal",
|
|
344
|
+
description: "Heals a friendly unit for 100 or 200",
|
|
345
|
+
target: "unit",
|
|
346
|
+
targets: {
|
|
347
|
+
allies: true,
|
|
348
|
+
self: false
|
|
349
|
+
},
|
|
350
|
+
manaCost: 10,
|
|
351
|
+
cooldown: 5,
|
|
352
|
+
range: 6,
|
|
353
|
+
effects: [{
|
|
354
|
+
type: "heal",
|
|
355
|
+
amount: [100, 200]
|
|
356
|
+
}]
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
id: "guard",
|
|
360
|
+
name: "Guard",
|
|
361
|
+
description: "+20 armor for 10 seconds",
|
|
362
|
+
target: "none",
|
|
363
|
+
cooldown: 10,
|
|
364
|
+
effects: [{
|
|
365
|
+
type: "buff",
|
|
366
|
+
buff: "guarded",
|
|
367
|
+
on: "caster"
|
|
368
|
+
}]
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: "drink",
|
|
372
|
+
name: "Drink",
|
|
373
|
+
description: "Heals 100",
|
|
374
|
+
target: "none",
|
|
375
|
+
cooldown: 10,
|
|
376
|
+
effects: [{
|
|
377
|
+
type: "heal",
|
|
378
|
+
amount: 100,
|
|
379
|
+
on: "caster"
|
|
380
|
+
}]
|
|
381
|
+
}
|
|
382
|
+
];
|
|
383
|
+
var items = [{
|
|
384
|
+
id: "sword",
|
|
385
|
+
name: "Sword",
|
|
386
|
+
description: "+10 attack damage",
|
|
387
|
+
tier: "basic",
|
|
388
|
+
cost: { gold: 100 },
|
|
389
|
+
bonus: { stats: { attackDamage: 10 } }
|
|
390
|
+
}, {
|
|
391
|
+
id: "potion",
|
|
392
|
+
name: "Potion",
|
|
393
|
+
description: "Heals 100 when drunk",
|
|
394
|
+
tier: "consumable",
|
|
395
|
+
cost: { gold: 50 },
|
|
396
|
+
charges: 1,
|
|
397
|
+
ability: "drink"
|
|
398
|
+
}];
|
|
399
|
+
/** A hall where a player starts, and five workers on the side of the closest mine */
|
|
400
|
+
function startingBase(world, player, at) {
|
|
401
|
+
const base = world.spawn("hall", player, at);
|
|
402
|
+
let closest;
|
|
403
|
+
for (const entity of world.entities.values()) {
|
|
404
|
+
if (entity.def.resource?.kind !== "gold" || dist(entity.pos, base.pos) > 20) continue;
|
|
405
|
+
if (!closest || dist(entity.pos, base.pos) < dist(closest, base.pos)) closest = entity.pos;
|
|
406
|
+
}
|
|
407
|
+
const towards = closest ? {
|
|
408
|
+
x: Math.sign(closest.x - at.x),
|
|
409
|
+
z: Math.sign(closest.z - at.z)
|
|
410
|
+
} : {
|
|
411
|
+
x: 1,
|
|
412
|
+
z: 0
|
|
413
|
+
};
|
|
414
|
+
for (let i = 0; i < 5; i++) world.spawn("worker", player, {
|
|
415
|
+
x: at.x + towards.x * 4 + (i - 2) * towards.z,
|
|
416
|
+
z: at.z + towards.z * 4 + (i - 2) * towards.x
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* The fixture game (see the top of this file): every role of the kit, with round numbers. Gold and lumber, 1000 of each
|
|
421
|
+
* to start with; a hall and five workers at each start; heroes on (`withHeroes`); no fog explored at the start. A new
|
|
422
|
+
* one each time, for a test to change
|
|
423
|
+
*
|
|
424
|
+
* @beta
|
|
425
|
+
*/
|
|
426
|
+
function fixtureGame() {
|
|
427
|
+
return {
|
|
428
|
+
id: "fixture",
|
|
429
|
+
name: "Fixture",
|
|
430
|
+
version: "1.0.0",
|
|
431
|
+
units: [
|
|
432
|
+
worker,
|
|
433
|
+
soldier,
|
|
434
|
+
archer,
|
|
435
|
+
hero,
|
|
436
|
+
creep,
|
|
437
|
+
hall,
|
|
438
|
+
keep,
|
|
439
|
+
supply,
|
|
440
|
+
barracks,
|
|
441
|
+
tower,
|
|
442
|
+
altar,
|
|
443
|
+
mine,
|
|
444
|
+
tree
|
|
445
|
+
].map((def) => structuredClone(def)),
|
|
446
|
+
upgrades: [structuredClone(armour)],
|
|
447
|
+
abilities: structuredClone(abilities),
|
|
448
|
+
buffs: [structuredClone(guarded)],
|
|
449
|
+
items: structuredClone(items),
|
|
450
|
+
resources: [{
|
|
451
|
+
id: "gold",
|
|
452
|
+
name: "Gold",
|
|
453
|
+
start: 1e3
|
|
454
|
+
}, {
|
|
455
|
+
id: "lumber",
|
|
456
|
+
name: "Lumber",
|
|
457
|
+
start: 1e3
|
|
458
|
+
}],
|
|
459
|
+
currency: "gold",
|
|
460
|
+
packs: [withHeroes()],
|
|
461
|
+
start: { spawn: startingBase }
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* A map of the fixture game, 64 cells a side: two players, starting at opposite corners (12, 12 and 52, 52), a gold
|
|
466
|
+
* mine and a row of trees by each start, and a camp of three creeps in the middle
|
|
467
|
+
*
|
|
468
|
+
* @beta
|
|
469
|
+
*/
|
|
470
|
+
function fixtureMap() {
|
|
471
|
+
const map = createMap({
|
|
472
|
+
name: "Fixture",
|
|
473
|
+
width: 64,
|
|
474
|
+
depth: 64,
|
|
475
|
+
game: "fixture"
|
|
476
|
+
});
|
|
477
|
+
map.players[0].start = {
|
|
478
|
+
x: 12,
|
|
479
|
+
z: 12
|
|
480
|
+
};
|
|
481
|
+
map.players[1].start = {
|
|
482
|
+
x: 52,
|
|
483
|
+
z: 52
|
|
484
|
+
};
|
|
485
|
+
let id = 1;
|
|
486
|
+
const object = (type, x, z) => map.objects.push({
|
|
487
|
+
id: id++,
|
|
488
|
+
type,
|
|
489
|
+
owner: 0,
|
|
490
|
+
x,
|
|
491
|
+
z,
|
|
492
|
+
facing: 0,
|
|
493
|
+
scale: 1
|
|
494
|
+
});
|
|
495
|
+
object("mine", 12, 22);
|
|
496
|
+
object("mine", 52, 42);
|
|
497
|
+
for (let i = 0; i < 6; i++) {
|
|
498
|
+
object("tree", 22.5 + i, 4.5);
|
|
499
|
+
object("tree", 36.5 + i, 59.5);
|
|
500
|
+
}
|
|
501
|
+
map.camps.push({
|
|
502
|
+
id: id++,
|
|
503
|
+
x: 32,
|
|
504
|
+
z: 32,
|
|
505
|
+
units: [
|
|
506
|
+
"creep",
|
|
507
|
+
"creep",
|
|
508
|
+
"creep"
|
|
509
|
+
]
|
|
510
|
+
});
|
|
511
|
+
return map;
|
|
512
|
+
}
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/aiTesting.ts
|
|
515
|
+
/**
|
|
516
|
+
* Testing a computer player (AI plan, E3): what a game's tests of its brains are written with, in
|
|
517
|
+
* `@v12-engine/engine/testing` beside `testWorld` and `run`. A brain is tested as it plays, through the world it sits in:
|
|
518
|
+
*
|
|
519
|
+
* const world = await testWorld(myGame)
|
|
520
|
+
* world.computers.seat(ENEMY, { enemyBases: [home], brain: myBrain })
|
|
521
|
+
* const watch = watchComputer(world, ENEMY) // its invariants, every think
|
|
522
|
+
* run(world, 120)
|
|
523
|
+
* expectDecision(thinkOnce(world, ENEMY), 'ai.attack')
|
|
524
|
+
* watch.assert() // no rule broken
|
|
525
|
+
*
|
|
526
|
+
* `thinkOnce` has a seated brain think now and gives its report; `expectDecision` says a goal runs, or why not in
|
|
527
|
+
* words; `watchComputer` checks the invariants of `ai/checks.ts` after every think (a unit in one squad, gold spent
|
|
528
|
+
* once, finite numbers, commands about what it sees, no churn, nothing stuck) and keeps what it decided; `fairPlay` is
|
|
529
|
+
* the one about seeing; `recordDecisions` keeps every computer's decisions for a replay's side file; `checkComputerSave`
|
|
530
|
+
* is the seventh invariant: a game saved and loaded decides the same from then on. These take the world, which a test
|
|
531
|
+
* may: the brains under test never do.
|
|
532
|
+
*/
|
|
533
|
+
/**
|
|
534
|
+
* The brain that plays a player, where one is seated (`world.computers.seat`): none where nobody is, or where what is
|
|
535
|
+
* seated is not a brain (`defineComputer`)
|
|
536
|
+
*
|
|
537
|
+
* @beta
|
|
538
|
+
*/
|
|
539
|
+
function brainOf(world, player) {
|
|
540
|
+
const computer = world.computers.get(player)?.computer;
|
|
541
|
+
return computer instanceof Brain ? computer : void 0;
|
|
542
|
+
}
|
|
543
|
+
/** The brain at a player, or an error that says what is seated there instead */
|
|
544
|
+
function seated(world, player) {
|
|
545
|
+
const brain = brainOf(world, player);
|
|
546
|
+
if (brain) return brain;
|
|
547
|
+
const system = world.computers.get(player);
|
|
548
|
+
throw new Error(system ? `The computer player at ${player} is not a brain (defineBrain): it has no report` : `No computer player is seated at ${player}`);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Has the brain at a player think now, all of it (a think its budget slices over ticks is run to its end), over a new
|
|
552
|
+
* picture of the game, and gives its report: what a test reads after setting a scene. Its commands land at the next
|
|
553
|
+
* tick, as a think's do. A think the seat would have had is not skipped: this one is beside them
|
|
554
|
+
*
|
|
555
|
+
* @beta
|
|
556
|
+
*/
|
|
557
|
+
function thinkOnce(world, player) {
|
|
558
|
+
const brain = seated(world, player);
|
|
559
|
+
do
|
|
560
|
+
brain.think();
|
|
561
|
+
while (brain.busy);
|
|
562
|
+
return brain.report();
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Says that a goal runs, as of a report (`thinkOnce`, `Brain.report`): it is picked and holds what it claimed. Throws
|
|
566
|
+
* where it does not, with its row in words (`Brain.explain`): what each consideration answered and what kept it out.
|
|
567
|
+
* Gives the goal's row. With `picked: false`, says it does not run
|
|
568
|
+
*
|
|
569
|
+
* expectDecision(thinkOnce(world, ENEMY), 'ai.defend')
|
|
570
|
+
*
|
|
571
|
+
* @beta
|
|
572
|
+
*/
|
|
573
|
+
function expectDecision(report, goal, options = {}) {
|
|
574
|
+
const wanted = options.picked ?? true;
|
|
575
|
+
const row = report.goals.find((each) => each.id === goal);
|
|
576
|
+
if (!row) throw new Error(`The brain ${report.brain} has no goal "${goal}": its goals are ${report.goals.map((each) => each.id).join(", ") || "none"}`);
|
|
577
|
+
if (row.picked === wanted) return row;
|
|
578
|
+
const seconds = (tick) => tick / report.tickRate;
|
|
579
|
+
throw new Error(`Expected ${goal} ${wanted ? "to run" : "not to run"} at ${report.time.toFixed(1)} s, and it ${wanted ? "does not" : "does"}. ${explainGoal(row, seconds, report.time)}${report.goal && report.goal !== goal ? ` The best goal is ${report.goal}.` : ""}`);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Follows the brain at a player from now on: after every think, the invariants of `ai/checks.ts` (a unit in one squad
|
|
583
|
+
* and one hold, gold spent once, finite numbers, commands about what it sees, no order sent again to a unit still at
|
|
584
|
+
* it, builders, tasks, holds and wants that end), with what it decided and sent. A broken rule is kept, not thrown,
|
|
585
|
+
* unless `onViolation` throws: `assert` at the end says them all
|
|
586
|
+
*
|
|
587
|
+
* @beta
|
|
588
|
+
*/
|
|
589
|
+
function watchComputer(world, player, options = {}) {
|
|
590
|
+
const brain = seated(world, player);
|
|
591
|
+
const violations = [];
|
|
592
|
+
const decisions = [];
|
|
593
|
+
let thinks = 0;
|
|
594
|
+
let commands = 0;
|
|
595
|
+
const { stop: unchecked } = checkBrain(brain, {
|
|
596
|
+
...options,
|
|
597
|
+
onViolation: (violation) => {
|
|
598
|
+
violations.push(violation);
|
|
599
|
+
options.onViolation?.(violation);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
const observer = options.observe ? new LocalSession(world, player, { observer: true }) : void 0;
|
|
603
|
+
const unwatched = brain.watch({
|
|
604
|
+
sent: (command) => {
|
|
605
|
+
commands++;
|
|
606
|
+
options.sent?.(command, brain);
|
|
607
|
+
},
|
|
608
|
+
decided: (decision) => void decisions.push(decision),
|
|
609
|
+
thought: () => {
|
|
610
|
+
thinks++;
|
|
611
|
+
if (observer) options.observe(observer.snapshot(), brain);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
return {
|
|
615
|
+
brain,
|
|
616
|
+
violations,
|
|
617
|
+
decisions,
|
|
618
|
+
get thinks() {
|
|
619
|
+
return thinks;
|
|
620
|
+
},
|
|
621
|
+
get commands() {
|
|
622
|
+
return commands;
|
|
623
|
+
},
|
|
624
|
+
assert() {
|
|
625
|
+
if (!violations.length) return;
|
|
626
|
+
const lines = violations.slice(0, 20).map((each) => ` ${each.invariant} at tick ${each.tick}: ${each.message}`);
|
|
627
|
+
const more = violations.length > 20 ? `\n and ${violations.length - 20} more` : "";
|
|
628
|
+
throw new Error(`The computer player at ${player} broke ${violations.length} rule${violations.length === 1 ? "" : "s"}:\n${lines.join("\n")}${more}`);
|
|
629
|
+
},
|
|
630
|
+
stop() {
|
|
631
|
+
unchecked();
|
|
632
|
+
unwatched();
|
|
633
|
+
observer?.dispose();
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Follows the brain at a player from now on for fair play: that it never sends a command about an entity its snapshot
|
|
639
|
+
* did not have (a fog leak: its seat's session would drop the command, so a game shows nothing; this says it), and
|
|
640
|
+
* counts what the game refused
|
|
641
|
+
*
|
|
642
|
+
* @beta
|
|
643
|
+
*/
|
|
644
|
+
function fairPlay(world, player) {
|
|
645
|
+
const watch = watchComputer(world, player, { invariants: ["commands"] });
|
|
646
|
+
const brain = watch.brain;
|
|
647
|
+
const refusedBefore = brain.stats.refused;
|
|
648
|
+
return {
|
|
649
|
+
get unfair() {
|
|
650
|
+
return watch.violations;
|
|
651
|
+
},
|
|
652
|
+
get refused() {
|
|
653
|
+
return brain.stats.refused - refusedBefore;
|
|
654
|
+
},
|
|
655
|
+
assert: () => watch.assert(),
|
|
656
|
+
stop: () => watch.stop()
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
/** How many recorders were made: each one's system a name of its own */
|
|
660
|
+
var recorders = 0;
|
|
661
|
+
/**
|
|
662
|
+
* Keeps the decisions of every brain seated at a world, from now on, those seated later included (looked for at each
|
|
663
|
+
* tick, by a system of its own that keeps nothing in a save)
|
|
664
|
+
*
|
|
665
|
+
* @beta
|
|
666
|
+
*/
|
|
667
|
+
function recordDecisions(world) {
|
|
668
|
+
const decisions = [];
|
|
669
|
+
const watched = /* @__PURE__ */ new Map();
|
|
670
|
+
const look = () => {
|
|
671
|
+
for (const player of world.computers.list()) {
|
|
672
|
+
const brain = brainOf(world, player);
|
|
673
|
+
if (!brain || watched.has(brain)) continue;
|
|
674
|
+
watched.set(brain, brain.watch({ decided: (decision) => void decisions.push({
|
|
675
|
+
...decision,
|
|
676
|
+
player: brain.playerId,
|
|
677
|
+
brain: brain.def.id
|
|
678
|
+
}) }));
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
look();
|
|
682
|
+
const name = `core.ai.recordDecisions.${++recorders}`;
|
|
683
|
+
world.addSystem({
|
|
684
|
+
name,
|
|
685
|
+
update: look
|
|
686
|
+
});
|
|
687
|
+
return {
|
|
688
|
+
decisions,
|
|
689
|
+
stop() {
|
|
690
|
+
world.removeSystem(name);
|
|
691
|
+
for (const unwatch of watched.values()) unwatch();
|
|
692
|
+
watched.clear();
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* The seventh invariant of a computer player: a game saved at tick `from` and loaded makes the same decisions from then
|
|
698
|
+
* on as the game it was saved from, under the same commands. Makes the world with `make` (its computers seated, as a
|
|
699
|
+
* game seats them), runs it to `from`, saves it as a file would keep it and loads that with `game`, then runs both to
|
|
700
|
+
* `ticks` and compares what every computer decided. What lost rng, tasks or memory in a restore shows as
|
|
701
|
+
*
|
|
702
|
+
* expect(await checkComputerSave(() => duel(), { from: 600, ticks: 2400, game: melee })).toBeNull()
|
|
703
|
+
*
|
|
704
|
+
* @beta
|
|
705
|
+
*/
|
|
706
|
+
async function checkComputerSave(make, options) {
|
|
707
|
+
const saved = await make();
|
|
708
|
+
let loaded;
|
|
709
|
+
try {
|
|
710
|
+
while (saved.tick < options.from) saved.step();
|
|
711
|
+
loaded = await loadWorld(decodeSave(encodeSave(saveWorld(saved))), { game: options.game });
|
|
712
|
+
const before = recordDecisions(saved);
|
|
713
|
+
const after = recordDecisions(loaded);
|
|
714
|
+
while (saved.tick < options.ticks) {
|
|
715
|
+
saved.step();
|
|
716
|
+
loaded.step();
|
|
717
|
+
}
|
|
718
|
+
before.stop();
|
|
719
|
+
after.stop();
|
|
720
|
+
const a = before.decisions;
|
|
721
|
+
const b = after.decisions;
|
|
722
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) if (JSON.stringify(a[i]) !== JSON.stringify(b[i])) return {
|
|
723
|
+
index: i,
|
|
724
|
+
...a[i] && { saved: a[i] },
|
|
725
|
+
...b[i] && { loaded: b[i] }
|
|
726
|
+
};
|
|
727
|
+
return null;
|
|
728
|
+
} finally {
|
|
729
|
+
saved.dispose();
|
|
730
|
+
loaded?.dispose();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
//#endregion
|
|
734
|
+
//#region src/testing.ts
|
|
735
|
+
/**
|
|
736
|
+
* Flat ground, `size` cells a side: 48 when left out
|
|
737
|
+
*
|
|
738
|
+
* @beta
|
|
739
|
+
*/
|
|
740
|
+
var flatTerrain = (size = 48) => new Terrain(size, size, new Float32Array((size + 1) * (size + 1)));
|
|
741
|
+
/**
|
|
742
|
+
* The players of a test world, when a test says none: a neutral one, and two who are enemies
|
|
743
|
+
*
|
|
744
|
+
* @beta
|
|
745
|
+
*/
|
|
746
|
+
var TEST_PLAYERS = [
|
|
747
|
+
{
|
|
748
|
+
id: 0,
|
|
749
|
+
name: "Neutral",
|
|
750
|
+
team: 0,
|
|
751
|
+
color: [
|
|
752
|
+
1,
|
|
753
|
+
1,
|
|
754
|
+
0
|
|
755
|
+
],
|
|
756
|
+
neutral: true
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
id: 1,
|
|
760
|
+
name: "Player",
|
|
761
|
+
team: 1,
|
|
762
|
+
color: [
|
|
763
|
+
0,
|
|
764
|
+
0,
|
|
765
|
+
1
|
|
766
|
+
]
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
id: 2,
|
|
770
|
+
name: "Enemy",
|
|
771
|
+
team: 2,
|
|
772
|
+
color: [
|
|
773
|
+
1,
|
|
774
|
+
0,
|
|
775
|
+
0
|
|
776
|
+
]
|
|
777
|
+
}
|
|
778
|
+
];
|
|
779
|
+
/**
|
|
780
|
+
* A small world of a game for a test: flat ground, a neutral player and two enemies (`TEST_PLAYERS`), the same every
|
|
781
|
+
* time (seed 7). Each of them is the test's to say otherwise
|
|
782
|
+
*
|
|
783
|
+
* @beta
|
|
784
|
+
*/
|
|
785
|
+
function testWorld(game, options = {}) {
|
|
786
|
+
return createWorld({
|
|
787
|
+
terrain: options.terrain ?? flatTerrain(options.size),
|
|
788
|
+
game,
|
|
789
|
+
seed: options.seed ?? 7,
|
|
790
|
+
players: options.players ?? TEST_PLAYERS.map((player) => ({ ...player })),
|
|
791
|
+
log: options.log
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Steps the world for up to `seconds`, stopping early once `until` holds. Returns the time that went by in it
|
|
796
|
+
*
|
|
797
|
+
* @beta
|
|
798
|
+
*/
|
|
799
|
+
function run(world, seconds, until) {
|
|
800
|
+
const ticks = Math.ceil(seconds * world.tickRate);
|
|
801
|
+
for (let i = 0; i < ticks; i++) {
|
|
802
|
+
world.step();
|
|
803
|
+
if (until?.()) return (i + 1) / world.tickRate;
|
|
804
|
+
}
|
|
805
|
+
return seconds;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Gives units an order, as their owner would: taken up at the next tick. `queue` adds it after what they are doing
|
|
809
|
+
*
|
|
810
|
+
* @beta
|
|
811
|
+
*/
|
|
812
|
+
var order = (world, entities, init, queue = false) => world.issue({
|
|
813
|
+
type: "order",
|
|
814
|
+
player: entities[0].owner,
|
|
815
|
+
entities: entities.map((entity) => entity.id),
|
|
816
|
+
order: init,
|
|
817
|
+
queue
|
|
818
|
+
});
|
|
819
|
+
//#endregion
|
|
820
|
+
export { TEST_PLAYERS, brainOf, checkComputerSave, checkDeterminism, defineSimulation, expectDecision, fairPlay, firstDifference, fixtureGame, fixtureMap, flatTerrain, order, playGame, recordDecisions, run, simulate, testWorld, thinkOnce, watchComputer, winRates, worldFingerprint, worldHash, worldState };
|
|
821
|
+
|
|
822
|
+
//# sourceMappingURL=testing.js.map
|