From b3da47a42fce13870a1716de7e9cfa5ad6b55487 Mon Sep 17 00:00:00 2001 From: treeform Date: Thu, 24 Sep 2026 10:54:22 -0700 Subject: [PATCH 1/7] Add metered neural array policy operations --- config.nims | 4 + coworld/dependencies.lock | 2 +- docs/neural-arrays.md | 108 ++ examples/awm/awmbots.nim | 2 + examples/call_to_adventure/bots.nim | 2 + examples/gods_of_the_arena/bots.nim | 2 + examples/gods_of_the_arena/players/neural.bas | 828 ++++++++++++ examples/heartleaf/bots.nim | 2 + examples/light_vs_dark/bots.nim | 2 + experiments/neural-policy/bench_neural.nim | 56 + experiments/neural-policy/compare.nim | 29 + experiments/neural-policy/convert.py | 109 ++ experiments/neural-policy/original.bas | 1147 +++++++++++++++++ experiments/neural-policy/results.md | 122 ++ nimby.lock | 2 +- src/polyworld/arrays.nim | 135 ++ tests/test_arrays.nim | 166 +++ tests/tests.nim | 1 + 18 files changed, 2717 insertions(+), 2 deletions(-) create mode 100644 docs/neural-arrays.md create mode 100644 examples/gods_of_the_arena/players/neural.bas create mode 100644 experiments/neural-policy/bench_neural.nim create mode 100644 experiments/neural-policy/compare.nim create mode 100644 experiments/neural-policy/convert.py create mode 100644 experiments/neural-policy/original.bas create mode 100644 experiments/neural-policy/results.md create mode 100644 src/polyworld/arrays.nim create mode 100644 tests/test_arrays.nim diff --git a/config.nims b/config.nims index 41fc5b76..ab0e0dea 100644 --- a/config.nims +++ b/config.nims @@ -25,6 +25,10 @@ else: --define:nimTypeNames --define:flatty64 +let bassyPath = getEnv("BASSY_PATH") +if bassyPath.len > 0: + switch("path", bassyPath) + when defined(coworld): when defined(emscripten): error("Coworld servers are native. Build replay viewers without -d:coworld.") diff --git a/coworld/dependencies.lock b/coworld/dependencies.lock index 311ecd0f..9c9eb24f 100644 --- a/coworld/dependencies.lock +++ b/coworld/dependencies.lock @@ -1,4 +1,4 @@ -bassy 0.1.0 https://github.com/treeform/bassy b25e0efef3fec0bd86ed3154659c0762a7158bd3 +bassy 0.1.0 https://github.com/treeform/bassy 78b3f3c9538cf1512ae99188d412cfffa134259a fixxy 0.1.0 https://github.com/treeform/fixxy 05e5446dffb70093056cebb0c57721a60deaf52a silky 0.2.0 https://github.com/treeform/silky fb9b13910edd66cf1751056784c2f7d2932a59fc pixie 6.1.0 https://github.com/treeform/pixie 87cecced5c4c6f311c658a5f3ca0c9b43edb6aa7 diff --git a/docs/neural-arrays.md b/docs/neural-arrays.md new file mode 100644 index 00000000..0fc2844d --- /dev/null +++ b/docs/neural-arrays.md @@ -0,0 +1,108 @@ +# BASIC data and native array arithmetic + +Polyworld policies can define their own networks in an ordinary `.bas` file. +`DATA` stores read-only numeric arrays, `DIM` reserves mutable arrays, and +native functions perform the expensive loops. There is no model manifest or +fixed neural architecture. ZIP packages and external weight files are deferred. + +```basic +DATA encoder AS int32 = _ + 2, -1, 3, _ + 4, 0, 1 +DATA bias = 5, -2 + +DIM features(2) +DIM hidden(1) +features(0) = 10 +features(1) = 20 +features(2) = 30 + +linear(features, encoder, bias, hidden, 3, 2) +relu(hidden, 2) +choice = argmax(hidden, 2) +``` + +This computes two rows, each with three inputs. `DIM features(2)` has three +elements because BASIC bounds are inclusive. Counts passed to native functions +are lengths, not upper bounds. Operations can use a prefix of a larger array. + +## Data declarations + +`DATA name = value, ...` accepts numeric literals and optional `+` or `-` signs. +An underscore at the end of a line continues the declaration. Declarations +must be at the top level. A DATA array must contain at least one element. + +Without `AS`, integer literals remain signed int32 and decimal literals use +Bassy's deterministic Q16.16 fixed-point representation. `AS int32` requires +exact integers; `AS fixed32` converts every literal to Q16.16. Float32 is not +part of this interface. These are the same numeric semantics as ordinary BASIC +expressions, including int32 wraparound and fixed-point rounding. + +`weights(0)` reads an element. A bare `weights`, or `weights()`, supplies a +program-local array handle to a host function. Handles are checked against the +current VM; they never refer to host files, pointers, or another player's data. +String arrays cannot be passed to numeric operations. + +DATA values initialize when the VM is created and are restored by `reset`. +Per-decision `restart` retains them along with ordinary arrays and globals. +Writes to DATA fail in BASIC and through native destination arguments. + +## Operations and charging + +| Function | Meaning | Native operations charged | +| --- | --- | --- | +| `linear(x, weights, bias, output, inputs, outputs)` | Bias plus the ordered dot product for each row | `2 * inputs * outputs + 2 * outputs` | +| `relu(values, count)` | Replace negatives with zero in place | `2 * count` | +| `argmax(values, count)` | Index of the greatest value; first index wins ties | `count` | +| `argmaxMasked(values, mask, count)` | Greatest eligible value, or `-1` when every mask entry is zero | `2 * count` | +| `dataAdd(left, right, output, count)` | Elementwise sum | `count` | +| `dataMultiply(left, right, output, count)` | Elementwise product | `count` | +| `dataCopy(source, output, count)` | Copy a numeric prefix | `count` | +| `dataFill(output, value, count)` | Fill a numeric prefix | `count` | +| `dataDot(left, right, count)` | Ordered dot product starting at zero | `2 * count` | + +Each call also pays the existing VM instruction and host-entry work charges. +Native costs are deducted from **both** remaining instructions and work units, +once before the kernel executes. Size calculations use checked bounds and +int64 arithmetic. Invalid dimensions, too-short arrays, read-only destinations, +or an insufficient budget raise `BasicError` before any kernel output changes. +A later error elsewhere in BASIC does not roll back earlier completed calls. +The counts are deterministic work units, not elapsed CPU time. + +Weights are row-major: `weights(row * inputs + column)`. `linear` starts with +the bias and adds products from left to right. It does not fuse, reorder, +parallelize, or convert arithmetic to floats. Its output must be a separate +array from its inputs, weights, and biases. Elementwise operations support +in-place use of the same array. + +Arrays allocate when the runtime is constructed. Native kernels allocate no +tensor buffers or scratch arrays. DATA shares the existing array count and +element limits; its stored initializer and live VM copy both count toward the +logical memory allowance. Cells use Bassy's tagged numeric representation, +budgeted at 16 bytes per cell, rather than packed four-byte weight storage. +Source bytes and compiled instruction counts retain their separate limits. + +GotA's existing limits remain 64 KiB of source, 32 arrays, 4,096 total array +elements, 2 MiB of logical runtime memory, 20,000 instructions and 50,000 work +units per decision. This policy fits without raising any of them. + +## Implementation and dependency + +Bassy implements DATA parsing, initialization, checked array views, and +`ContextHostProc` callbacks. Polyworld's `src/polyworld/arrays.nim` implements +the arithmetic and registers it in all five BASIC game hosts. Game observations +and commands keep their existing APIs. + +The required Bassy revision is pinned in both `nimby.lock` and +`coworld/dependencies.lock`. Install the locked dependencies before building. +For development against a sibling Bassy checkout, the optional `BASSY_PATH` +override selects its source directory: + +```sh +export BASSY_PATH=../bassy-nn/src +nim check tests/tests.nim +nim r tests/tests.nim +``` + +See the [converted policy](../examples/gods_of_the_arena/players/neural.bas) and +[recorded comparison](../experiments/neural-policy/results.md). diff --git a/examples/awm/awmbots.nim b/examples/awm/awmbots.nim index 71b13d3f..cab80177 100644 --- a/examples/awm/awmbots.nim +++ b/examples/awm/awmbots.nim @@ -3,6 +3,7 @@ ## Each invocation plays at most one card; the game loop calls repeatedly ## until the bot ends its turn. import bassy +import polyworld/arrays import awmsim type @@ -56,6 +57,7 @@ proc botLimits(): Limits = proc buildBotHost(playerId: int32): Host = result = initHost() + result.addArrayFunctions() for name in DataSlotNames: discard result.addData(name) diff --git a/examples/call_to_adventure/bots.nim b/examples/call_to_adventure/bots.nim index 42356bb0..784483c5 100644 --- a/examples/call_to_adventure/bots.nim +++ b/examples/call_to_adventure/bots.nim @@ -5,6 +5,7 @@ ## cannot write world fields directly. import + polyworld/arrays, bassy, polyworld/[bodies, metrics, cli, controllers, pathing, profiles], content, @@ -114,6 +115,7 @@ proc heroLimits(): Limits = proc buildHeroHost(heroId: int32): Host = ## Builds the world-query and high-level action API for one hero. result = initHost() + result.addArrayFunctions() for name in HeroDataNames: discard result.addData(name) diff --git a/examples/gods_of_the_arena/bots.nim b/examples/gods_of_the_arena/bots.nim index 5a7e5b55..58e39831 100644 --- a/examples/gods_of_the_arena/bots.nim +++ b/examples/gods_of_the_arena/bots.nim @@ -2,6 +2,7 @@ ## on the simulation. import + polyworld/arrays, bassy, fixxy, polyworld/[metrics, bodies, cli, controllers, pathing, profiles, tapes], content, @@ -262,6 +263,7 @@ proc abilityProc(heroId: int32, field: AbilityField): HostProc = proc initHeroHost(heroId: int32): Host = ## Builds the bounded world-query and action interface for one hero. result = initHost() + result.addArrayFunctions() for error in ActionError: discard result.addData($error, error.ord.int32) for class in HeroClass: diff --git a/examples/gods_of_the_arena/players/neural.bas b/examples/gods_of_the_arena/players/neural.bas new file mode 100644 index 00000000..6ad70d92 --- /dev/null +++ b/examples/gods_of_the_arena/players/neural.bas @@ -0,0 +1,828 @@ +' Richard's v63 policy using native, metered array arithmetic. +' Generated by convert.py. Game observations and commands are unchanged. +' DATA initializes once. DIM retains its inclusive BASIC upper bound. +' Matrices are row-major: weights(outputIndex * inputCount + inputIndex). + +DATA draftWeights AS fixed32 = _ + -0.252276, 0.208388, 0.165655, -0.077038, -0.307499, 0.393190, _ + -0.041722, -0.247450, -0.267264, 0.060881, 0.000000, -0.056464, _ + -0.558547, 0.238349, 0.148874, -0.351042, 0.022783, 0.133934, _ + -0.148994, -0.127461, 0.000000, -0.404200, 0.140616, -0.413587, _ + -0.093651, -0.294424, -0.233337, -0.138760, 0.163981, -0.185696, _ + -0.139714, -0.291812, 0.252661, 0.220766, -0.066995, 0.046584, _ + -0.074833, 0.282953, -0.316809, 0.260309, 0.498069, 0.607530, _ + -0.054461, 0.000000, 0.185443, 0.307806, 0.073798, 0.095548, _ + -0.624112, -0.128341, 0.116202, -0.113738, 0.022566, 0.000000, _ + 0.102318, -0.133625, 0.221801, -0.157735, 1.161687, 0.088798, _ + -0.393505, -0.273026, -0.028371, 0.127856, 0.160424, -0.457565, _ + 0.306957, -0.351070, 0.501679, 0.772043, -0.220549, -0.161796, _ + 0.448017, -0.150996, 0.136125, 0.178068, 0.000000, 0.247534, _ + -0.188988, -0.238955, 0.883443, 0.034082, 0.088084, 0.000000, _ + 0.010408, 0.586454, 0.000000, 0.410493, -0.005734, -0.226131, _ + -0.355937, 0.434671, -0.229144, 0.457953, 0.227879, 0.216607, _ + 0.191406, -0.378401, -0.681596, -0.102972, 0.400384, -0.198847, _ + -0.095249, -0.499520, 0.154836, 0.124309, -0.098351, 0.079512, _ + 0.896409, 0.000000, -0.105452, -0.086497, -0.053812, 0.204039, _ + -0.676251, 0.027087, -0.196028, 0.195917, -0.317785, 0.000000, _ + -0.397904, 0.182371, 0.046089, 0.192509, 0.418443, -0.254368, _ + 0.037337, -0.026151, -0.615030, 0.124070, -0.215150, -0.227015, _ + -0.279421, -0.805966, -0.051813, 0.012569, -0.390292, -0.465863, _ + 0.349372, 0.302593, -0.114656, -0.015071, 0.000000, 0.281769, _ + -0.040930, 0.011989, -0.098396, -0.247384, -0.013763, -0.706084, _ + 0.050385, -0.037334, 0.000000, 0.244777, -0.186678, -0.303979, _ + 0.209267, 0.433826, 0.085857, -0.061916, -0.232312, -0.183645, _ + -0.270940, -0.249030, -0.064811, -0.242457, -0.040038, -0.037761, _ + -0.901328, 0.027995, -0.392084, -0.497526, 0.312257, 0.104801, _ + -0.183950, 0.000000, 0.045312, 0.127481, 0.460752, -0.051012, _ + 0.381939, 0.438713, -0.283774, -0.098228, 0.006305, 0.000000, _ + -0.247731, -0.332176, 0.198120, -0.219440, -0.014794, -0.031255, _ + 0.727924, 0.905780, 0.451952, 0.593039, 0.580693, 0.784972, _ + 0.714471, 0.539523, 0.567051, 0.609534, 0.213345, -0.291630, _ + 0.069507, 0.000000, 0.103936, 0.023463, 0.000000, 0.043553, _ + 0.061384, 0.061384, -0.226798, 0.100322, 0.193012, 0.121433, _ + 0.029843, -0.093964, 0.000000, 0.131395, 0.086036, 0.043553, _ + 0.056989, 0.076966, 0.168151, -0.445483, -0.279111, 0.122942, _ + -0.494774, -0.299634, -0.131236, 0.092192, 0.164071, -0.176015, _ + 0.002602, 0.310725, -0.538125, 0.274210, 0.488313, -0.038538, _ + 0.069213, 0.000000, 0.434066, 0.183095, -0.078561, 0.226950, _ + 0.909428, -0.304960, 0.098653, 0.430365, 0.154216, 0.000000, _ + -0.505944, 0.085113, 0.237112, 0.203052, -0.294455, 0.111733, _ + 0.787697, 0.595129, 0.080587, -0.130948, 0.222401, 0.450339, _ + 0.041381, -0.138733, -0.194858, 0.316302, -0.090298, -1.080760, _ + -0.178166, -0.011497, -0.011433, -0.360402, 0.000000, 0.268030, _ + 0.530694, -0.386654, -0.656018, 0.527012, 0.138960, 0.183827, _ + -0.169586, -0.048555, 0.000000, -0.087915, -0.268616, 0.006353, _ + -0.106492, -0.105644, -0.198469, -0.350568, -0.342004, -0.555305, _ + -0.039455, -0.408405, -0.314541, -0.498641, 0.038310, -0.341580, _ + -0.161989, -0.076870, -0.158366, 0.325749, -0.290464, -0.066335, _ + -0.236848, 0.000000, -0.430459, -0.051418, -0.138183, -0.071204, _ + 0.001728, -0.269085, -0.168722, -0.023902, 0.052747, 0.000000, _ + -0.193678, -0.219699 + +DATA draftBias AS fixed32 = _ + -0.252276, 0.220766, 0.306957, -0.102972, -0.279421, -0.242457, _ + 0.714471, 0.092192, 0.041381, -0.498641 + +DATA encoder AS int32 = _ + 263, 222, -62, -8, 156, 176, _ + -660, 470, 178, 648, 309, 38, _ + 78, -150, 200, 5, -491, 183, _ + -26, 311, 646, 80, 544, 39, _ + 255, 126, -23, 117, -25, 585, _ + -305, -320, 118, 235, 32, 21, _ + -234, 656, 93, 602, 414, 439, _ + 243, 243, -340, 187, -78, 72, _ + -522, 315, -346, 10, -181, 180, _ + 64, 725, -25, 71, 104, -523, _ + -232, -80, -27, -30, -313, -350, _ + -320, -11, 493, -48, -128, 182, _ + -73, -520, -23, 190, 158, -294, _ + 61, -155, -122, 280, -28, -22, _ + 396, 438, 38, 385, 238, -15, _ + -727, 119, 363, -197, -128, -135, _ + -497, 121, -12, -15, 431, -81, _ + 108, -107, 178, -317, -339, 286, _ + 230, -103, -293, -141, 714, 388, _ + -197, -213, 109, 384, 214, 562, _ + 195, 323, 25, 668, -158, 360, _ + 159, 398, 339, -52, -357, -172, _ + 110, -22, 646, 125, -453, -365, _ + 177, 230, -367, 408, 465, 703, _ + 61, 136, 543, 41, -54, 54, _ + 122, -158, -130, 204, -64, -508, _ + 639, 551, 299, 239, -38, -406, _ + -41, -158, -309, 353, 131, -140, _ + -48, 244, 161, 174, -9, 91, _ + 715, -90, 489, 349, 361, 116, _ + -315, -91, -101, 238, -164, 238, _ + -133, 125, 458, 371, 3, 30, _ + 203, -106, 482, -345, 302, 932, _ + 147, 505, 101, 49, 180, -178, _ + -311, 502, -51, 361, 179, 638, _ + -61, 240, 314, 248, -8, 483, _ + 730, 314, 155, 291, -257, 241, _ + 543, 9, -66, 411, 470, 170, _ + 61, 50, -20, -192, -211, 60, _ + 68, -32, -109, -126, -404, 49, _ + 543, -90, 554, 445, 341, -324, _ + -471, -27, 378, 114, 861, 381, _ + 130, 413, -252, 152, 159, 421, _ + -140, -151, 669, -70, 515, 338, _ + 102, 453, -71, 383, 255, -158, _ + 195, 441, 133, 189, 264, 82, _ + -702, 275, 91, -190, 47, -505, _ + -288, -299, -56, 716, -16, 78, _ + 18, 95, 6, 302, -370, 419, _ + 284, 2, -157, 129, 332, 261, _ + -187, 342, -126, -271, -109, -323, _ + 149, 43, -690, 236, -95, 383, _ + 276, 62, 485, 215, -249, -34, _ + 413, 130, 137, 562, 61, 214, _ + 123, -49, 121, 69, 190, 856, _ + 44, -31, -132, -120, 458, 407, _ + -483, 6, 396, -271, 452, 40, _ + -47, -98, -226, -24, 342, 192, _ + 508, -284, 432, 481, 400, -699, _ + 108, 103, -31, -289, 548, 208, _ + 338, 67, 157, 80, -99, -136, _ + 179, -206, 274, -139, 86, 412, _ + 33, 278, 594, 651, 160, -271, _ + 341, 228, -548, -380, 170, 260, _ + 78, 188, 579, -65, 58, -197, _ + 173, 208, -353, 354, -48, -264, _ + 293, 413, -53, -198 + +DATA encoderBias AS int32 = _ + 18208, 18392, 3752, 19627, 18521, 18435, _ + 23325, 19286, 18159, 19073, 18190, 19629, _ + 19929, 21374, 18212, 18656 + +DATA decoder AS int32 = _ + -128, -127, -9, -160, -125, -151, _ + -198, -129, -126, -140, -111, -199, _ + -146, -192, -125, -129, 113, 102, _ + 10, 106, 96, 110, 83, 86, _ + 101, 96, 95, 62, 124, 115, _ + 101, 103, 114, 122, 25, 121, _ + 105, 113, 104, 113, 112, 113, _ + 102, 86, 104, 132, 98, 113, _ + 124, 135, -1, 134, 118, 129, _ + 105, 120, 110, 115, 108, 65, _ + 116, 119, 124, 124, 111, 115, _ + 18, 135, 112, 136, 94, 112, _ + 107, 125, 98, 77, 105, 139, _ + 114, 111, 118, 123, -6, 122, _ + 94, 112, 107, 125, 116, 113, _ + 102, 68, 107, 137, 113, 113, _ + 102, 104, 17, 120, 107, 107, _ + 99, 105, 107, 108, 94, 94, _ + 107, 134, 96, 112, 127, 114, _ + 43, 132, 111, 125, 106, 120, _ + 132, 123, 118, 89, 113, 133, _ + 117, 131, 112, 119, 1, 125, _ + 110, 130, 105, 116, 115, 109, _ + 102, 95, 107, 142, 105, 115, _ + -137, -135, -16, -166, -133, -152, _ + -209, -142, -122, -142, -121, -185, _ + -152, -198, -139, -141, 115, 113, _ + 36, 104, 98, 120, 76, 94, _ + 107, 122, 97, 66, 133, 122, _ + 107, 112, 107, 128, -22, 122, _ + 105, 128, 116, 113, 113, 109, _ + 99, 103, 97, 152, 108, 119, _ + 116, 134, 8, 128, 110, 125, _ + 89, 117, 109, 106, 100, 83, _ + 115, 114, 114, 111, 125, 129, _ + -7, 136, 117, 137, 124, 118, _ + 119, 119, 108, 83, 129, 149, _ + 122, 130, 120, 121, -14, 127, _ + 100, 106, 113, 117, 110, 112, _ + 106, 76, 119, 129, 112, 106, _ + 119, 118, -26, 137, 124, 135, _ + 105, 124, 123, 129, 114, 132, _ + 121, 145, 113, 121, 122, 97, _ + -11, 122, 110, 109, 97, 118, _ + 112, 106, 111, 73, 133, 130, _ + 112, 123, 133, 140, 12, 135, _ + 112, 131, 116, 125, 125, 129, _ + 114, 121, 106, 153, 120, 119 + +DATA decoderBias AS int32 = _ + 387878561, 8621915, 9365853, 10032520, 10514535, 10182099, _ + 9061429, 10778495, 9785349, 387767339, 9125238, 9920025, _ + 9813639, 11515426, 9815518, 11022095, 10193590, 10897366 + +dim draftScores(9) +dim logits(17) + +' Gods of the Arena 2026.9.21.3 compatibility layer. +' Draft a balanced roster, spend ability points, buy back, and keep the +' previously trained neural clock relative to the start of combat. +dim draftF(31) +sub chooseHero() + if draftTurnId <> selfId then + exit sub + end if + for draftClass = 0 to 9 + draftF(draftClass) = heroAvailable(draftClass) + draftF(10 + draftClass) = 0 + draftF(20 + draftClass) = 0 + next draftClass + draftAllyCount = 0 + draftEnemyCount = 0 + for draftPlayer = 0 to draftPlayerCount() - 1 + draftPicked = draftedClass(draftPlayerId(draftPlayer)) + if draftPicked >= 0 then + if draftPlayerTeam(draftPlayer) = selfTeam then + draftF(10 + draftPicked) = 1 + draftAllyCount = draftAllyCount + 1 + else + draftF(20 + draftPicked) = 1 + draftEnemyCount = draftEnemyCount + 1 + end if + end if + next draftPlayer + draftF(30) = draftAllyCount / 5 + draftF(31) = draftEnemyCount / 5 + linear(draftF, draftWeights, draftBias, draftScores, 32, 10) + draftBestClass = argmaxMasked(draftScores, draftF, 10) + if draftBestClass >= 0 then + draftHero(draftBestClass) + end if +end sub + +if drafting then + chooseHero() + end +end if + +if battleStarted = 0 then + battleStartTick = worldTick + battleStarted = 1 +end if +battleTick = worldTick - battleStartTick + +if selfHp <= 0 then + buybackCost = buybackPrice() + if buybackCost > 0 and selfGold >= buybackCost then + buyback() + end if + end +end if + +' Spend every currently legal point. The trained actor favors W, then E/Q; +' take the ultimate whenever its level gate opens. +for upgrade = 1 to 4 + if canLevelAbility(3) then + levelAbility(3) + elseif canLevelAbility(1) then + levelAbility(1) + elseif canLevelAbility(2) then + levelAbility(2) + elseif canLevelAbility(0) then + levelAbility(0) + end if +next upgrade + +dim f(27) +bestId = 0 +bestDistance = 2147483647 +objectiveId = 0 +objectiveKind = 0 +siegeThreatId = 0 +siegeThreatHp = 2147483647 +objectiveDistance = 2147483647 +heroId = 0 +heroDistance = 2147483647 +enemyX = selfX +enemyY = selfY +enemyHp = 0 +enemyKind = 0 +enemyAttackTarget = 0 +routeOpeningX = 71 +routeOpeningY = 12 +if selfTeam = 1 then + routeOpeningX = 45 + routeOpeningY = 104 +end if +routeGroupCount = 0 +if selfTeam = 0 then + gateHomeX = 105 + gateHomeY = 10 +else + gateHomeX = 10 + gateHomeY = 105 +end if +gateThreatDistance = 2147483647 +gateThreatSeen = 0 +gateThreatX = gateHomeX +reserveHome = 0 +allyCount = 0 +allyX = 0 +allyY = 0 +maxAllyDistance = 0 +index = 0 +while index < objectCount() + if objectAlive(index) then + if objectTeam(index) = selfTeam and objectKind(index) = 1 then + reserveHome = 1 + reserveHomeId = objectId(index) + reserveX = objectX(index) + reserveY = objectY(index) + end if + if objectTeam(index) = selfTeam and objectKind(index) = 2 then + allyCount = allyCount + 1 + routeDx = objectX(index) - routeOpeningX + routeDy = objectY(index) - routeOpeningY + if routeDx * routeDx + routeDy * routeDy <= 16 then + routeGroupCount = routeGroupCount + 1 + end if + allyX = allyX + objectX(index) + allyY = allyY + objectY(index) + dx = objectX(index) - selfX + dy = objectY(index) - selfY + distance = dx * dx + dy * dy + if distance > maxAllyDistance then + maxAllyDistance = distance + end if + end if + if objectTeam(index) <> selfTeam then + if battleTick < 1000 and (objectKind(index) = 2 or objectKind(index) = 3) then + gateDx = objectX(index) - gateHomeX + gateDy = objectY(index) - gateHomeY + gateDistance = gateDx * gateDx + gateDy * gateDy + if gateDistance < gateThreatDistance then + gateThreatDistance = gateDistance + gateThreatSeen = 1 + gateThreatX = objectX(index) + end if + end if + dx = objectX(index) - selfX + dy = objectY(index) - selfY + distance = dx * dx + dy * dy + if distance < bestDistance then + bestDistance = distance + bestId = objectId(index) + enemyX = objectX(index) + enemyY = objectY(index) + enemyHp = objectHp(index) + enemyKind = objectKind(index) + enemyAttackTarget = objectTarget(index) + end if + if objectKind(index) = 2 and objectTarget(index) = selfId then + siegeRange = selfAttackRange \ 1000 + if distance * 3600 <= siegeRange * siegeRange and objectHp(index) < siegeThreatHp then + siegeThreatId = objectId(index) + siegeThreatHp = objectHp(index) + end if + end if + if objectKind(index) = 1 or objectKind(index) = 4 then + if distance < objectiveDistance then + objectiveDistance = distance + objectiveId = objectId(index) + objectiveKind = objectKind(index) + end if + end if + if objectKind(index) = 2 and distance < heroDistance then + heroDistance = distance + heroId = objectId(index) + ringHeroX = objectX(index) + ringHeroY = objectY(index) + ringHeroTarget = objectTarget(index) + end if + end if + end if + index = index + 1 +wend +if allyCount > 0 then + allyX = allyX \ allyCount + allyY = allyY \ allyCount +else + allyX = selfX + allyY = selfY +end if +f(0) = selfHp * 100 \ selfMaxHp +f(1) = 0 +if selfMaxMana > 0 then + f(1) = selfMana * 100 \ selfMaxMana +end if +f(2) = maxAllyDistance \ 100 +f(3) = selfLevel * 5 +f(4) = allyX - selfX +f(5) = allyY - selfY +f(6) = enemyX - selfX +f(7) = enemyY - selfY +f(8) = enemyHp \ 10 +f(9) = enemyKind * 25 +if selfTeam = 1 then + f(4) = 0 - f(4) + f(5) = 0 - f(5) + f(6) = 0 - f(6) + f(7) = 0 - f(7) +end if +f(10) = battleTick \ 288 +f(11) = abilityCharges(0) * 25 +f(12) = abilityCharges(1) * 25 +f(13) = abilityCharges(2) * 25 +f(14) = abilityCharges(3) * 25 +f(15 + selfClass) = 100 +f(25) = battleTick \ 16 +if battleTick < 1000 and gateThreatSeen = 1 then + gateEarlyThreatX = gateThreatX + gateEarlyThreatSeen = 1 +end if +f(26) = 0 +if gateEarlyThreatSeen = 1 then + f(26) = gateEarlyThreatX - gateHomeX + if selfTeam = 1 then + f(26) = 0 - f(26) + end if +end if +index = 0 +while index < 27 + if f(index) > 100 then + f(index) = 100 + end if + if f(index) < -100 then + f(index) = -100 + end if + index = index + 1 +wend +dim h(16) +neuralActionCountdown = neuralActionCountdown - 1 +if neuralActionCountdown <= 0 then + neuralActionCountdown = 4 + linear(f, encoder, encoderBias, h, 25, 16) + relu(h, 16) + linear(h, decoder, decoderBias, logits, 16, 18) + decision = argmax(logits, 18) + ' Preserve the original sentinel edge case for int32 wraparound. + if logits(decision) <= -2147483647 then + decision = 0 + end if +end if +if decision = 18 then + combatDecision = 8 +else +combatDecision = decision +end if +objectiveBuild = 0 +if decision >= 9 then + combatDecision = decision - 9 + objectiveBuild = 1 +end if + +routeFallback = 0 +if combatDecision = 2 or (combatDecision = 8 and bestId = 0) then + if (objectiveId = 0 or objectiveDistance > 300) and (heroId = 0 or heroDistance > 700) then + routeFallback = 1 + end if +end if +routeDx = selfX - routeOpeningX +routeDy = selfY - routeOpeningY +if battleTick < 3500 and routeFallback and routeGroupCount >= 3 then + if routeDx * routeDx + routeDy * routeDy <= 16 then + groupDeparture = 1 + end if +end if + +if combatDecision = 8 then + if objectiveId <> 0 and objectiveDistance <= 300 then + attackTarget(objectiveId) + else + if heroId <> 0 and heroDistance <= 700 then + attackTarget(heroId) + else + if bestId <> 0 then + attackTarget(bestId) + else + if selfTeam = 0 then + if battleTick < 3500 and groupDeparture = 0 then + walkTo(71, 12) + else + if battleTick < 4500 then + walkTo(9, 33) + else + walkTo(11, 106) + end if + end if + else + if battleTick < 3500 and groupDeparture = 0 then + walkTo(45, 104) + else + if battleTick < 4500 then + walkTo(107, 83) + else + walkTo(105, 10) + end if + end if + end if + end if + end if + end if +else + if bestId <> 0 then + attackTarget(bestId) + else + walkTo(64, 64) + end if +end if +if selfClass = 7 and heroId <> 0 and ringHeroTarget = selfId then + if heroDistance > 25 and heroDistance <= 49 then + castPoint(3, (selfX + ringHeroX) \ 2, (selfY + ringHeroY) \ 2) + end if +end if +hasHeal = 0 +elixirCount = 0 +hasMana = 0 +hasPoison = 0 +hasGear = 0 +hasDagger = 0 +hasSword = 0 +hasArmor = 0 +hasAxe = 0 +hasBook = 0 +emptySlot = 0 +slot = 0 +while slot < 6 + id = itemId(slot) + if id = 0 then + emptySlot = 1 + end if + if id = 1 or id = 2 then + hasHeal = 1 + if selfHp * 5 < selfMaxHp * 3 and itemCooldown(slot) = 0 then + useItem(slot) + end if + end if + if id = 2 then + elixirCount = elixirCount + itemCount(slot) + end if + if id = 3 or id = 22 then + hasMana = 1 + if objectiveBuild = 0 and selfMana * 5 < selfMaxMana * 2 and itemCooldown(slot) = 0 then + useItem(slot) + end if + end if + if id = 4 then + hasPoison = 1 + if objectiveBuild = 0 and bestId <> 0 then + useItem(slot) + end if + end if + if id >= 5 and id <= 20 then + hasGear = 1 + end if + if id = 11 then + hasDagger = 1 + end if + if id = 13 then + hasSword = 1 + end if + if id = 16 then + hasArmor = 1 + end if + if id = 18 then + hasAxe = 1 + end if + if id = 20 then + hasBook = 1 + end if + slot = slot + 1 +wend +if canShop() then +' Stock immediate healing after the opening build. Damage interrupts +' ordinary potion recovery, and a full six-slot pack cannot buy a cure. +if selfClass = 2 and selfLevel >= 4 and elixirCount < 3 then + if selfGold >= 75 and (elixirCount > 0 or emptySlot <> 0) then + buyItem(2) + hasHeal = 1 + end if +end if +if selfHp * 2 < selfMaxHp and hasHeal = 0 then + if selfGold >= 50 then + buyItem(2) + end if + if selfGold >= 30 then + buyItem(1) + end if +end if +if objectiveBuild = 0 then +if selfMaxMana > 0 then + if selfMana * 2 < selfMaxMana and hasMana = 0 then + if selfGold >= 45 then + buyItem(22) + end if + end if +end if +if bestId <> 0 and hasPoison = 0 then + if selfGold >= 40 then + buyItem(4) + end if +end if +if emptySlot <> 0 then + melee = 0 + ranged = 0 + magic = 0 + if selfClass = 0 or selfClass = 4 or selfClass = 5 or selfClass = 9 then + melee = 1 + end if + if selfClass = 1 or selfClass = 6 then + ranged = 1 + end if + if selfClass = 2 or selfClass = 3 or selfClass = 7 or selfClass = 8 then + magic = 1 + end if + if melee = 1 then + if hasGear = 0 and selfGold >= 70 then + buyItem(7) + end if + if selfGold >= 80 then + buyItem(5) + end if + if selfGold >= 110 then + buyItem(11) + end if + if selfGold >= 150 then + buyItem(13) + end if + if selfGold >= 180 then + buyItem(18) + end if + end if + if ranged = 1 then + if hasGear = 0 and selfGold >= 100 then + buyItem(8) + end if + if selfGold >= 150 then + buyItem(14) + end if + if selfGold >= 180 then + buyItem(19) + end if + end if + if magic = 1 then + if hasGear = 0 and selfGold >= 140 then + buyItem(12) + end if + if selfGold >= 120 then + buyItem(10) + end if + if selfGold >= 170 then + buyItem(17) + end if + if selfGold >= 190 then + buyItem(20) + end if + end if + if selfGold >= 90 then + buyItem(6) + end if + if selfGold >= 120 then + buyItem(9) + end if +end if +else + if emptySlot <> 0 then + if hasDagger = 0 and selfGold >= 110 then + buyItem(11) + end if + if hasSword = 0 and selfGold >= 150 then + buyItem(13) + end if + if hasArmor = 0 and selfGold >= 160 then + buyItem(16) + end if + if hasAxe = 0 and selfGold >= 180 then + buyItem(18) + end if + if hasBook = 0 and selfGold >= 190 then + buyItem(20) + end if + end if +end if +end if +if combatDecision = 1 then + if selfTeam = 0 then + walkTo(mapWidth - 10, 10) + else + walkTo(10, mapHeight - 10) + end if +end if +if combatDecision = 2 then + if objectiveId <> 0 and objectiveDistance <= 300 then + attackTarget(objectiveId) + else + if heroId <> 0 and heroDistance <= 700 then + attackTarget(heroId) + else + if selfTeam = 0 then + if battleTick < 3500 and groupDeparture = 0 then + walkTo(71, 12) + else + if battleTick < 4500 then + walkTo(9, 33) + else + walkTo(11, 106) + end if + end if + else + if battleTick < 3500 and groupDeparture = 0 then + walkTo(45, 104) + else + if battleTick < 4500 then + walkTo(107, 83) + else + walkTo(105, 10) + end if + end if + end if + end if + end if +end if +if combatDecision >= 3 and combatDecision <= 6 then + if bestId <> 0 then + castTarget(combatDecision - 3, bestId) + else + castTarget(combatDecision - 3, selfId) + end if +end if +if combatDecision = 7 then + walkTo(allyX, allyY) +end if + +if decision = 18 and (bestId = 0 or bestDistance > 64) then + specialistX = mapWidth - 1 - gateHomeX + specialistY = gateHomeY + specialistDx = selfX - specialistX + specialistDy = selfY - specialistY + if specialistDx * specialistDx + specialistDy * specialistDy <= 64 then + perimeterStage = 1 + end if + if perimeterStage <> 0 then + specialistY = mapHeight - 1 - gateHomeY + end if + walkTo(specialistX, specialistY) +end if + +if (combatDecision = 2 or combatDecision = 8) and objectiveKind = 4 and objectiveDistance <= 300 and siegeThreatId <> 0 then + attackTarget(siegeThreatId) +end if + +if reserveHome <> 0 then + reserveGuards = 0 + reserveGuardCritical = 0 + reserveNearest = 1 + reserveDx = selfX - reserveX + reserveDy = selfY - reserveY + reserveDistance = reserveDx * reserveDx + reserveDy * reserveDy + reserveTarget = 0 + reserveTargetDistance = 401 + reserveDirect = 0 + reserveDirectDistance = 401 + reserveHero = 0 + reserveHeroDistance = 401 + reserveFinish = 0 + reserveIndex = 0 + while reserveIndex < objectCount() + reserveDx = objectX(reserveIndex) - reserveX + reserveDy = objectY(reserveIndex) - reserveY + reserveObjectHome = reserveDx * reserveDx + reserveDy * reserveDy + if objectTeam(reserveIndex) = selfTeam then + if objectKind(reserveIndex) = 4 and objectHp(reserveIndex) > 0 and reserveObjectHome <= 100 then + reserveGuards = reserveGuards + 1 + if objectHp(reserveIndex) <= 780 then + reserveGuardCritical = 1 + end if + end if + if objectKind(reserveIndex) = 2 and objectAlive(reserveIndex) and reserveObjectHome < reserveDistance then + reserveNearest = 0 + end if + else + if objectAlive(reserveIndex) then + if (objectKind(reserveIndex) = 2 or objectKind(reserveIndex) = 3) and reserveObjectHome < reserveDirectDistance then + if objectTarget(reserveIndex) = reserveHomeId then + reserveDirect = objectId(reserveIndex) + reserveDirectDistance = reserveObjectHome + end if + end if + if objectKind(reserveIndex) = 2 and reserveObjectHome < reserveHeroDistance then + reserveHero = objectId(reserveIndex) + reserveHeroDistance = reserveObjectHome + end if + if (objectKind(reserveIndex) = 2 or objectKind(reserveIndex) = 3) and reserveObjectHome < reserveTargetDistance then + reserveTargetDistance = reserveObjectHome + reserveTarget = objectId(reserveIndex) + end if + if objectKind(reserveIndex) = 1 and objectHp(reserveIndex) <= selfAttackDamage then + reserveDx = objectX(reserveIndex) - selfX + reserveDy = objectY(reserveIndex) - selfY + reserveRange = selfAttackRange \ 1000 + if (reserveDx * reserveDx + reserveDy * reserveDy) * 3600 <= reserveRange * reserveRange then + reserveFinish = 1 + end if + end if + end if + end if + reserveIndex = reserveIndex + 1 + wend + if reserveHero <> 0 then + reserveTarget = reserveHero + end if + if reserveDirect <> 0 then + reserveTarget = reserveDirect + end if + if (reserveGuards <= 1 or (reserveGuardCritical <> 0 and reserveDistance <= 900)) and reserveNearest <> 0 and reserveFinish = 0 then + if reserveDistance <= 400 and reserveTarget <> 0 then + attackTarget(reserveTarget) + else + walkTo(reserveX, reserveY) + end if + end if +end if + +if recoveryInitialized <> 0 and selfAttacksLanded > recoveryLastHit then + walkTo(selfX, selfY) +end if +recoveryLastHit = selfAttacksLanded +recoveryInitialized = 1 diff --git a/examples/heartleaf/bots.nim b/examples/heartleaf/bots.nim index 6d4129d9..06047e97 100644 --- a/examples/heartleaf/bots.nim +++ b/examples/heartleaf/bots.nim @@ -9,6 +9,7 @@ ## commands only queue orders. import + polyworld/arrays, bassy, polyworld/[bodies, profiles], content, @@ -137,6 +138,7 @@ proc buildVillagerHost*(slot: int32): Host = ## live instance, because `initRuntime` validates every binding's arity ## and work cost against what the program was compiled with. result = initHost() + result.addArrayFunctions() for name in VillagerDataNames: discard result.addData(name) diff --git a/examples/light_vs_dark/bots.nim b/examples/light_vs_dark/bots.nim index c7ce0985..b0ce3f66 100644 --- a/examples/light_vs_dark/bots.nim +++ b/examples/light_vs_dark/bots.nim @@ -11,6 +11,7 @@ ## kill things, and it is where fog of war is applied. import + polyworld/arrays, bassy, polyworld/[bodies, metrics, profiles], content, @@ -280,6 +281,7 @@ proc buildOverlordHost*(playerId: int32): Host = ## cost far more than their own cycles, so a script's budget prices its ## demand on the simulation rather than only its own arithmetic. result = initHost() + result.addArrayFunctions() for name in OverlordDataNames: discard result.addData(name) diff --git a/experiments/neural-policy/bench_neural.nim b/experiments/neural-policy/bench_neural.nim new file mode 100644 index 00000000..a1202b1b --- /dev/null +++ b/experiments/neural-policy/bench_neural.nim @@ -0,0 +1,56 @@ +import + std/[os, strutils], + bassy, benchy, + polyworld/arrays + +const Directory = currentSourcePath().parentDir + +let + original = readFile(Directory / "original.bas") + converted = readFile( + Directory / "../../examples/gods_of_the_arena/players/neural.bas" + ) + first = original.find("h(0) = ") + last = original.find("\nend if\nend if\nif decision = 18", first) + scalarSource = "dim f(27)\ndim h(16)\n" & + original[first ..< last + "\nend if".len] + nativeSource = converted[0 ..< converted.find("dim draftScores(9)")] & """ +dim f(27) +dim h(16) +dim logits(17) +linear(f, encoder, encoderBias, h, 25, 16) +relu(h, 16) +linear(h, decoder, decoderBias, logits, 16, 18) +decision = argmax(logits, 18) +if logits(decision) <= -2147483647 then decision = 0 +""" +var schema = initHost() +schema.addArrayFunctions() +var + scalar = initRuntime(compile(scalarSource)) + native = initRuntime(compile(nativeSource, schema), schema) +for trial in 0 ..< 256: + for i in 0 ..< 25: + let value = int32((trial * 97 + i * 37) mod 257 - 128) + scalar.setArray("f", int32(i), value) + native.setArray("f", int32(i), value) + scalar.restart() + native.restart() + discard scalar.run() + discard native.run() + doAssert scalar.getGlobal("decision") == native.getGlobal("decision") + for i in 0 ..< 16: + doAssert scalar.getArray("h", int32(i)) == native.getArray("h", int32(i)) +echo "Combat inference parity: 256 feature vectors" +echo "Scalar instructions/work: ", scalar.instructionsUsed, "/", scalar.workUsed +echo "Native instructions/work: ", native.instructionsUsed, "/", native.workUsed +echo "Scalar/native VM bytes: ", scalar.memoryBytes, "/", native.memoryBytes + +timeIt "1000 interpreted combat inferences", 20: + for i in 0 ..< 1000: + scalar.restart() + discard scalar.run() +timeIt "1000 native combat inferences", 20: + for i in 0 ..< 1000: + native.restart() + discard native.run() diff --git a/experiments/neural-policy/compare.nim b/experiments/neural-policy/compare.nim new file mode 100644 index 00000000..c10220d5 --- /dev/null +++ b/experiments/neural-policy/compare.nim @@ -0,0 +1,29 @@ +import + std/[os, strutils], + ../../examples/gods_of_the_arena/replays + +let paths = commandLineParams() +doAssert paths.len == 2, "pass the original and converted replay paths" +let + original = loadReplay(paths[0]) + converted = loadReplay(paths[1]) +doAssert original.header == converted.header, "match setup differs" +doAssert original.actions == converted.actions, "action tapes differ" +doAssert original.hashes == converted.hashes, "per-tick state hashes differ" +doAssert original.metrics.tickRate == converted.metrics.tickRate +doAssert original.metrics.interval == converted.metrics.interval +doAssert original.metrics.frames.len == converted.metrics.frames.len +doAssert original.metrics.final.len == converted.metrics.final.len +for i, frame in original.metrics.frames: + doAssert frame.tick == converted.metrics.frames[i].tick + doAssert frame.rows.len == converted.metrics.frames[i].rows.len +var comparable = converted +comparable.config.players = original.config.players +comparable.metrics = original.metrics +doAssert original == comparable, "unexpected non-telemetry difference" +echo "Identical actions: ", original.actions.len +echo "Identical per-tick hashes: ", original.hashes.len +echo "Final state hash: ", original.hashes[^1].toHex(16) +echo "Only player labels and instruction telemetry may differ." +echo "Original final CPU percentages: ", original.metrics.final +echo "Converted final CPU percentages: ", converted.metrics.final diff --git a/experiments/neural-policy/convert.py b/experiments/neural-policy/convert.py new file mode 100644 index 00000000..b5dec31c --- /dev/null +++ b/experiments/neural-policy/convert.py @@ -0,0 +1,109 @@ +"""Extract the supplied v63 policy's literals without changing its game code.""" + +import argparse +import hashlib +from pathlib import Path +import re + + +def data(name, kind, rows): + """Format one row-major DATA array using exact source literal strings.""" + values = [value for row in rows for value in row] + lines = [f"DATA {name} AS {kind} = _"] + for offset in range(0, len(values), 6): + line = " " + ", ".join(values[offset:offset + 6]) + if offset + 6 < len(values): + line += ", _" + lines.append(line) + return "\n".join(lines) + "\n" + + +def convert(source): + """Convert only the three affine layers, ReLU, and score selection.""" + draft_start = source.index(" draftBestClass = -1\n") + draft_end = source.index(" if draftBestClass >= 0 then\n") + blocks = re.findall( + r" if draftF\((\d+)\) then\n(.*?)\n end if", + source[draft_start:draft_end], re.S, + ) + assert [int(index) for index, _ in blocks] == list(range(10)) + draft_weights, draft_biases = [], [] + for _, body in blocks: + draft_biases.append(re.search(r"draftScore = (-?[\d.]+)", body)[1]) + terms = re.findall( + r"draftScore = draftScore \+ draftF\((\d+)\) \* (-?[\d.]+)", + body, + ) + indices = [int(index) for index, _ in terms] + assert indices == sorted(set(indices)) and max(indices) < 32 + row = ["0.000000"] * 32 + for index, value in terms: + row[int(index)] = value + draft_weights.append(row) + + encoder_rows = re.findall(r"^h\((\d+)\) = (-?\d+)(.*)$", source, re.M) + decoder_rows = re.findall(r"^score = (-?\d+)(.*)$", source, re.M) + assert [int(index) for index, _, _ in encoder_rows] == list(range(16)) + assert len(decoder_rows) == 18 + encoder_weights, encoder_biases = [], [] + decoder_weights, decoder_biases = [], [] + for _, bias, body in encoder_rows: + terms = re.findall(r"f\((\d+)\) \* \((-?\d+)\)", body) + assert [int(index) for index, _ in terms] == list(range(25)) + encoder_weights.append([value for _, value in terms]) + encoder_biases.append(bias) + for bias, body in decoder_rows: + terms = re.findall(r"h\((\d+)\) \* \((-?\d+)\)", body) + assert [int(index) for index, _ in terms] == list(range(16)) + decoder_weights.append([value for _, value in terms]) + decoder_biases.append(bias) + + draft = """ linear(draftF, draftWeights, draftBias, draftScores, 32, 10) + draftBestClass = argmaxMasked(draftScores, draftF, 10) +""" + source = source[:draft_start] + draft + source[draft_end:] + start = source.index("h(0) = ") + end = source.index("\nend if", source.index(" decision = 17\n", start)) + # Keep the END IF for the four-tick inference countdown. + end = source.index("\nend if", end + 1) + combat = """ linear(f, encoder, encoderBias, h, 25, 16) + relu(h, 16) + linear(h, decoder, decoderBias, logits, 16, 18) + decision = argmax(logits, 18) + ' Preserve the original sentinel edge case for int32 wraparound. + if logits(decision) <= -2147483647 then + decision = 0 + end if""" + source = source[:start] + combat + source[end:] + header = """' Richard's v63 policy using native, metered array arithmetic. +' Generated by convert.py. Game observations and commands are unchanged. +' DATA initializes once. DIM retains its inclusive BASIC upper bound. +' Matrices are row-major: weights(outputIndex * inputCount + inputIndex). + +""" + for name, kind, rows in [ + ("draftWeights", "fixed32", draft_weights), + ("draftBias", "fixed32", [draft_biases]), + ("encoder", "int32", encoder_weights), + ("encoderBias", "int32", [encoder_biases]), + ("decoder", "int32", decoder_weights), + ("decoderBias", "int32", [decoder_biases]), + ]: + header += data(name, kind, rows) + "\n" + header += "dim draftScores(9)\ndim logits(17)\n\n" + return header + source + + +def main(): + """Convert an unchanged input file and print its provenance digest.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("source", type=Path) + parser.add_argument("destination", type=Path) + arguments = parser.parse_args() + source = arguments.source.read_bytes() + arguments.destination.write_text(convert(source.decode("utf-8"))) + print("Source SHA-256:", hashlib.sha256(source).hexdigest()) + + +if __name__ == "__main__": + main() diff --git a/experiments/neural-policy/original.bas b/experiments/neural-policy/original.bas new file mode 100644 index 00000000..23714260 --- /dev/null +++ b/experiments/neural-policy/original.bas @@ -0,0 +1,1147 @@ +' Gods of the Arena 2026.9.21.3 compatibility layer. +' Draft a balanced roster, spend ability points, buy back, and keep the +' previously trained neural clock relative to the start of combat. +dim draftF(31) +sub chooseHero() + if draftTurnId <> selfId then + exit sub + end if + for draftClass = 0 to 9 + draftF(draftClass) = heroAvailable(draftClass) + draftF(10 + draftClass) = 0 + draftF(20 + draftClass) = 0 + next draftClass + draftAllyCount = 0 + draftEnemyCount = 0 + for draftPlayer = 0 to draftPlayerCount() - 1 + draftPicked = draftedClass(draftPlayerId(draftPlayer)) + if draftPicked >= 0 then + if draftPlayerTeam(draftPlayer) = selfTeam then + draftF(10 + draftPicked) = 1 + draftAllyCount = draftAllyCount + 1 + else + draftF(20 + draftPicked) = 1 + draftEnemyCount = draftEnemyCount + 1 + end if + end if + next draftPlayer + draftF(30) = draftAllyCount / 5 + draftF(31) = draftEnemyCount / 5 + draftBestClass = -1 + draftBestScore = -2147483647 + if draftF(0) then + draftScore = -0.252276 + draftScore = draftScore + draftF(0) * -0.252276 + draftScore = draftScore + draftF(1) * 0.208388 + draftScore = draftScore + draftF(2) * 0.165655 + draftScore = draftScore + draftF(3) * -0.077038 + draftScore = draftScore + draftF(4) * -0.307499 + draftScore = draftScore + draftF(5) * 0.393190 + draftScore = draftScore + draftF(6) * -0.041722 + draftScore = draftScore + draftF(7) * -0.247450 + draftScore = draftScore + draftF(8) * -0.267264 + draftScore = draftScore + draftF(9) * 0.060881 + draftScore = draftScore + draftF(11) * -0.056464 + draftScore = draftScore + draftF(12) * -0.558547 + draftScore = draftScore + draftF(13) * 0.238349 + draftScore = draftScore + draftF(14) * 0.148874 + draftScore = draftScore + draftF(15) * -0.351042 + draftScore = draftScore + draftF(16) * 0.022783 + draftScore = draftScore + draftF(17) * 0.133934 + draftScore = draftScore + draftF(18) * -0.148994 + draftScore = draftScore + draftF(19) * -0.127461 + draftScore = draftScore + draftF(21) * -0.404200 + draftScore = draftScore + draftF(22) * 0.140616 + draftScore = draftScore + draftF(23) * -0.413587 + draftScore = draftScore + draftF(24) * -0.093651 + draftScore = draftScore + draftF(25) * -0.294424 + draftScore = draftScore + draftF(26) * -0.233337 + draftScore = draftScore + draftF(27) * -0.138760 + draftScore = draftScore + draftF(28) * 0.163981 + draftScore = draftScore + draftF(29) * -0.185696 + draftScore = draftScore + draftF(30) * -0.139714 + draftScore = draftScore + draftF(31) * -0.291812 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 0 + end if + end if + if draftF(1) then + draftScore = 0.220766 + draftScore = draftScore + draftF(0) * 0.252661 + draftScore = draftScore + draftF(1) * 0.220766 + draftScore = draftScore + draftF(2) * -0.066995 + draftScore = draftScore + draftF(3) * 0.046584 + draftScore = draftScore + draftF(4) * -0.074833 + draftScore = draftScore + draftF(5) * 0.282953 + draftScore = draftScore + draftF(6) * -0.316809 + draftScore = draftScore + draftF(7) * 0.260309 + draftScore = draftScore + draftF(8) * 0.498069 + draftScore = draftScore + draftF(9) * 0.607530 + draftScore = draftScore + draftF(10) * -0.054461 + draftScore = draftScore + draftF(12) * 0.185443 + draftScore = draftScore + draftF(13) * 0.307806 + draftScore = draftScore + draftF(14) * 0.073798 + draftScore = draftScore + draftF(15) * 0.095548 + draftScore = draftScore + draftF(16) * -0.624112 + draftScore = draftScore + draftF(17) * -0.128341 + draftScore = draftScore + draftF(18) * 0.116202 + draftScore = draftScore + draftF(19) * -0.113738 + draftScore = draftScore + draftF(20) * 0.022566 + draftScore = draftScore + draftF(22) * 0.102318 + draftScore = draftScore + draftF(23) * -0.133625 + draftScore = draftScore + draftF(24) * 0.221801 + draftScore = draftScore + draftF(25) * -0.157735 + draftScore = draftScore + draftF(26) * 1.161687 + draftScore = draftScore + draftF(27) * 0.088798 + draftScore = draftScore + draftF(28) * -0.393505 + draftScore = draftScore + draftF(29) * -0.273026 + draftScore = draftScore + draftF(30) * -0.028371 + draftScore = draftScore + draftF(31) * 0.127856 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 1 + end if + end if + if draftF(2) then + draftScore = 0.306957 + draftScore = draftScore + draftF(0) * 0.160424 + draftScore = draftScore + draftF(1) * -0.457565 + draftScore = draftScore + draftF(2) * 0.306957 + draftScore = draftScore + draftF(3) * -0.351070 + draftScore = draftScore + draftF(4) * 0.501679 + draftScore = draftScore + draftF(5) * 0.772043 + draftScore = draftScore + draftF(6) * -0.220549 + draftScore = draftScore + draftF(7) * -0.161796 + draftScore = draftScore + draftF(8) * 0.448017 + draftScore = draftScore + draftF(9) * -0.150996 + draftScore = draftScore + draftF(10) * 0.136125 + draftScore = draftScore + draftF(11) * 0.178068 + draftScore = draftScore + draftF(13) * 0.247534 + draftScore = draftScore + draftF(14) * -0.188988 + draftScore = draftScore + draftF(15) * -0.238955 + draftScore = draftScore + draftF(16) * 0.883443 + draftScore = draftScore + draftF(17) * 0.034082 + draftScore = draftScore + draftF(18) * 0.088084 + draftScore = draftScore + draftF(20) * 0.010408 + draftScore = draftScore + draftF(21) * 0.586454 + draftScore = draftScore + draftF(23) * 0.410493 + draftScore = draftScore + draftF(24) * -0.005734 + draftScore = draftScore + draftF(25) * -0.226131 + draftScore = draftScore + draftF(26) * -0.355937 + draftScore = draftScore + draftF(27) * 0.434671 + draftScore = draftScore + draftF(28) * -0.229144 + draftScore = draftScore + draftF(29) * 0.457953 + draftScore = draftScore + draftF(30) * 0.227879 + draftScore = draftScore + draftF(31) * 0.216607 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 2 + end if + end if + if draftF(3) then + draftScore = -0.102972 + draftScore = draftScore + draftF(0) * 0.191406 + draftScore = draftScore + draftF(1) * -0.378401 + draftScore = draftScore + draftF(2) * -0.681596 + draftScore = draftScore + draftF(3) * -0.102972 + draftScore = draftScore + draftF(4) * 0.400384 + draftScore = draftScore + draftF(5) * -0.198847 + draftScore = draftScore + draftF(6) * -0.095249 + draftScore = draftScore + draftF(7) * -0.499520 + draftScore = draftScore + draftF(8) * 0.154836 + draftScore = draftScore + draftF(9) * 0.124309 + draftScore = draftScore + draftF(10) * -0.098351 + draftScore = draftScore + draftF(11) * 0.079512 + draftScore = draftScore + draftF(12) * 0.896409 + draftScore = draftScore + draftF(14) * -0.105452 + draftScore = draftScore + draftF(15) * -0.086497 + draftScore = draftScore + draftF(16) * -0.053812 + draftScore = draftScore + draftF(17) * 0.204039 + draftScore = draftScore + draftF(18) * -0.676251 + draftScore = draftScore + draftF(19) * 0.027087 + draftScore = draftScore + draftF(20) * -0.196028 + draftScore = draftScore + draftF(21) * 0.195917 + draftScore = draftScore + draftF(22) * -0.317785 + draftScore = draftScore + draftF(24) * -0.397904 + draftScore = draftScore + draftF(25) * 0.182371 + draftScore = draftScore + draftF(26) * 0.046089 + draftScore = draftScore + draftF(27) * 0.192509 + draftScore = draftScore + draftF(28) * 0.418443 + draftScore = draftScore + draftF(29) * -0.254368 + draftScore = draftScore + draftF(30) * 0.037337 + draftScore = draftScore + draftF(31) * -0.026151 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 3 + end if + end if + if draftF(4) then + draftScore = -0.279421 + draftScore = draftScore + draftF(0) * -0.615030 + draftScore = draftScore + draftF(1) * 0.124070 + draftScore = draftScore + draftF(2) * -0.215150 + draftScore = draftScore + draftF(3) * -0.227015 + draftScore = draftScore + draftF(4) * -0.279421 + draftScore = draftScore + draftF(5) * -0.805966 + draftScore = draftScore + draftF(6) * -0.051813 + draftScore = draftScore + draftF(7) * 0.012569 + draftScore = draftScore + draftF(8) * -0.390292 + draftScore = draftScore + draftF(9) * -0.465863 + draftScore = draftScore + draftF(10) * 0.349372 + draftScore = draftScore + draftF(11) * 0.302593 + draftScore = draftScore + draftF(12) * -0.114656 + draftScore = draftScore + draftF(13) * -0.015071 + draftScore = draftScore + draftF(15) * 0.281769 + draftScore = draftScore + draftF(16) * -0.040930 + draftScore = draftScore + draftF(17) * 0.011989 + draftScore = draftScore + draftF(18) * -0.098396 + draftScore = draftScore + draftF(19) * -0.247384 + draftScore = draftScore + draftF(20) * -0.013763 + draftScore = draftScore + draftF(21) * -0.706084 + draftScore = draftScore + draftF(22) * 0.050385 + draftScore = draftScore + draftF(23) * -0.037334 + draftScore = draftScore + draftF(25) * 0.244777 + draftScore = draftScore + draftF(26) * -0.186678 + draftScore = draftScore + draftF(27) * -0.303979 + draftScore = draftScore + draftF(28) * 0.209267 + draftScore = draftScore + draftF(29) * 0.433826 + draftScore = draftScore + draftF(30) * 0.085857 + draftScore = draftScore + draftF(31) * -0.061916 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 4 + end if + end if + if draftF(5) then + draftScore = -0.242457 + draftScore = draftScore + draftF(0) * -0.232312 + draftScore = draftScore + draftF(1) * -0.183645 + draftScore = draftScore + draftF(2) * -0.270940 + draftScore = draftScore + draftF(3) * -0.249030 + draftScore = draftScore + draftF(4) * -0.064811 + draftScore = draftScore + draftF(5) * -0.242457 + draftScore = draftScore + draftF(6) * -0.040038 + draftScore = draftScore + draftF(7) * -0.037761 + draftScore = draftScore + draftF(8) * -0.901328 + draftScore = draftScore + draftF(9) * 0.027995 + draftScore = draftScore + draftF(10) * -0.392084 + draftScore = draftScore + draftF(11) * -0.497526 + draftScore = draftScore + draftF(12) * 0.312257 + draftScore = draftScore + draftF(13) * 0.104801 + draftScore = draftScore + draftF(14) * -0.183950 + draftScore = draftScore + draftF(16) * 0.045312 + draftScore = draftScore + draftF(17) * 0.127481 + draftScore = draftScore + draftF(18) * 0.460752 + draftScore = draftScore + draftF(19) * -0.051012 + draftScore = draftScore + draftF(20) * 0.381939 + draftScore = draftScore + draftF(21) * 0.438713 + draftScore = draftScore + draftF(22) * -0.283774 + draftScore = draftScore + draftF(23) * -0.098228 + draftScore = draftScore + draftF(24) * 0.006305 + draftScore = draftScore + draftF(26) * -0.247731 + draftScore = draftScore + draftF(27) * -0.332176 + draftScore = draftScore + draftF(28) * 0.198120 + draftScore = draftScore + draftF(29) * -0.219440 + draftScore = draftScore + draftF(30) * -0.014794 + draftScore = draftScore + draftF(31) * -0.031255 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 5 + end if + end if + if draftF(6) then + draftScore = 0.714471 + draftScore = draftScore + draftF(0) * 0.727924 + draftScore = draftScore + draftF(1) * 0.905780 + draftScore = draftScore + draftF(2) * 0.451952 + draftScore = draftScore + draftF(3) * 0.593039 + draftScore = draftScore + draftF(4) * 0.580693 + draftScore = draftScore + draftF(5) * 0.784972 + draftScore = draftScore + draftF(6) * 0.714471 + draftScore = draftScore + draftF(7) * 0.539523 + draftScore = draftScore + draftF(8) * 0.567051 + draftScore = draftScore + draftF(9) * 0.609534 + draftScore = draftScore + draftF(10) * 0.213345 + draftScore = draftScore + draftF(11) * -0.291630 + draftScore = draftScore + draftF(12) * 0.069507 + draftScore = draftScore + draftF(14) * 0.103936 + draftScore = draftScore + draftF(15) * 0.023463 + draftScore = draftScore + draftF(17) * 0.043553 + draftScore = draftScore + draftF(18) * 0.061384 + draftScore = draftScore + draftF(19) * 0.061384 + draftScore = draftScore + draftF(20) * -0.226798 + draftScore = draftScore + draftF(21) * 0.100322 + draftScore = draftScore + draftF(22) * 0.193012 + draftScore = draftScore + draftF(23) * 0.121433 + draftScore = draftScore + draftF(24) * 0.029843 + draftScore = draftScore + draftF(25) * -0.093964 + draftScore = draftScore + draftF(27) * 0.131395 + draftScore = draftScore + draftF(28) * 0.086036 + draftScore = draftScore + draftF(29) * 0.043553 + draftScore = draftScore + draftF(30) * 0.056989 + draftScore = draftScore + draftF(31) * 0.076966 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 6 + end if + end if + if draftF(7) then + draftScore = 0.092192 + draftScore = draftScore + draftF(0) * 0.168151 + draftScore = draftScore + draftF(1) * -0.445483 + draftScore = draftScore + draftF(2) * -0.279111 + draftScore = draftScore + draftF(3) * 0.122942 + draftScore = draftScore + draftF(4) * -0.494774 + draftScore = draftScore + draftF(5) * -0.299634 + draftScore = draftScore + draftF(6) * -0.131236 + draftScore = draftScore + draftF(7) * 0.092192 + draftScore = draftScore + draftF(8) * 0.164071 + draftScore = draftScore + draftF(9) * -0.176015 + draftScore = draftScore + draftF(10) * 0.002602 + draftScore = draftScore + draftF(11) * 0.310725 + draftScore = draftScore + draftF(12) * -0.538125 + draftScore = draftScore + draftF(13) * 0.274210 + draftScore = draftScore + draftF(14) * 0.488313 + draftScore = draftScore + draftF(15) * -0.038538 + draftScore = draftScore + draftF(16) * 0.069213 + draftScore = draftScore + draftF(18) * 0.434066 + draftScore = draftScore + draftF(19) * 0.183095 + draftScore = draftScore + draftF(20) * -0.078561 + draftScore = draftScore + draftF(21) * 0.226950 + draftScore = draftScore + draftF(22) * 0.909428 + draftScore = draftScore + draftF(23) * -0.304960 + draftScore = draftScore + draftF(24) * 0.098653 + draftScore = draftScore + draftF(25) * 0.430365 + draftScore = draftScore + draftF(26) * 0.154216 + draftScore = draftScore + draftF(28) * -0.505944 + draftScore = draftScore + draftF(29) * 0.085113 + draftScore = draftScore + draftF(30) * 0.237112 + draftScore = draftScore + draftF(31) * 0.203052 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 7 + end if + end if + if draftF(8) then + draftScore = 0.041381 + draftScore = draftScore + draftF(0) * -0.294455 + draftScore = draftScore + draftF(1) * 0.111733 + draftScore = draftScore + draftF(2) * 0.787697 + draftScore = draftScore + draftF(3) * 0.595129 + draftScore = draftScore + draftF(4) * 0.080587 + draftScore = draftScore + draftF(5) * -0.130948 + draftScore = draftScore + draftF(6) * 0.222401 + draftScore = draftScore + draftF(7) * 0.450339 + draftScore = draftScore + draftF(8) * 0.041381 + draftScore = draftScore + draftF(9) * -0.138733 + draftScore = draftScore + draftF(10) * -0.194858 + draftScore = draftScore + draftF(11) * 0.316302 + draftScore = draftScore + draftF(12) * -0.090298 + draftScore = draftScore + draftF(13) * -1.080760 + draftScore = draftScore + draftF(14) * -0.178166 + draftScore = draftScore + draftF(15) * -0.011497 + draftScore = draftScore + draftF(16) * -0.011433 + draftScore = draftScore + draftF(17) * -0.360402 + draftScore = draftScore + draftF(19) * 0.268030 + draftScore = draftScore + draftF(20) * 0.530694 + draftScore = draftScore + draftF(21) * -0.386654 + draftScore = draftScore + draftF(22) * -0.656018 + draftScore = draftScore + draftF(23) * 0.527012 + draftScore = draftScore + draftF(24) * 0.138960 + draftScore = draftScore + draftF(25) * 0.183827 + draftScore = draftScore + draftF(26) * -0.169586 + draftScore = draftScore + draftF(27) * -0.048555 + draftScore = draftScore + draftF(29) * -0.087915 + draftScore = draftScore + draftF(30) * -0.268616 + draftScore = draftScore + draftF(31) * 0.006353 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 8 + end if + end if + if draftF(9) then + draftScore = -0.498641 + draftScore = draftScore + draftF(0) * -0.106492 + draftScore = draftScore + draftF(1) * -0.105644 + draftScore = draftScore + draftF(2) * -0.198469 + draftScore = draftScore + draftF(3) * -0.350568 + draftScore = draftScore + draftF(4) * -0.342004 + draftScore = draftScore + draftF(5) * -0.555305 + draftScore = draftScore + draftF(6) * -0.039455 + draftScore = draftScore + draftF(7) * -0.408405 + draftScore = draftScore + draftF(8) * -0.314541 + draftScore = draftScore + draftF(9) * -0.498641 + draftScore = draftScore + draftF(10) * 0.038310 + draftScore = draftScore + draftF(11) * -0.341580 + draftScore = draftScore + draftF(12) * -0.161989 + draftScore = draftScore + draftF(13) * -0.076870 + draftScore = draftScore + draftF(14) * -0.158366 + draftScore = draftScore + draftF(15) * 0.325749 + draftScore = draftScore + draftF(16) * -0.290464 + draftScore = draftScore + draftF(17) * -0.066335 + draftScore = draftScore + draftF(18) * -0.236848 + draftScore = draftScore + draftF(20) * -0.430459 + draftScore = draftScore + draftF(21) * -0.051418 + draftScore = draftScore + draftF(22) * -0.138183 + draftScore = draftScore + draftF(23) * -0.071204 + draftScore = draftScore + draftF(24) * 0.001728 + draftScore = draftScore + draftF(25) * -0.269085 + draftScore = draftScore + draftF(26) * -0.168722 + draftScore = draftScore + draftF(27) * -0.023902 + draftScore = draftScore + draftF(28) * 0.052747 + draftScore = draftScore + draftF(30) * -0.193678 + draftScore = draftScore + draftF(31) * -0.219699 + if draftScore > draftBestScore then + draftBestScore = draftScore + draftBestClass = 9 + end if + end if + if draftBestClass >= 0 then + draftHero(draftBestClass) + end if +end sub + +if drafting then + chooseHero() + end +end if + +if battleStarted = 0 then + battleStartTick = worldTick + battleStarted = 1 +end if +battleTick = worldTick - battleStartTick + +if selfHp <= 0 then + buybackCost = buybackPrice() + if buybackCost > 0 and selfGold >= buybackCost then + buyback() + end if + end +end if + +' Spend every currently legal point. The trained actor favors W, then E/Q; +' take the ultimate whenever its level gate opens. +for upgrade = 1 to 4 + if canLevelAbility(3) then + levelAbility(3) + elseif canLevelAbility(1) then + levelAbility(1) + elseif canLevelAbility(2) then + levelAbility(2) + elseif canLevelAbility(0) then + levelAbility(0) + end if +next upgrade + +dim f(27) +bestId = 0 +bestDistance = 2147483647 +objectiveId = 0 +objectiveKind = 0 +siegeThreatId = 0 +siegeThreatHp = 2147483647 +objectiveDistance = 2147483647 +heroId = 0 +heroDistance = 2147483647 +enemyX = selfX +enemyY = selfY +enemyHp = 0 +enemyKind = 0 +enemyAttackTarget = 0 +routeOpeningX = 71 +routeOpeningY = 12 +if selfTeam = 1 then + routeOpeningX = 45 + routeOpeningY = 104 +end if +routeGroupCount = 0 +if selfTeam = 0 then + gateHomeX = 105 + gateHomeY = 10 +else + gateHomeX = 10 + gateHomeY = 105 +end if +gateThreatDistance = 2147483647 +gateThreatSeen = 0 +gateThreatX = gateHomeX +reserveHome = 0 +allyCount = 0 +allyX = 0 +allyY = 0 +maxAllyDistance = 0 +index = 0 +while index < objectCount() + if objectAlive(index) then + if objectTeam(index) = selfTeam and objectKind(index) = 1 then + reserveHome = 1 + reserveHomeId = objectId(index) + reserveX = objectX(index) + reserveY = objectY(index) + end if + if objectTeam(index) = selfTeam and objectKind(index) = 2 then + allyCount = allyCount + 1 + routeDx = objectX(index) - routeOpeningX + routeDy = objectY(index) - routeOpeningY + if routeDx * routeDx + routeDy * routeDy <= 16 then + routeGroupCount = routeGroupCount + 1 + end if + allyX = allyX + objectX(index) + allyY = allyY + objectY(index) + dx = objectX(index) - selfX + dy = objectY(index) - selfY + distance = dx * dx + dy * dy + if distance > maxAllyDistance then + maxAllyDistance = distance + end if + end if + if objectTeam(index) <> selfTeam then + if battleTick < 1000 and (objectKind(index) = 2 or objectKind(index) = 3) then + gateDx = objectX(index) - gateHomeX + gateDy = objectY(index) - gateHomeY + gateDistance = gateDx * gateDx + gateDy * gateDy + if gateDistance < gateThreatDistance then + gateThreatDistance = gateDistance + gateThreatSeen = 1 + gateThreatX = objectX(index) + end if + end if + dx = objectX(index) - selfX + dy = objectY(index) - selfY + distance = dx * dx + dy * dy + if distance < bestDistance then + bestDistance = distance + bestId = objectId(index) + enemyX = objectX(index) + enemyY = objectY(index) + enemyHp = objectHp(index) + enemyKind = objectKind(index) + enemyAttackTarget = objectTarget(index) + end if + if objectKind(index) = 2 and objectTarget(index) = selfId then + siegeRange = selfAttackRange \ 1000 + if distance * 3600 <= siegeRange * siegeRange and objectHp(index) < siegeThreatHp then + siegeThreatId = objectId(index) + siegeThreatHp = objectHp(index) + end if + end if + if objectKind(index) = 1 or objectKind(index) = 4 then + if distance < objectiveDistance then + objectiveDistance = distance + objectiveId = objectId(index) + objectiveKind = objectKind(index) + end if + end if + if objectKind(index) = 2 and distance < heroDistance then + heroDistance = distance + heroId = objectId(index) + ringHeroX = objectX(index) + ringHeroY = objectY(index) + ringHeroTarget = objectTarget(index) + end if + end if + end if + index = index + 1 +wend +if allyCount > 0 then + allyX = allyX \ allyCount + allyY = allyY \ allyCount +else + allyX = selfX + allyY = selfY +end if +f(0) = selfHp * 100 \ selfMaxHp +f(1) = 0 +if selfMaxMana > 0 then + f(1) = selfMana * 100 \ selfMaxMana +end if +f(2) = maxAllyDistance \ 100 +f(3) = selfLevel * 5 +f(4) = allyX - selfX +f(5) = allyY - selfY +f(6) = enemyX - selfX +f(7) = enemyY - selfY +f(8) = enemyHp \ 10 +f(9) = enemyKind * 25 +if selfTeam = 1 then + f(4) = 0 - f(4) + f(5) = 0 - f(5) + f(6) = 0 - f(6) + f(7) = 0 - f(7) +end if +f(10) = battleTick \ 288 +f(11) = abilityCharges(0) * 25 +f(12) = abilityCharges(1) * 25 +f(13) = abilityCharges(2) * 25 +f(14) = abilityCharges(3) * 25 +f(15 + selfClass) = 100 +f(25) = battleTick \ 16 +if battleTick < 1000 and gateThreatSeen = 1 then + gateEarlyThreatX = gateThreatX + gateEarlyThreatSeen = 1 +end if +f(26) = 0 +if gateEarlyThreatSeen = 1 then + f(26) = gateEarlyThreatX - gateHomeX + if selfTeam = 1 then + f(26) = 0 - f(26) + end if +end if +index = 0 +while index < 27 + if f(index) > 100 then + f(index) = 100 + end if + if f(index) < -100 then + f(index) = -100 + end if + index = index + 1 +wend +dim h(16) +neuralActionCountdown = neuralActionCountdown - 1 +if neuralActionCountdown <= 0 then + neuralActionCountdown = 4 +h(0) = 18208 + f(0) * (263) + f(1) * (222) + f(2) * (-62) + f(3) * (-8) + f(4) * (156) + f(5) * (176) + f(6) * (-660) + f(7) * (470) + f(8) * (178) + f(9) * (648) + f(10) * (309) + f(11) * (38) + f(12) * (78) + f(13) * (-150) + f(14) * (200) + f(15) * (5) + f(16) * (-491) + f(17) * (183) + f(18) * (-26) + f(19) * (311) + f(20) * (646) + f(21) * (80) + f(22) * (544) + f(23) * (39) + f(24) * (255) +if h(0) < 0 then + h(0) = 0 +end if +h(1) = 18392 + f(0) * (126) + f(1) * (-23) + f(2) * (117) + f(3) * (-25) + f(4) * (585) + f(5) * (-305) + f(6) * (-320) + f(7) * (118) + f(8) * (235) + f(9) * (32) + f(10) * (21) + f(11) * (-234) + f(12) * (656) + f(13) * (93) + f(14) * (602) + f(15) * (414) + f(16) * (439) + f(17) * (243) + f(18) * (243) + f(19) * (-340) + f(20) * (187) + f(21) * (-78) + f(22) * (72) + f(23) * (-522) + f(24) * (315) +if h(1) < 0 then + h(1) = 0 +end if +h(2) = 3752 + f(0) * (-346) + f(1) * (10) + f(2) * (-181) + f(3) * (180) + f(4) * (64) + f(5) * (725) + f(6) * (-25) + f(7) * (71) + f(8) * (104) + f(9) * (-523) + f(10) * (-232) + f(11) * (-80) + f(12) * (-27) + f(13) * (-30) + f(14) * (-313) + f(15) * (-350) + f(16) * (-320) + f(17) * (-11) + f(18) * (493) + f(19) * (-48) + f(20) * (-128) + f(21) * (182) + f(22) * (-73) + f(23) * (-520) + f(24) * (-23) +if h(2) < 0 then + h(2) = 0 +end if +h(3) = 19627 + f(0) * (190) + f(1) * (158) + f(2) * (-294) + f(3) * (61) + f(4) * (-155) + f(5) * (-122) + f(6) * (280) + f(7) * (-28) + f(8) * (-22) + f(9) * (396) + f(10) * (438) + f(11) * (38) + f(12) * (385) + f(13) * (238) + f(14) * (-15) + f(15) * (-727) + f(16) * (119) + f(17) * (363) + f(18) * (-197) + f(19) * (-128) + f(20) * (-135) + f(21) * (-497) + f(22) * (121) + f(23) * (-12) + f(24) * (-15) +if h(3) < 0 then + h(3) = 0 +end if +h(4) = 18521 + f(0) * (431) + f(1) * (-81) + f(2) * (108) + f(3) * (-107) + f(4) * (178) + f(5) * (-317) + f(6) * (-339) + f(7) * (286) + f(8) * (230) + f(9) * (-103) + f(10) * (-293) + f(11) * (-141) + f(12) * (714) + f(13) * (388) + f(14) * (-197) + f(15) * (-213) + f(16) * (109) + f(17) * (384) + f(18) * (214) + f(19) * (562) + f(20) * (195) + f(21) * (323) + f(22) * (25) + f(23) * (668) + f(24) * (-158) +if h(4) < 0 then + h(4) = 0 +end if +h(5) = 18435 + f(0) * (360) + f(1) * (159) + f(2) * (398) + f(3) * (339) + f(4) * (-52) + f(5) * (-357) + f(6) * (-172) + f(7) * (110) + f(8) * (-22) + f(9) * (646) + f(10) * (125) + f(11) * (-453) + f(12) * (-365) + f(13) * (177) + f(14) * (230) + f(15) * (-367) + f(16) * (408) + f(17) * (465) + f(18) * (703) + f(19) * (61) + f(20) * (136) + f(21) * (543) + f(22) * (41) + f(23) * (-54) + f(24) * (54) +if h(5) < 0 then + h(5) = 0 +end if +h(6) = 23325 + f(0) * (122) + f(1) * (-158) + f(2) * (-130) + f(3) * (204) + f(4) * (-64) + f(5) * (-508) + f(6) * (639) + f(7) * (551) + f(8) * (299) + f(9) * (239) + f(10) * (-38) + f(11) * (-406) + f(12) * (-41) + f(13) * (-158) + f(14) * (-309) + f(15) * (353) + f(16) * (131) + f(17) * (-140) + f(18) * (-48) + f(19) * (244) + f(20) * (161) + f(21) * (174) + f(22) * (-9) + f(23) * (91) + f(24) * (715) +if h(6) < 0 then + h(6) = 0 +end if +h(7) = 19286 + f(0) * (-90) + f(1) * (489) + f(2) * (349) + f(3) * (361) + f(4) * (116) + f(5) * (-315) + f(6) * (-91) + f(7) * (-101) + f(8) * (238) + f(9) * (-164) + f(10) * (238) + f(11) * (-133) + f(12) * (125) + f(13) * (458) + f(14) * (371) + f(15) * (3) + f(16) * (30) + f(17) * (203) + f(18) * (-106) + f(19) * (482) + f(20) * (-345) + f(21) * (302) + f(22) * (932) + f(23) * (147) + f(24) * (505) +if h(7) < 0 then + h(7) = 0 +end if +h(8) = 18159 + f(0) * (101) + f(1) * (49) + f(2) * (180) + f(3) * (-178) + f(4) * (-311) + f(5) * (502) + f(6) * (-51) + f(7) * (361) + f(8) * (179) + f(9) * (638) + f(10) * (-61) + f(11) * (240) + f(12) * (314) + f(13) * (248) + f(14) * (-8) + f(15) * (483) + f(16) * (730) + f(17) * (314) + f(18) * (155) + f(19) * (291) + f(20) * (-257) + f(21) * (241) + f(22) * (543) + f(23) * (9) + f(24) * (-66) +if h(8) < 0 then + h(8) = 0 +end if +h(9) = 19073 + f(0) * (411) + f(1) * (470) + f(2) * (170) + f(3) * (61) + f(4) * (50) + f(5) * (-20) + f(6) * (-192) + f(7) * (-211) + f(8) * (60) + f(9) * (68) + f(10) * (-32) + f(11) * (-109) + f(12) * (-126) + f(13) * (-404) + f(14) * (49) + f(15) * (543) + f(16) * (-90) + f(17) * (554) + f(18) * (445) + f(19) * (341) + f(20) * (-324) + f(21) * (-471) + f(22) * (-27) + f(23) * (378) + f(24) * (114) +if h(9) < 0 then + h(9) = 0 +end if +h(10) = 18190 + f(0) * (861) + f(1) * (381) + f(2) * (130) + f(3) * (413) + f(4) * (-252) + f(5) * (152) + f(6) * (159) + f(7) * (421) + f(8) * (-140) + f(9) * (-151) + f(10) * (669) + f(11) * (-70) + f(12) * (515) + f(13) * (338) + f(14) * (102) + f(15) * (453) + f(16) * (-71) + f(17) * (383) + f(18) * (255) + f(19) * (-158) + f(20) * (195) + f(21) * (441) + f(22) * (133) + f(23) * (189) + f(24) * (264) +if h(10) < 0 then + h(10) = 0 +end if +h(11) = 19629 + f(0) * (82) + f(1) * (-702) + f(2) * (275) + f(3) * (91) + f(4) * (-190) + f(5) * (47) + f(6) * (-505) + f(7) * (-288) + f(8) * (-299) + f(9) * (-56) + f(10) * (716) + f(11) * (-16) + f(12) * (78) + f(13) * (18) + f(14) * (95) + f(15) * (6) + f(16) * (302) + f(17) * (-370) + f(18) * (419) + f(19) * (284) + f(20) * (2) + f(21) * (-157) + f(22) * (129) + f(23) * (332) + f(24) * (261) +if h(11) < 0 then + h(11) = 0 +end if +h(12) = 19929 + f(0) * (-187) + f(1) * (342) + f(2) * (-126) + f(3) * (-271) + f(4) * (-109) + f(5) * (-323) + f(6) * (149) + f(7) * (43) + f(8) * (-690) + f(9) * (236) + f(10) * (-95) + f(11) * (383) + f(12) * (276) + f(13) * (62) + f(14) * (485) + f(15) * (215) + f(16) * (-249) + f(17) * (-34) + f(18) * (413) + f(19) * (130) + f(20) * (137) + f(21) * (562) + f(22) * (61) + f(23) * (214) + f(24) * (123) +if h(12) < 0 then + h(12) = 0 +end if +h(13) = 21374 + f(0) * (-49) + f(1) * (121) + f(2) * (69) + f(3) * (190) + f(4) * (856) + f(5) * (44) + f(6) * (-31) + f(7) * (-132) + f(8) * (-120) + f(9) * (458) + f(10) * (407) + f(11) * (-483) + f(12) * (6) + f(13) * (396) + f(14) * (-271) + f(15) * (452) + f(16) * (40) + f(17) * (-47) + f(18) * (-98) + f(19) * (-226) + f(20) * (-24) + f(21) * (342) + f(22) * (192) + f(23) * (508) + f(24) * (-284) +if h(13) < 0 then + h(13) = 0 +end if +h(14) = 18212 + f(0) * (432) + f(1) * (481) + f(2) * (400) + f(3) * (-699) + f(4) * (108) + f(5) * (103) + f(6) * (-31) + f(7) * (-289) + f(8) * (548) + f(9) * (208) + f(10) * (338) + f(11) * (67) + f(12) * (157) + f(13) * (80) + f(14) * (-99) + f(15) * (-136) + f(16) * (179) + f(17) * (-206) + f(18) * (274) + f(19) * (-139) + f(20) * (86) + f(21) * (412) + f(22) * (33) + f(23) * (278) + f(24) * (594) +if h(14) < 0 then + h(14) = 0 +end if +h(15) = 18656 + f(0) * (651) + f(1) * (160) + f(2) * (-271) + f(3) * (341) + f(4) * (228) + f(5) * (-548) + f(6) * (-380) + f(7) * (170) + f(8) * (260) + f(9) * (78) + f(10) * (188) + f(11) * (579) + f(12) * (-65) + f(13) * (58) + f(14) * (-197) + f(15) * (173) + f(16) * (208) + f(17) * (-353) + f(18) * (354) + f(19) * (-48) + f(20) * (-264) + f(21) * (293) + f(22) * (413) + f(23) * (-53) + f(24) * (-198) +if h(15) < 0 then + h(15) = 0 +end if +decision = 0 +bestScore = -2147483647 +score = 387878561 + h(0) * (-128) + h(1) * (-127) + h(2) * (-9) + h(3) * (-160) + h(4) * (-125) + h(5) * (-151) + h(6) * (-198) + h(7) * (-129) + h(8) * (-126) + h(9) * (-140) + h(10) * (-111) + h(11) * (-199) + h(12) * (-146) + h(13) * (-192) + h(14) * (-125) + h(15) * (-129) +if score > bestScore then + bestScore = score + decision = 0 +end if +score = 8621915 + h(0) * (113) + h(1) * (102) + h(2) * (10) + h(3) * (106) + h(4) * (96) + h(5) * (110) + h(6) * (83) + h(7) * (86) + h(8) * (101) + h(9) * (96) + h(10) * (95) + h(11) * (62) + h(12) * (124) + h(13) * (115) + h(14) * (101) + h(15) * (103) +if score > bestScore then + bestScore = score + decision = 1 +end if +score = 9365853 + h(0) * (114) + h(1) * (122) + h(2) * (25) + h(3) * (121) + h(4) * (105) + h(5) * (113) + h(6) * (104) + h(7) * (113) + h(8) * (112) + h(9) * (113) + h(10) * (102) + h(11) * (86) + h(12) * (104) + h(13) * (132) + h(14) * (98) + h(15) * (113) +if score > bestScore then + bestScore = score + decision = 2 +end if +score = 10032520 + h(0) * (124) + h(1) * (135) + h(2) * (-1) + h(3) * (134) + h(4) * (118) + h(5) * (129) + h(6) * (105) + h(7) * (120) + h(8) * (110) + h(9) * (115) + h(10) * (108) + h(11) * (65) + h(12) * (116) + h(13) * (119) + h(14) * (124) + h(15) * (124) +if score > bestScore then + bestScore = score + decision = 3 +end if +score = 10514535 + h(0) * (111) + h(1) * (115) + h(2) * (18) + h(3) * (135) + h(4) * (112) + h(5) * (136) + h(6) * (94) + h(7) * (112) + h(8) * (107) + h(9) * (125) + h(10) * (98) + h(11) * (77) + h(12) * (105) + h(13) * (139) + h(14) * (114) + h(15) * (111) +if score > bestScore then + bestScore = score + decision = 4 +end if +score = 10182099 + h(0) * (118) + h(1) * (123) + h(2) * (-6) + h(3) * (122) + h(4) * (94) + h(5) * (112) + h(6) * (107) + h(7) * (125) + h(8) * (116) + h(9) * (113) + h(10) * (102) + h(11) * (68) + h(12) * (107) + h(13) * (137) + h(14) * (113) + h(15) * (113) +if score > bestScore then + bestScore = score + decision = 5 +end if +score = 9061429 + h(0) * (102) + h(1) * (104) + h(2) * (17) + h(3) * (120) + h(4) * (107) + h(5) * (107) + h(6) * (99) + h(7) * (105) + h(8) * (107) + h(9) * (108) + h(10) * (94) + h(11) * (94) + h(12) * (107) + h(13) * (134) + h(14) * (96) + h(15) * (112) +if score > bestScore then + bestScore = score + decision = 6 +end if +score = 10778495 + h(0) * (127) + h(1) * (114) + h(2) * (43) + h(3) * (132) + h(4) * (111) + h(5) * (125) + h(6) * (106) + h(7) * (120) + h(8) * (132) + h(9) * (123) + h(10) * (118) + h(11) * (89) + h(12) * (113) + h(13) * (133) + h(14) * (117) + h(15) * (131) +if score > bestScore then + bestScore = score + decision = 7 +end if +score = 9785349 + h(0) * (112) + h(1) * (119) + h(2) * (1) + h(3) * (125) + h(4) * (110) + h(5) * (130) + h(6) * (105) + h(7) * (116) + h(8) * (115) + h(9) * (109) + h(10) * (102) + h(11) * (95) + h(12) * (107) + h(13) * (142) + h(14) * (105) + h(15) * (115) +if score > bestScore then + bestScore = score + decision = 8 +end if +score = 387767339 + h(0) * (-137) + h(1) * (-135) + h(2) * (-16) + h(3) * (-166) + h(4) * (-133) + h(5) * (-152) + h(6) * (-209) + h(7) * (-142) + h(8) * (-122) + h(9) * (-142) + h(10) * (-121) + h(11) * (-185) + h(12) * (-152) + h(13) * (-198) + h(14) * (-139) + h(15) * (-141) +if score > bestScore then + bestScore = score + decision = 9 +end if +score = 9125238 + h(0) * (115) + h(1) * (113) + h(2) * (36) + h(3) * (104) + h(4) * (98) + h(5) * (120) + h(6) * (76) + h(7) * (94) + h(8) * (107) + h(9) * (122) + h(10) * (97) + h(11) * (66) + h(12) * (133) + h(13) * (122) + h(14) * (107) + h(15) * (112) +if score > bestScore then + bestScore = score + decision = 10 +end if +score = 9920025 + h(0) * (107) + h(1) * (128) + h(2) * (-22) + h(3) * (122) + h(4) * (105) + h(5) * (128) + h(6) * (116) + h(7) * (113) + h(8) * (113) + h(9) * (109) + h(10) * (99) + h(11) * (103) + h(12) * (97) + h(13) * (152) + h(14) * (108) + h(15) * (119) +if score > bestScore then + bestScore = score + decision = 11 +end if +score = 9813639 + h(0) * (116) + h(1) * (134) + h(2) * (8) + h(3) * (128) + h(4) * (110) + h(5) * (125) + h(6) * (89) + h(7) * (117) + h(8) * (109) + h(9) * (106) + h(10) * (100) + h(11) * (83) + h(12) * (115) + h(13) * (114) + h(14) * (114) + h(15) * (111) +if score > bestScore then + bestScore = score + decision = 12 +end if +score = 11515426 + h(0) * (125) + h(1) * (129) + h(2) * (-7) + h(3) * (136) + h(4) * (117) + h(5) * (137) + h(6) * (124) + h(7) * (118) + h(8) * (119) + h(9) * (119) + h(10) * (108) + h(11) * (83) + h(12) * (129) + h(13) * (149) + h(14) * (122) + h(15) * (130) +if score > bestScore then + bestScore = score + decision = 13 +end if +score = 9815518 + h(0) * (120) + h(1) * (121) + h(2) * (-14) + h(3) * (127) + h(4) * (100) + h(5) * (106) + h(6) * (113) + h(7) * (117) + h(8) * (110) + h(9) * (112) + h(10) * (106) + h(11) * (76) + h(12) * (119) + h(13) * (129) + h(14) * (112) + h(15) * (106) +if score > bestScore then + bestScore = score + decision = 14 +end if +score = 11022095 + h(0) * (119) + h(1) * (118) + h(2) * (-26) + h(3) * (137) + h(4) * (124) + h(5) * (135) + h(6) * (105) + h(7) * (124) + h(8) * (123) + h(9) * (129) + h(10) * (114) + h(11) * (132) + h(12) * (121) + h(13) * (145) + h(14) * (113) + h(15) * (121) +if score > bestScore then + bestScore = score + decision = 15 +end if +score = 10193590 + h(0) * (122) + h(1) * (97) + h(2) * (-11) + h(3) * (122) + h(4) * (110) + h(5) * (109) + h(6) * (97) + h(7) * (118) + h(8) * (112) + h(9) * (106) + h(10) * (111) + h(11) * (73) + h(12) * (133) + h(13) * (130) + h(14) * (112) + h(15) * (123) +if score > bestScore then + bestScore = score + decision = 16 +end if +score = 10897366 + h(0) * (133) + h(1) * (140) + h(2) * (12) + h(3) * (135) + h(4) * (112) + h(5) * (131) + h(6) * (116) + h(7) * (125) + h(8) * (125) + h(9) * (129) + h(10) * (114) + h(11) * (121) + h(12) * (106) + h(13) * (153) + h(14) * (120) + h(15) * (119) +if score > bestScore then + bestScore = score + decision = 17 +end if +end if +if decision = 18 then + combatDecision = 8 +else +combatDecision = decision +end if +objectiveBuild = 0 +if decision >= 9 then + combatDecision = decision - 9 + objectiveBuild = 1 +end if + +routeFallback = 0 +if combatDecision = 2 or (combatDecision = 8 and bestId = 0) then + if (objectiveId = 0 or objectiveDistance > 300) and (heroId = 0 or heroDistance > 700) then + routeFallback = 1 + end if +end if +routeDx = selfX - routeOpeningX +routeDy = selfY - routeOpeningY +if battleTick < 3500 and routeFallback and routeGroupCount >= 3 then + if routeDx * routeDx + routeDy * routeDy <= 16 then + groupDeparture = 1 + end if +end if + +if combatDecision = 8 then + if objectiveId <> 0 and objectiveDistance <= 300 then + attackTarget(objectiveId) + else + if heroId <> 0 and heroDistance <= 700 then + attackTarget(heroId) + else + if bestId <> 0 then + attackTarget(bestId) + else + if selfTeam = 0 then + if battleTick < 3500 and groupDeparture = 0 then + walkTo(71, 12) + else + if battleTick < 4500 then + walkTo(9, 33) + else + walkTo(11, 106) + end if + end if + else + if battleTick < 3500 and groupDeparture = 0 then + walkTo(45, 104) + else + if battleTick < 4500 then + walkTo(107, 83) + else + walkTo(105, 10) + end if + end if + end if + end if + end if + end if +else + if bestId <> 0 then + attackTarget(bestId) + else + walkTo(64, 64) + end if +end if +if selfClass = 7 and heroId <> 0 and ringHeroTarget = selfId then + if heroDistance > 25 and heroDistance <= 49 then + castPoint(3, (selfX + ringHeroX) \ 2, (selfY + ringHeroY) \ 2) + end if +end if +hasHeal = 0 +elixirCount = 0 +hasMana = 0 +hasPoison = 0 +hasGear = 0 +hasDagger = 0 +hasSword = 0 +hasArmor = 0 +hasAxe = 0 +hasBook = 0 +emptySlot = 0 +slot = 0 +while slot < 6 + id = itemId(slot) + if id = 0 then + emptySlot = 1 + end if + if id = 1 or id = 2 then + hasHeal = 1 + if selfHp * 5 < selfMaxHp * 3 and itemCooldown(slot) = 0 then + useItem(slot) + end if + end if + if id = 2 then + elixirCount = elixirCount + itemCount(slot) + end if + if id = 3 or id = 22 then + hasMana = 1 + if objectiveBuild = 0 and selfMana * 5 < selfMaxMana * 2 and itemCooldown(slot) = 0 then + useItem(slot) + end if + end if + if id = 4 then + hasPoison = 1 + if objectiveBuild = 0 and bestId <> 0 then + useItem(slot) + end if + end if + if id >= 5 and id <= 20 then + hasGear = 1 + end if + if id = 11 then + hasDagger = 1 + end if + if id = 13 then + hasSword = 1 + end if + if id = 16 then + hasArmor = 1 + end if + if id = 18 then + hasAxe = 1 + end if + if id = 20 then + hasBook = 1 + end if + slot = slot + 1 +wend +if canShop() then +' Stock immediate healing after the opening build. Damage interrupts +' ordinary potion recovery, and a full six-slot pack cannot buy a cure. +if selfClass = 2 and selfLevel >= 4 and elixirCount < 3 then + if selfGold >= 75 and (elixirCount > 0 or emptySlot <> 0) then + buyItem(2) + hasHeal = 1 + end if +end if +if selfHp * 2 < selfMaxHp and hasHeal = 0 then + if selfGold >= 50 then + buyItem(2) + end if + if selfGold >= 30 then + buyItem(1) + end if +end if +if objectiveBuild = 0 then +if selfMaxMana > 0 then + if selfMana * 2 < selfMaxMana and hasMana = 0 then + if selfGold >= 45 then + buyItem(22) + end if + end if +end if +if bestId <> 0 and hasPoison = 0 then + if selfGold >= 40 then + buyItem(4) + end if +end if +if emptySlot <> 0 then + melee = 0 + ranged = 0 + magic = 0 + if selfClass = 0 or selfClass = 4 or selfClass = 5 or selfClass = 9 then + melee = 1 + end if + if selfClass = 1 or selfClass = 6 then + ranged = 1 + end if + if selfClass = 2 or selfClass = 3 or selfClass = 7 or selfClass = 8 then + magic = 1 + end if + if melee = 1 then + if hasGear = 0 and selfGold >= 70 then + buyItem(7) + end if + if selfGold >= 80 then + buyItem(5) + end if + if selfGold >= 110 then + buyItem(11) + end if + if selfGold >= 150 then + buyItem(13) + end if + if selfGold >= 180 then + buyItem(18) + end if + end if + if ranged = 1 then + if hasGear = 0 and selfGold >= 100 then + buyItem(8) + end if + if selfGold >= 150 then + buyItem(14) + end if + if selfGold >= 180 then + buyItem(19) + end if + end if + if magic = 1 then + if hasGear = 0 and selfGold >= 140 then + buyItem(12) + end if + if selfGold >= 120 then + buyItem(10) + end if + if selfGold >= 170 then + buyItem(17) + end if + if selfGold >= 190 then + buyItem(20) + end if + end if + if selfGold >= 90 then + buyItem(6) + end if + if selfGold >= 120 then + buyItem(9) + end if +end if +else + if emptySlot <> 0 then + if hasDagger = 0 and selfGold >= 110 then + buyItem(11) + end if + if hasSword = 0 and selfGold >= 150 then + buyItem(13) + end if + if hasArmor = 0 and selfGold >= 160 then + buyItem(16) + end if + if hasAxe = 0 and selfGold >= 180 then + buyItem(18) + end if + if hasBook = 0 and selfGold >= 190 then + buyItem(20) + end if + end if +end if +end if +if combatDecision = 1 then + if selfTeam = 0 then + walkTo(mapWidth - 10, 10) + else + walkTo(10, mapHeight - 10) + end if +end if +if combatDecision = 2 then + if objectiveId <> 0 and objectiveDistance <= 300 then + attackTarget(objectiveId) + else + if heroId <> 0 and heroDistance <= 700 then + attackTarget(heroId) + else + if selfTeam = 0 then + if battleTick < 3500 and groupDeparture = 0 then + walkTo(71, 12) + else + if battleTick < 4500 then + walkTo(9, 33) + else + walkTo(11, 106) + end if + end if + else + if battleTick < 3500 and groupDeparture = 0 then + walkTo(45, 104) + else + if battleTick < 4500 then + walkTo(107, 83) + else + walkTo(105, 10) + end if + end if + end if + end if + end if +end if +if combatDecision >= 3 and combatDecision <= 6 then + if bestId <> 0 then + castTarget(combatDecision - 3, bestId) + else + castTarget(combatDecision - 3, selfId) + end if +end if +if combatDecision = 7 then + walkTo(allyX, allyY) +end if + +if decision = 18 and (bestId = 0 or bestDistance > 64) then + specialistX = mapWidth - 1 - gateHomeX + specialistY = gateHomeY + specialistDx = selfX - specialistX + specialistDy = selfY - specialistY + if specialistDx * specialistDx + specialistDy * specialistDy <= 64 then + perimeterStage = 1 + end if + if perimeterStage <> 0 then + specialistY = mapHeight - 1 - gateHomeY + end if + walkTo(specialistX, specialistY) +end if + +if (combatDecision = 2 or combatDecision = 8) and objectiveKind = 4 and objectiveDistance <= 300 and siegeThreatId <> 0 then + attackTarget(siegeThreatId) +end if + +if reserveHome <> 0 then + reserveGuards = 0 + reserveGuardCritical = 0 + reserveNearest = 1 + reserveDx = selfX - reserveX + reserveDy = selfY - reserveY + reserveDistance = reserveDx * reserveDx + reserveDy * reserveDy + reserveTarget = 0 + reserveTargetDistance = 401 + reserveDirect = 0 + reserveDirectDistance = 401 + reserveHero = 0 + reserveHeroDistance = 401 + reserveFinish = 0 + reserveIndex = 0 + while reserveIndex < objectCount() + reserveDx = objectX(reserveIndex) - reserveX + reserveDy = objectY(reserveIndex) - reserveY + reserveObjectHome = reserveDx * reserveDx + reserveDy * reserveDy + if objectTeam(reserveIndex) = selfTeam then + if objectKind(reserveIndex) = 4 and objectHp(reserveIndex) > 0 and reserveObjectHome <= 100 then + reserveGuards = reserveGuards + 1 + if objectHp(reserveIndex) <= 780 then + reserveGuardCritical = 1 + end if + end if + if objectKind(reserveIndex) = 2 and objectAlive(reserveIndex) and reserveObjectHome < reserveDistance then + reserveNearest = 0 + end if + else + if objectAlive(reserveIndex) then + if (objectKind(reserveIndex) = 2 or objectKind(reserveIndex) = 3) and reserveObjectHome < reserveDirectDistance then + if objectTarget(reserveIndex) = reserveHomeId then + reserveDirect = objectId(reserveIndex) + reserveDirectDistance = reserveObjectHome + end if + end if + if objectKind(reserveIndex) = 2 and reserveObjectHome < reserveHeroDistance then + reserveHero = objectId(reserveIndex) + reserveHeroDistance = reserveObjectHome + end if + if (objectKind(reserveIndex) = 2 or objectKind(reserveIndex) = 3) and reserveObjectHome < reserveTargetDistance then + reserveTargetDistance = reserveObjectHome + reserveTarget = objectId(reserveIndex) + end if + if objectKind(reserveIndex) = 1 and objectHp(reserveIndex) <= selfAttackDamage then + reserveDx = objectX(reserveIndex) - selfX + reserveDy = objectY(reserveIndex) - selfY + reserveRange = selfAttackRange \ 1000 + if (reserveDx * reserveDx + reserveDy * reserveDy) * 3600 <= reserveRange * reserveRange then + reserveFinish = 1 + end if + end if + end if + end if + reserveIndex = reserveIndex + 1 + wend + if reserveHero <> 0 then + reserveTarget = reserveHero + end if + if reserveDirect <> 0 then + reserveTarget = reserveDirect + end if + if (reserveGuards <= 1 or (reserveGuardCritical <> 0 and reserveDistance <= 900)) and reserveNearest <> 0 and reserveFinish = 0 then + if reserveDistance <= 400 and reserveTarget <> 0 then + attackTarget(reserveTarget) + else + walkTo(reserveX, reserveY) + end if + end if +end if + +if recoveryInitialized <> 0 and selfAttacksLanded > recoveryLastHit then + walkTo(selfX, selfY) +end if +recoveryLastHit = selfAttacksLanded +recoveryInitialized = 1 diff --git a/experiments/neural-policy/results.md b/experiments/neural-policy/results.md new file mode 100644 index 00000000..5a59dc42 --- /dev/null +++ b/experiments/neural-policy/results.md @@ -0,0 +1,122 @@ +# Richard's policy conversion results + +The original and converted policy produced identical gameplay in the recorded +GotA match: all 129,497 actions and all 28,909 per-tick state hashes match. +Both ended with state hash **`00000000491E52B0`**. All 10 scripts remained +active through 288,010 decisions. The match reached its 20-minute battle limit. + +The raw replay files differ because their player labels and instruction-use +telemetry differ. `compare.nim` verifies setup, every action, every tick hash, +and every other field after allowing only those two metadata differences. +No actions or state hashes are normalized, and neither recording is modified. + +## Policy and environment + +- [Original BASIC](original.bas): 1,147 lines, 46,457 bytes, unchanged. +- [Converted BASIC](../../examples/gods_of_the_arena/players/neural.bas): 828 lines, 24,944 bytes. +- Source: [co-gas at 131b8fde](https://github.com/Metta-AI/co-gas/blob/131b8fde058561cf369449ee2854579f02c64676/players/users/relh/co-gas/polyworld-basic/gods_of_the_arena_neural_v63_arcanist_elixir_stock.bas). +- Original source SHA-256: `d5514435f27c400a53bf026b338e7786114f5a4948e9a99d5dd1c3dea9fd1f3a`. +- Polyworld baseline: `8b31ddd`, gameplay version 63. +- Bassy baseline: `b25e0efef3fec0bd86ed3154659c0762a7158bd3`. +- Fixxy: `05e5446dffb70093056cebb0c57721a60deaf52a`. +- Local validation: macOS, Nim 2.2.6, native release build. +- Match seed: `20260924`. Map seed: `54`, map hash: `0000000099EBA3A6`. +- Seats 1-5: Richard's policy. Seats 6-10: the unchanged GotA base policy. +- Battle: 28,800 ticks. Draft: 109 ticks. Total: 28,909 ticks. + +The original binary was built and the baseline recorded before modifying +Bassy or Polyworld. It remains at `../../tmp/neural-parity/gota-original`. + +## What changed in the policy + +All three affine layers now call `linear`, followed by `relu` and `argmax` as +appropriate. The draft model is 32 to 10 with availability masking. The combat +model is 25 to 16, ReLU, then 16 to 18. All 1,052 matrix and bias entries are +inline DATA, including zero padding in the sparse draft matrix. No binary or +ZIP file is needed. + +The converter extracts the exact literal strings without rounding them in +Python. Draft values use fixed-point arithmetic, and combat values retain +int32 arithmetic. The original feature extraction, action mapping, shopping, +draft eligibility, and four-tick inference cadence are preserved. The combat +selection also preserves the original sentinel behavior at the bottom of the +int32 range. + +The inference itself is now: + +```basic +linear(f, encoder, encoderBias, h, 25, 16) +relu(h, 16) +linear(h, decoder, decoderBias, logits, 16, 18) +decision = argmax(logits, 18) +``` + +## Performance and validation + +`bench_neural.nim` additionally compares all 16 hidden outputs and the selected +action across 256 deterministic combat feature vectors. All match. + +| Isolated combat inference | Interpreted | Native | +| --- | ---: | ---: | +| Mean milliseconds per 1,000 evaluations, 20 samples | 12.295 | 4.180 | +| Charged instructions for the last sample | 3,693 | 1,521 | +| Charged work units for the last sample | 4,421 | 1,598 | + +That local kernel benchmark is about 2.9 times faster. Full-match elapsed times +were 20.03 and 20.61 seconds; those include the whole simulation and do not +demonstrate a whole-game speedup. + +Checks passed: + +- Bassy `nim check tests/tests.nim` and `nim r tests/tests.nim`. +- Polyworld `nim check tests/tests.nim` and `nim r tests/tests.nim`. +- AWM host `nim check`, in addition to the games covered by the main suite. +- Playback of the converted recording consumed all 129,497 actions with no + state-hash mismatches and ended at the same final state hash. +- DATA syntax, integer/fixed coercion, immutability, reset/restart behavior, + context callback binding, array-handle boundaries, and memory limits. +- Scalar/native arithmetic equivalence, int32 wraparound, fixed rounding, + argmax ties/masks, alias and shape rejection, instruction/work exhaustion + before kernel mutation, and stable memory across repeated inference. + +## Reproduce + +From the repository root, after installing the dependencies in `nimby.lock`: + +```sh +mkdir -p tmp/neural-parity +python3 experiments/neural-policy/convert.py \ + experiments/neural-policy/original.bas \ + examples/gods_of_the_arena/players/neural.bas +nim c -d:headless -o:tmp/neural-parity/gota-native \ + examples/gods_of_the_arena/gota.nim +tmp/neural-parity/gota-native \ + --bot experiments/neural-policy/original.bas:5 \ + --bot examples/gods_of_the_arena/players/base.bas:5 \ + --seed 20260924 --ticks 28800 \ + --record tmp/neural-parity/original.replay +tmp/neural-parity/gota-native \ + --bot examples/gods_of_the_arena/players/neural.bas:5 \ + --bot examples/gods_of_the_arena/players/base.bas:5 \ + --seed 20260924 --ticks 28800 \ + --record tmp/neural-parity/converted.replay +nim r experiments/neural-policy/compare.nim \ + tmp/neural-parity/original.replay tmp/neural-parity/converted.replay +nim r experiments/neural-policy/bench_neural.nim +``` + +The commands above compare both policies on the new runtime. To rerun the +historical baseline, use the preserved `gota-original` binary +with `--bot experiments/neural-policy/original.bas:5`, the same opponent, +seed, and tick limit. To rebuild that binary, use the baseline commits above +without the Bassy override. Names in metadata follow the supplied BAS filename. + +## Saved artifacts + +Both replay files remain under the worktree's ignored `tmp/neural-parity/` +directory, along with build, test, comparison, and benchmark logs. + +| Replay | Bytes | SHA-256 of raw file | +| --- | ---: | --- | +| `tmp/neural-parity/original.replay` | 4,438,360 | `d2e07478a5fa8bf510de9c3ad84f74164bec466c8610ce7834905b741ab01e52` | +| `tmp/neural-parity/converted.replay` | 4,438,365 | `f54b3d9c22364727977dc1b7c264c3585f3e48b5a647444d8becdbdb56a21ec1` | diff --git a/nimby.lock b/nimby.lock index 35a5d928..9c6ba79a 100644 --- a/nimby.lock +++ b/nimby.lock @@ -1,4 +1,4 @@ -bassy 0.1.0 https://github.com/treeform/bassy b25e0efef3fec0bd86ed3154659c0762a7158bd3 +bassy 0.1.0 https://github.com/treeform/bassy 78b3f3c9538cf1512ae99188d412cfffa134259a fixxy 0.1.0 https://github.com/treeform/fixxy 05e5446dffb70093056cebb0c57721a60deaf52a silky 0.2.0 https://github.com/treeform/silky fb9b13910edd66cf1751056784c2f7d2932a59fc pixie 6.1.0 https://github.com/treeform/pixie 87cecced5c4c6f311c658a5f3ca0c9b43edb6aa7 diff --git a/src/polyworld/arrays.nim b/src/polyworld/arrays.nim new file mode 100644 index 00000000..3ed91c41 --- /dev/null +++ b/src/polyworld/arrays.nim @@ -0,0 +1,135 @@ +## Deterministic native array arithmetic for every Polyworld BASIC host. + +import bassy + +type + ArrayOperation = enum + Add, Multiply, Copy, Fill, Dot + +proc count(value: Value): int = + ## Accepts a positive, exact element count without narrowing first. + let size = value.asInt + if size <= 0: + raise newException(BasicError, "array count must be positive") + int(size) + +proc requireLength(values: ArrayView, size: int) = + ## Checks a prefix before any kernel runs or output changes. + if size > values.len: + raise newException(BasicError, "array is smaller than the requested shape") + +proc linear(runtime: Runtime, arguments: openArray[Value]): Value = + ## Evaluates bias-first, row-major affine rows in BASIC arithmetic order. + let + inputs = runtime.arrayView(arguments[0]) + weights = runtime.arrayView(arguments[1]) + biases = runtime.arrayView(arguments[2]) + outputs = runtime.arrayView(arguments[3], writable = true) + width = count(arguments[4]) + height = count(arguments[5]) + cells = int64(width) * int64(height) + inputs.requireLength(width) + biases.requireLength(height) + outputs.requireLength(height) + if cells > int64(weights.len): + raise newException(BasicError, "linear weights do not fit the shape") + if outputs.overlaps(inputs) or outputs.overlaps(weights) or + outputs.overlaps(biases): + raise newException(BasicError, "linear output must use separate storage") + runtime.chargeOperations(2 * cells + 2 * int64(height)) + for row in 0 ..< height: + var total = biases[row] + for column in 0 ..< width: + total = total + inputs[column] * weights[row * width + column] + outputs[row] = total + toValue(0) + +proc relu(runtime: Runtime, arguments: openArray[Value]): Value = + ## Replaces negative values in a mutable prefix with integer zero. + let + values = runtime.arrayView(arguments[0], writable = true) + size = count(arguments[1]) + values.requireLength(size) + runtime.chargeOperations(2 * int64(size)) + for i in 0 ..< size: + if values[i] < toValue(0): + values[i] = toValue(0) + toValue(0) + +proc maximum(masked: bool): ContextHostProc = + ## Creates a first-index argmax with optional nonzero eligibility masks. + result = proc(runtime: Runtime, arguments: openArray[Value]): Value = + ## Validates and meters a maximum search before reading its elements. + let + values = runtime.arrayView(arguments[0]) + size = count(arguments[if masked: 2 else: 1]) + mask = + if masked: + runtime.arrayView(arguments[1]) + else: + values + values.requireLength(size) + if masked: + mask.requireLength(size) + runtime.chargeOperations(int64(size) * (if masked: 2 else: 1)) + var best = -1 + for i in 0 ..< size: + if masked and not mask[i].asBool: + continue + if best < 0 or values[best] < values[i]: + best = i + toValue(best) + +proc arithmetic(operation: ArrayOperation): ContextHostProc = + ## Creates a bounded elementwise kernel or ordered dot product. + result = proc(runtime: Runtime, arguments: openArray[Value]): Value = + ## Reserves all work before touching the destination array. + let + binary = operation in {Add, Multiply} + size = count(arguments[if binary: 3 else: 2]) + left = runtime.arrayView(arguments[0], operation == Fill) + right = + if operation in {Add, Multiply, Dot}: + runtime.arrayView(arguments[1]) + else: + left + outputs = + case operation + of Add, Multiply: + runtime.arrayView(arguments[2], writable = true) + of Copy: + runtime.arrayView(arguments[1], writable = true) + of Fill, Dot: + left + left.requireLength(size) + right.requireLength(size) + outputs.requireLength(size) + if operation == Fill and arguments[1].kind == StringValue: + raise newException(BasicError, "dataFill requires a number") + runtime.chargeOperations(int64(size) * (if operation == Dot: 2 else: 1)) + var total = toValue(0) + for i in 0 ..< size: + case operation + of Add: + outputs[i] = left[i] + right[i] + of Multiply: + outputs[i] = left[i] * right[i] + of Copy: + outputs[i] = left[i] + of Fill: + outputs[i] = arguments[1] + of Dot: + total = total + left[i] * right[i] + total + +proc addArrayFunctions*(host: var Host) = + ## Registers generic numeric operations without prescribing a network. + discard host.addFunction("linear", 6, linear) + discard host.addFunction("relu", 2, relu) + discard host.addFunction("argmax", 2, maximum(false)) + discard host.addFunction("argmaxMasked", 3, maximum(true)) + discard host.addFunction("dataAdd", 4, arithmetic(Add)) + discard host.addFunction("dataMultiply", 4, arithmetic(Multiply)) + discard host.addFunction("dataCopy", 3, arithmetic(Copy)) + discard host.addFunction("dataFill", 3, arithmetic(Fill)) + discard host.addFunction("dataDot", 3, arithmetic(Dot)) diff --git a/tests/test_arrays.nim b/tests/test_arrays.nim new file mode 100644 index 00000000..96335564 --- /dev/null +++ b/tests/test_arrays.nim @@ -0,0 +1,166 @@ +import + std/strutils, + bassy, + polyworld/arrays + +proc host(): Host = + ## Creates the same native array function set used by game hosts. + result = initHost() + result.addArrayFunctions() + +proc rejects(action: proc() {.closure.}, message: string) = + ## Requires an explicit BASIC error instead of a defect or silent failure. + var caught = false + try: + action() + except BasicError as error: + caught = true + doAssert message in error.msg, error.msg + doAssert caught, "expected BASIC error: " & message + +echo "Testing linear preserves scalar integer wrapping and fixed rounding" +for representation in ["int32", "fixed32"]: + let + weights = + if representation == "int32": + "2147483647, -25000, 13, -2147483648, 7654, -99" + else: + "0.220766, -0.498641, 0.306957, 1.125, -0.000031, 0.75" + biases = + if representation == "int32": "387878561, 10897366" + else: "-0.252276, 0.714471" + source = "data weights as " & representation & " = " & weights & + "\ndata biases as " & representation & " = " & biases & "\n" & """ +dim features(2) +dim actual(1) +dim expected(1) +for trial = 0 to 63 + features(0) = trial - 32 + features(1) = trial * 3 - 57 + features(2) = 77 - trial + for row = 0 to 1 + expected(row) = biases(row) + for column = 0 to 2 + expected(row) = expected(row) + features(column) * weights(row * 3 + column) + next column + next row + linear(features, weights, biases, actual, 3, 2) + for row = 0 to 1 + if actual(row) <> expected(row) then mismatches = mismatches + 1 + next row +next trial +""" + schema = host() + var runtime = initRuntime(compile(source, schema), schema) + discard runtime.run() + doAssert runtime.getGlobal("mismatches") == 0 + +echo "Testing generic arithmetic, ReLU, masks, and first-index ties" +block: + let + schema = host() + program = compile(""" +data x = -2, 3, 3, 500 +data y = 4, 5, -7, 500 +data mask = 0, 0, 1, 1 +data emptyMask = 0, 0, 0 +dim sums(3) +dim products(3) +dim copied(3) +dataAdd(x, y, sums, 3) +dataMultiply(x, y, products, 3) +dataCopy(x, copied, 3) +relu(copied, 3) +best = argmax(copied, 3) +allowed = argmaxMasked(x, mask, 3) +empty = argmaxMasked(x, emptyMask, 3) +dot = dataDot(x, y, 3) +dataFill(products, 17, 2) +dataAdd(sums, sums, sums, 3) +""", schema) + var runtime = initRuntime(program, schema) + discard runtime.run() + doAssert runtime.getArray("sums", 0) == 4 + doAssert runtime.getArray("sums", 1) == 16 + doAssert runtime.getArray("sums", 2) == -8 + doAssert runtime.getArray("products", 0) == 17 + doAssert runtime.getArray("products", 2) == -21 + doAssert runtime.getArray("copied", 0) == 0 + doAssert runtime.getArray("copied", 3) == 0 + doAssert runtime.getGlobal("best") == 1 + doAssert runtime.getGlobal("allowed") == 2 + doAssert runtime.getGlobal("empty") == -1 + doAssert runtime.getGlobal("dot") == -14 + +echo "Testing shape, mutability, and alias validation before mutation" +for call in [ + "linear(x, weights, biases, output, 0, 2)", + "linear(x, weights, biases, output, -1, 2)", + "linear(x, weights, biases, output, 2147483647, 2147483647)", + "linear(x, weights, biases, output, 2, 3)", + "linear(x, weights, biases, output, 1.5, 2)", + "linear(x, weights, biases, weights, 2, 2)", + "linear(output, weights, biases, output, 2, 2)", + "relu(weights, 4)", + "dataFill(weights, 1, 4)", + "dataFill(output, \"bad\", 2)", + "dataAdd(x, x, output, 3)", + "argmax(x, 0)", + "argmaxMasked(x, biases, 3)", + "relu(-1, 2)", + "argmax(texts, 1)" +]: + let + schema = host() + source = """ +data x = 2, 3 +data weights = 1, 2, 3, 4 +data biases = 5, 6 +dim output(1) +dim texts$(0) +""" & call.replace("texts,", "texts$,") + var runtime = initRuntime(compile(source, schema), schema) + rejects(proc() = discard runtime.run(), "") + doAssert runtime.getArray("output", 0) == 0 + doAssert runtime.getArray("output", 1) == 0 + +echo "Testing native operations count against both shared budgets" +block: + let + schema = host() + program = compile(""" +data x = 2, 3 +data weights = 1, 2, 3, 4 +data biases = 5, 6 +dim output(1) +linear(x, weights, biases, output, 2, 2) +""", schema) + var full = initRuntime(program, schema) + let stats = full.run() + doAssert stats.instructions >= 12 + doAssert stats.workUnits >= 12 + for instructions in [true, false]: + var limits = defaultLimits() + if instructions: + limits.maxInstructions = stats.instructions - 12 + else: + limits.maxWorkUnits = stats.workUnits - 12 + var blocked = initRuntime(program, schema, limits) + rejects(proc() = discard blocked.run(), "limit exceeded") + doAssert blocked.getArray("output", 0) == 0 + doAssert blocked.getArray("output", 1) == 0 + if instructions: + limits.maxInstructions = stats.instructions + else: + limits.maxWorkUnits = stats.workUnits + var exact = initRuntime(program, schema, limits) + discard exact.run() + doAssert exact.getArray("output", 0) == 13 + doAssert exact.getArray("output", 1) == 24 + let bytes = full.memoryBytes + for iteration in 0 ..< 100: + full.restart() + discard full.run() + doAssert full.memoryBytes == bytes + +echo "test_arrays: all checks passed" diff --git a/tests/tests.nim b/tests/tests.nim index 88386a14..16e75b54 100644 --- a/tests/tests.nim +++ b/tests/tests.nim @@ -2,6 +2,7 @@ {.warning[UnusedImport]: off.} import + test_arrays, test_assetpacks, test_actioncam, test_animblend_controls, From 9e7a89c7be0f826ec51cf2ef0df89781906ea6da Mon Sep 17 00:00:00 2001 From: treeform Date: Thu, 24 Sep 2026 10:59:33 -0700 Subject: [PATCH 2/7] Clarify fixed point only numeric execution --- docs/neural-arrays.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/neural-arrays.md b/docs/neural-arrays.md index 0fc2844d..62b9861e 100644 --- a/docs/neural-arrays.md +++ b/docs/neural-arrays.md @@ -34,9 +34,12 @@ must be at the top level. A DATA array must contain at least one element. Without `AS`, integer literals remain signed int32 and decimal literals use Bassy's deterministic Q16.16 fixed-point representation. `AS int32` requires -exact integers; `AS fixed32` converts every literal to Q16.16. Float32 is not -part of this interface. These are the same numeric semantics as ordinary BASIC -expressions, including int32 wraparound and fixed-point rounding. +exact integers; `AS fixed32` converts every literal to Q16.16. Here `fixed32` +means a signed 32-bit fixed-point value with 16 fractional bits. Numeric +execution uses only int32 and Q16.16. Decimal literals are parsed directly into +fixed-point values, and the language has no floating-point type or conversion +API. The native neural operations preserve ordinary BASIC's int32 wraparound +and fixed-point rounding. `weights(0)` reads an element. A bare `weights`, or `weights()`, supplies a program-local array handle to a host function. Handles are checked against the From 745c354673d14efc81ab91443fab9d0aa7d7fd76 Mon Sep 17 00:00:00 2001 From: treeform Date: Thu, 24 Sep 2026 11:12:54 -0700 Subject: [PATCH 3/7] Keep original neural policy in tmp --- experiments/neural-policy/bench_neural.nim | 2 +- experiments/neural-policy/original.bas | 1147 -------------------- experiments/neural-policy/results.md | 14 +- 3 files changed, 10 insertions(+), 1153 deletions(-) delete mode 100644 experiments/neural-policy/original.bas diff --git a/experiments/neural-policy/bench_neural.nim b/experiments/neural-policy/bench_neural.nim index a1202b1b..ebead925 100644 --- a/experiments/neural-policy/bench_neural.nim +++ b/experiments/neural-policy/bench_neural.nim @@ -6,7 +6,7 @@ import const Directory = currentSourcePath().parentDir let - original = readFile(Directory / "original.bas") + original = readFile(Directory / "../../tmp/neural-parity/original.bas") converted = readFile( Directory / "../../examples/gods_of_the_arena/players/neural.bas" ) diff --git a/experiments/neural-policy/original.bas b/experiments/neural-policy/original.bas deleted file mode 100644 index 23714260..00000000 --- a/experiments/neural-policy/original.bas +++ /dev/null @@ -1,1147 +0,0 @@ -' Gods of the Arena 2026.9.21.3 compatibility layer. -' Draft a balanced roster, spend ability points, buy back, and keep the -' previously trained neural clock relative to the start of combat. -dim draftF(31) -sub chooseHero() - if draftTurnId <> selfId then - exit sub - end if - for draftClass = 0 to 9 - draftF(draftClass) = heroAvailable(draftClass) - draftF(10 + draftClass) = 0 - draftF(20 + draftClass) = 0 - next draftClass - draftAllyCount = 0 - draftEnemyCount = 0 - for draftPlayer = 0 to draftPlayerCount() - 1 - draftPicked = draftedClass(draftPlayerId(draftPlayer)) - if draftPicked >= 0 then - if draftPlayerTeam(draftPlayer) = selfTeam then - draftF(10 + draftPicked) = 1 - draftAllyCount = draftAllyCount + 1 - else - draftF(20 + draftPicked) = 1 - draftEnemyCount = draftEnemyCount + 1 - end if - end if - next draftPlayer - draftF(30) = draftAllyCount / 5 - draftF(31) = draftEnemyCount / 5 - draftBestClass = -1 - draftBestScore = -2147483647 - if draftF(0) then - draftScore = -0.252276 - draftScore = draftScore + draftF(0) * -0.252276 - draftScore = draftScore + draftF(1) * 0.208388 - draftScore = draftScore + draftF(2) * 0.165655 - draftScore = draftScore + draftF(3) * -0.077038 - draftScore = draftScore + draftF(4) * -0.307499 - draftScore = draftScore + draftF(5) * 0.393190 - draftScore = draftScore + draftF(6) * -0.041722 - draftScore = draftScore + draftF(7) * -0.247450 - draftScore = draftScore + draftF(8) * -0.267264 - draftScore = draftScore + draftF(9) * 0.060881 - draftScore = draftScore + draftF(11) * -0.056464 - draftScore = draftScore + draftF(12) * -0.558547 - draftScore = draftScore + draftF(13) * 0.238349 - draftScore = draftScore + draftF(14) * 0.148874 - draftScore = draftScore + draftF(15) * -0.351042 - draftScore = draftScore + draftF(16) * 0.022783 - draftScore = draftScore + draftF(17) * 0.133934 - draftScore = draftScore + draftF(18) * -0.148994 - draftScore = draftScore + draftF(19) * -0.127461 - draftScore = draftScore + draftF(21) * -0.404200 - draftScore = draftScore + draftF(22) * 0.140616 - draftScore = draftScore + draftF(23) * -0.413587 - draftScore = draftScore + draftF(24) * -0.093651 - draftScore = draftScore + draftF(25) * -0.294424 - draftScore = draftScore + draftF(26) * -0.233337 - draftScore = draftScore + draftF(27) * -0.138760 - draftScore = draftScore + draftF(28) * 0.163981 - draftScore = draftScore + draftF(29) * -0.185696 - draftScore = draftScore + draftF(30) * -0.139714 - draftScore = draftScore + draftF(31) * -0.291812 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 0 - end if - end if - if draftF(1) then - draftScore = 0.220766 - draftScore = draftScore + draftF(0) * 0.252661 - draftScore = draftScore + draftF(1) * 0.220766 - draftScore = draftScore + draftF(2) * -0.066995 - draftScore = draftScore + draftF(3) * 0.046584 - draftScore = draftScore + draftF(4) * -0.074833 - draftScore = draftScore + draftF(5) * 0.282953 - draftScore = draftScore + draftF(6) * -0.316809 - draftScore = draftScore + draftF(7) * 0.260309 - draftScore = draftScore + draftF(8) * 0.498069 - draftScore = draftScore + draftF(9) * 0.607530 - draftScore = draftScore + draftF(10) * -0.054461 - draftScore = draftScore + draftF(12) * 0.185443 - draftScore = draftScore + draftF(13) * 0.307806 - draftScore = draftScore + draftF(14) * 0.073798 - draftScore = draftScore + draftF(15) * 0.095548 - draftScore = draftScore + draftF(16) * -0.624112 - draftScore = draftScore + draftF(17) * -0.128341 - draftScore = draftScore + draftF(18) * 0.116202 - draftScore = draftScore + draftF(19) * -0.113738 - draftScore = draftScore + draftF(20) * 0.022566 - draftScore = draftScore + draftF(22) * 0.102318 - draftScore = draftScore + draftF(23) * -0.133625 - draftScore = draftScore + draftF(24) * 0.221801 - draftScore = draftScore + draftF(25) * -0.157735 - draftScore = draftScore + draftF(26) * 1.161687 - draftScore = draftScore + draftF(27) * 0.088798 - draftScore = draftScore + draftF(28) * -0.393505 - draftScore = draftScore + draftF(29) * -0.273026 - draftScore = draftScore + draftF(30) * -0.028371 - draftScore = draftScore + draftF(31) * 0.127856 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 1 - end if - end if - if draftF(2) then - draftScore = 0.306957 - draftScore = draftScore + draftF(0) * 0.160424 - draftScore = draftScore + draftF(1) * -0.457565 - draftScore = draftScore + draftF(2) * 0.306957 - draftScore = draftScore + draftF(3) * -0.351070 - draftScore = draftScore + draftF(4) * 0.501679 - draftScore = draftScore + draftF(5) * 0.772043 - draftScore = draftScore + draftF(6) * -0.220549 - draftScore = draftScore + draftF(7) * -0.161796 - draftScore = draftScore + draftF(8) * 0.448017 - draftScore = draftScore + draftF(9) * -0.150996 - draftScore = draftScore + draftF(10) * 0.136125 - draftScore = draftScore + draftF(11) * 0.178068 - draftScore = draftScore + draftF(13) * 0.247534 - draftScore = draftScore + draftF(14) * -0.188988 - draftScore = draftScore + draftF(15) * -0.238955 - draftScore = draftScore + draftF(16) * 0.883443 - draftScore = draftScore + draftF(17) * 0.034082 - draftScore = draftScore + draftF(18) * 0.088084 - draftScore = draftScore + draftF(20) * 0.010408 - draftScore = draftScore + draftF(21) * 0.586454 - draftScore = draftScore + draftF(23) * 0.410493 - draftScore = draftScore + draftF(24) * -0.005734 - draftScore = draftScore + draftF(25) * -0.226131 - draftScore = draftScore + draftF(26) * -0.355937 - draftScore = draftScore + draftF(27) * 0.434671 - draftScore = draftScore + draftF(28) * -0.229144 - draftScore = draftScore + draftF(29) * 0.457953 - draftScore = draftScore + draftF(30) * 0.227879 - draftScore = draftScore + draftF(31) * 0.216607 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 2 - end if - end if - if draftF(3) then - draftScore = -0.102972 - draftScore = draftScore + draftF(0) * 0.191406 - draftScore = draftScore + draftF(1) * -0.378401 - draftScore = draftScore + draftF(2) * -0.681596 - draftScore = draftScore + draftF(3) * -0.102972 - draftScore = draftScore + draftF(4) * 0.400384 - draftScore = draftScore + draftF(5) * -0.198847 - draftScore = draftScore + draftF(6) * -0.095249 - draftScore = draftScore + draftF(7) * -0.499520 - draftScore = draftScore + draftF(8) * 0.154836 - draftScore = draftScore + draftF(9) * 0.124309 - draftScore = draftScore + draftF(10) * -0.098351 - draftScore = draftScore + draftF(11) * 0.079512 - draftScore = draftScore + draftF(12) * 0.896409 - draftScore = draftScore + draftF(14) * -0.105452 - draftScore = draftScore + draftF(15) * -0.086497 - draftScore = draftScore + draftF(16) * -0.053812 - draftScore = draftScore + draftF(17) * 0.204039 - draftScore = draftScore + draftF(18) * -0.676251 - draftScore = draftScore + draftF(19) * 0.027087 - draftScore = draftScore + draftF(20) * -0.196028 - draftScore = draftScore + draftF(21) * 0.195917 - draftScore = draftScore + draftF(22) * -0.317785 - draftScore = draftScore + draftF(24) * -0.397904 - draftScore = draftScore + draftF(25) * 0.182371 - draftScore = draftScore + draftF(26) * 0.046089 - draftScore = draftScore + draftF(27) * 0.192509 - draftScore = draftScore + draftF(28) * 0.418443 - draftScore = draftScore + draftF(29) * -0.254368 - draftScore = draftScore + draftF(30) * 0.037337 - draftScore = draftScore + draftF(31) * -0.026151 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 3 - end if - end if - if draftF(4) then - draftScore = -0.279421 - draftScore = draftScore + draftF(0) * -0.615030 - draftScore = draftScore + draftF(1) * 0.124070 - draftScore = draftScore + draftF(2) * -0.215150 - draftScore = draftScore + draftF(3) * -0.227015 - draftScore = draftScore + draftF(4) * -0.279421 - draftScore = draftScore + draftF(5) * -0.805966 - draftScore = draftScore + draftF(6) * -0.051813 - draftScore = draftScore + draftF(7) * 0.012569 - draftScore = draftScore + draftF(8) * -0.390292 - draftScore = draftScore + draftF(9) * -0.465863 - draftScore = draftScore + draftF(10) * 0.349372 - draftScore = draftScore + draftF(11) * 0.302593 - draftScore = draftScore + draftF(12) * -0.114656 - draftScore = draftScore + draftF(13) * -0.015071 - draftScore = draftScore + draftF(15) * 0.281769 - draftScore = draftScore + draftF(16) * -0.040930 - draftScore = draftScore + draftF(17) * 0.011989 - draftScore = draftScore + draftF(18) * -0.098396 - draftScore = draftScore + draftF(19) * -0.247384 - draftScore = draftScore + draftF(20) * -0.013763 - draftScore = draftScore + draftF(21) * -0.706084 - draftScore = draftScore + draftF(22) * 0.050385 - draftScore = draftScore + draftF(23) * -0.037334 - draftScore = draftScore + draftF(25) * 0.244777 - draftScore = draftScore + draftF(26) * -0.186678 - draftScore = draftScore + draftF(27) * -0.303979 - draftScore = draftScore + draftF(28) * 0.209267 - draftScore = draftScore + draftF(29) * 0.433826 - draftScore = draftScore + draftF(30) * 0.085857 - draftScore = draftScore + draftF(31) * -0.061916 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 4 - end if - end if - if draftF(5) then - draftScore = -0.242457 - draftScore = draftScore + draftF(0) * -0.232312 - draftScore = draftScore + draftF(1) * -0.183645 - draftScore = draftScore + draftF(2) * -0.270940 - draftScore = draftScore + draftF(3) * -0.249030 - draftScore = draftScore + draftF(4) * -0.064811 - draftScore = draftScore + draftF(5) * -0.242457 - draftScore = draftScore + draftF(6) * -0.040038 - draftScore = draftScore + draftF(7) * -0.037761 - draftScore = draftScore + draftF(8) * -0.901328 - draftScore = draftScore + draftF(9) * 0.027995 - draftScore = draftScore + draftF(10) * -0.392084 - draftScore = draftScore + draftF(11) * -0.497526 - draftScore = draftScore + draftF(12) * 0.312257 - draftScore = draftScore + draftF(13) * 0.104801 - draftScore = draftScore + draftF(14) * -0.183950 - draftScore = draftScore + draftF(16) * 0.045312 - draftScore = draftScore + draftF(17) * 0.127481 - draftScore = draftScore + draftF(18) * 0.460752 - draftScore = draftScore + draftF(19) * -0.051012 - draftScore = draftScore + draftF(20) * 0.381939 - draftScore = draftScore + draftF(21) * 0.438713 - draftScore = draftScore + draftF(22) * -0.283774 - draftScore = draftScore + draftF(23) * -0.098228 - draftScore = draftScore + draftF(24) * 0.006305 - draftScore = draftScore + draftF(26) * -0.247731 - draftScore = draftScore + draftF(27) * -0.332176 - draftScore = draftScore + draftF(28) * 0.198120 - draftScore = draftScore + draftF(29) * -0.219440 - draftScore = draftScore + draftF(30) * -0.014794 - draftScore = draftScore + draftF(31) * -0.031255 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 5 - end if - end if - if draftF(6) then - draftScore = 0.714471 - draftScore = draftScore + draftF(0) * 0.727924 - draftScore = draftScore + draftF(1) * 0.905780 - draftScore = draftScore + draftF(2) * 0.451952 - draftScore = draftScore + draftF(3) * 0.593039 - draftScore = draftScore + draftF(4) * 0.580693 - draftScore = draftScore + draftF(5) * 0.784972 - draftScore = draftScore + draftF(6) * 0.714471 - draftScore = draftScore + draftF(7) * 0.539523 - draftScore = draftScore + draftF(8) * 0.567051 - draftScore = draftScore + draftF(9) * 0.609534 - draftScore = draftScore + draftF(10) * 0.213345 - draftScore = draftScore + draftF(11) * -0.291630 - draftScore = draftScore + draftF(12) * 0.069507 - draftScore = draftScore + draftF(14) * 0.103936 - draftScore = draftScore + draftF(15) * 0.023463 - draftScore = draftScore + draftF(17) * 0.043553 - draftScore = draftScore + draftF(18) * 0.061384 - draftScore = draftScore + draftF(19) * 0.061384 - draftScore = draftScore + draftF(20) * -0.226798 - draftScore = draftScore + draftF(21) * 0.100322 - draftScore = draftScore + draftF(22) * 0.193012 - draftScore = draftScore + draftF(23) * 0.121433 - draftScore = draftScore + draftF(24) * 0.029843 - draftScore = draftScore + draftF(25) * -0.093964 - draftScore = draftScore + draftF(27) * 0.131395 - draftScore = draftScore + draftF(28) * 0.086036 - draftScore = draftScore + draftF(29) * 0.043553 - draftScore = draftScore + draftF(30) * 0.056989 - draftScore = draftScore + draftF(31) * 0.076966 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 6 - end if - end if - if draftF(7) then - draftScore = 0.092192 - draftScore = draftScore + draftF(0) * 0.168151 - draftScore = draftScore + draftF(1) * -0.445483 - draftScore = draftScore + draftF(2) * -0.279111 - draftScore = draftScore + draftF(3) * 0.122942 - draftScore = draftScore + draftF(4) * -0.494774 - draftScore = draftScore + draftF(5) * -0.299634 - draftScore = draftScore + draftF(6) * -0.131236 - draftScore = draftScore + draftF(7) * 0.092192 - draftScore = draftScore + draftF(8) * 0.164071 - draftScore = draftScore + draftF(9) * -0.176015 - draftScore = draftScore + draftF(10) * 0.002602 - draftScore = draftScore + draftF(11) * 0.310725 - draftScore = draftScore + draftF(12) * -0.538125 - draftScore = draftScore + draftF(13) * 0.274210 - draftScore = draftScore + draftF(14) * 0.488313 - draftScore = draftScore + draftF(15) * -0.038538 - draftScore = draftScore + draftF(16) * 0.069213 - draftScore = draftScore + draftF(18) * 0.434066 - draftScore = draftScore + draftF(19) * 0.183095 - draftScore = draftScore + draftF(20) * -0.078561 - draftScore = draftScore + draftF(21) * 0.226950 - draftScore = draftScore + draftF(22) * 0.909428 - draftScore = draftScore + draftF(23) * -0.304960 - draftScore = draftScore + draftF(24) * 0.098653 - draftScore = draftScore + draftF(25) * 0.430365 - draftScore = draftScore + draftF(26) * 0.154216 - draftScore = draftScore + draftF(28) * -0.505944 - draftScore = draftScore + draftF(29) * 0.085113 - draftScore = draftScore + draftF(30) * 0.237112 - draftScore = draftScore + draftF(31) * 0.203052 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 7 - end if - end if - if draftF(8) then - draftScore = 0.041381 - draftScore = draftScore + draftF(0) * -0.294455 - draftScore = draftScore + draftF(1) * 0.111733 - draftScore = draftScore + draftF(2) * 0.787697 - draftScore = draftScore + draftF(3) * 0.595129 - draftScore = draftScore + draftF(4) * 0.080587 - draftScore = draftScore + draftF(5) * -0.130948 - draftScore = draftScore + draftF(6) * 0.222401 - draftScore = draftScore + draftF(7) * 0.450339 - draftScore = draftScore + draftF(8) * 0.041381 - draftScore = draftScore + draftF(9) * -0.138733 - draftScore = draftScore + draftF(10) * -0.194858 - draftScore = draftScore + draftF(11) * 0.316302 - draftScore = draftScore + draftF(12) * -0.090298 - draftScore = draftScore + draftF(13) * -1.080760 - draftScore = draftScore + draftF(14) * -0.178166 - draftScore = draftScore + draftF(15) * -0.011497 - draftScore = draftScore + draftF(16) * -0.011433 - draftScore = draftScore + draftF(17) * -0.360402 - draftScore = draftScore + draftF(19) * 0.268030 - draftScore = draftScore + draftF(20) * 0.530694 - draftScore = draftScore + draftF(21) * -0.386654 - draftScore = draftScore + draftF(22) * -0.656018 - draftScore = draftScore + draftF(23) * 0.527012 - draftScore = draftScore + draftF(24) * 0.138960 - draftScore = draftScore + draftF(25) * 0.183827 - draftScore = draftScore + draftF(26) * -0.169586 - draftScore = draftScore + draftF(27) * -0.048555 - draftScore = draftScore + draftF(29) * -0.087915 - draftScore = draftScore + draftF(30) * -0.268616 - draftScore = draftScore + draftF(31) * 0.006353 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 8 - end if - end if - if draftF(9) then - draftScore = -0.498641 - draftScore = draftScore + draftF(0) * -0.106492 - draftScore = draftScore + draftF(1) * -0.105644 - draftScore = draftScore + draftF(2) * -0.198469 - draftScore = draftScore + draftF(3) * -0.350568 - draftScore = draftScore + draftF(4) * -0.342004 - draftScore = draftScore + draftF(5) * -0.555305 - draftScore = draftScore + draftF(6) * -0.039455 - draftScore = draftScore + draftF(7) * -0.408405 - draftScore = draftScore + draftF(8) * -0.314541 - draftScore = draftScore + draftF(9) * -0.498641 - draftScore = draftScore + draftF(10) * 0.038310 - draftScore = draftScore + draftF(11) * -0.341580 - draftScore = draftScore + draftF(12) * -0.161989 - draftScore = draftScore + draftF(13) * -0.076870 - draftScore = draftScore + draftF(14) * -0.158366 - draftScore = draftScore + draftF(15) * 0.325749 - draftScore = draftScore + draftF(16) * -0.290464 - draftScore = draftScore + draftF(17) * -0.066335 - draftScore = draftScore + draftF(18) * -0.236848 - draftScore = draftScore + draftF(20) * -0.430459 - draftScore = draftScore + draftF(21) * -0.051418 - draftScore = draftScore + draftF(22) * -0.138183 - draftScore = draftScore + draftF(23) * -0.071204 - draftScore = draftScore + draftF(24) * 0.001728 - draftScore = draftScore + draftF(25) * -0.269085 - draftScore = draftScore + draftF(26) * -0.168722 - draftScore = draftScore + draftF(27) * -0.023902 - draftScore = draftScore + draftF(28) * 0.052747 - draftScore = draftScore + draftF(30) * -0.193678 - draftScore = draftScore + draftF(31) * -0.219699 - if draftScore > draftBestScore then - draftBestScore = draftScore - draftBestClass = 9 - end if - end if - if draftBestClass >= 0 then - draftHero(draftBestClass) - end if -end sub - -if drafting then - chooseHero() - end -end if - -if battleStarted = 0 then - battleStartTick = worldTick - battleStarted = 1 -end if -battleTick = worldTick - battleStartTick - -if selfHp <= 0 then - buybackCost = buybackPrice() - if buybackCost > 0 and selfGold >= buybackCost then - buyback() - end if - end -end if - -' Spend every currently legal point. The trained actor favors W, then E/Q; -' take the ultimate whenever its level gate opens. -for upgrade = 1 to 4 - if canLevelAbility(3) then - levelAbility(3) - elseif canLevelAbility(1) then - levelAbility(1) - elseif canLevelAbility(2) then - levelAbility(2) - elseif canLevelAbility(0) then - levelAbility(0) - end if -next upgrade - -dim f(27) -bestId = 0 -bestDistance = 2147483647 -objectiveId = 0 -objectiveKind = 0 -siegeThreatId = 0 -siegeThreatHp = 2147483647 -objectiveDistance = 2147483647 -heroId = 0 -heroDistance = 2147483647 -enemyX = selfX -enemyY = selfY -enemyHp = 0 -enemyKind = 0 -enemyAttackTarget = 0 -routeOpeningX = 71 -routeOpeningY = 12 -if selfTeam = 1 then - routeOpeningX = 45 - routeOpeningY = 104 -end if -routeGroupCount = 0 -if selfTeam = 0 then - gateHomeX = 105 - gateHomeY = 10 -else - gateHomeX = 10 - gateHomeY = 105 -end if -gateThreatDistance = 2147483647 -gateThreatSeen = 0 -gateThreatX = gateHomeX -reserveHome = 0 -allyCount = 0 -allyX = 0 -allyY = 0 -maxAllyDistance = 0 -index = 0 -while index < objectCount() - if objectAlive(index) then - if objectTeam(index) = selfTeam and objectKind(index) = 1 then - reserveHome = 1 - reserveHomeId = objectId(index) - reserveX = objectX(index) - reserveY = objectY(index) - end if - if objectTeam(index) = selfTeam and objectKind(index) = 2 then - allyCount = allyCount + 1 - routeDx = objectX(index) - routeOpeningX - routeDy = objectY(index) - routeOpeningY - if routeDx * routeDx + routeDy * routeDy <= 16 then - routeGroupCount = routeGroupCount + 1 - end if - allyX = allyX + objectX(index) - allyY = allyY + objectY(index) - dx = objectX(index) - selfX - dy = objectY(index) - selfY - distance = dx * dx + dy * dy - if distance > maxAllyDistance then - maxAllyDistance = distance - end if - end if - if objectTeam(index) <> selfTeam then - if battleTick < 1000 and (objectKind(index) = 2 or objectKind(index) = 3) then - gateDx = objectX(index) - gateHomeX - gateDy = objectY(index) - gateHomeY - gateDistance = gateDx * gateDx + gateDy * gateDy - if gateDistance < gateThreatDistance then - gateThreatDistance = gateDistance - gateThreatSeen = 1 - gateThreatX = objectX(index) - end if - end if - dx = objectX(index) - selfX - dy = objectY(index) - selfY - distance = dx * dx + dy * dy - if distance < bestDistance then - bestDistance = distance - bestId = objectId(index) - enemyX = objectX(index) - enemyY = objectY(index) - enemyHp = objectHp(index) - enemyKind = objectKind(index) - enemyAttackTarget = objectTarget(index) - end if - if objectKind(index) = 2 and objectTarget(index) = selfId then - siegeRange = selfAttackRange \ 1000 - if distance * 3600 <= siegeRange * siegeRange and objectHp(index) < siegeThreatHp then - siegeThreatId = objectId(index) - siegeThreatHp = objectHp(index) - end if - end if - if objectKind(index) = 1 or objectKind(index) = 4 then - if distance < objectiveDistance then - objectiveDistance = distance - objectiveId = objectId(index) - objectiveKind = objectKind(index) - end if - end if - if objectKind(index) = 2 and distance < heroDistance then - heroDistance = distance - heroId = objectId(index) - ringHeroX = objectX(index) - ringHeroY = objectY(index) - ringHeroTarget = objectTarget(index) - end if - end if - end if - index = index + 1 -wend -if allyCount > 0 then - allyX = allyX \ allyCount - allyY = allyY \ allyCount -else - allyX = selfX - allyY = selfY -end if -f(0) = selfHp * 100 \ selfMaxHp -f(1) = 0 -if selfMaxMana > 0 then - f(1) = selfMana * 100 \ selfMaxMana -end if -f(2) = maxAllyDistance \ 100 -f(3) = selfLevel * 5 -f(4) = allyX - selfX -f(5) = allyY - selfY -f(6) = enemyX - selfX -f(7) = enemyY - selfY -f(8) = enemyHp \ 10 -f(9) = enemyKind * 25 -if selfTeam = 1 then - f(4) = 0 - f(4) - f(5) = 0 - f(5) - f(6) = 0 - f(6) - f(7) = 0 - f(7) -end if -f(10) = battleTick \ 288 -f(11) = abilityCharges(0) * 25 -f(12) = abilityCharges(1) * 25 -f(13) = abilityCharges(2) * 25 -f(14) = abilityCharges(3) * 25 -f(15 + selfClass) = 100 -f(25) = battleTick \ 16 -if battleTick < 1000 and gateThreatSeen = 1 then - gateEarlyThreatX = gateThreatX - gateEarlyThreatSeen = 1 -end if -f(26) = 0 -if gateEarlyThreatSeen = 1 then - f(26) = gateEarlyThreatX - gateHomeX - if selfTeam = 1 then - f(26) = 0 - f(26) - end if -end if -index = 0 -while index < 27 - if f(index) > 100 then - f(index) = 100 - end if - if f(index) < -100 then - f(index) = -100 - end if - index = index + 1 -wend -dim h(16) -neuralActionCountdown = neuralActionCountdown - 1 -if neuralActionCountdown <= 0 then - neuralActionCountdown = 4 -h(0) = 18208 + f(0) * (263) + f(1) * (222) + f(2) * (-62) + f(3) * (-8) + f(4) * (156) + f(5) * (176) + f(6) * (-660) + f(7) * (470) + f(8) * (178) + f(9) * (648) + f(10) * (309) + f(11) * (38) + f(12) * (78) + f(13) * (-150) + f(14) * (200) + f(15) * (5) + f(16) * (-491) + f(17) * (183) + f(18) * (-26) + f(19) * (311) + f(20) * (646) + f(21) * (80) + f(22) * (544) + f(23) * (39) + f(24) * (255) -if h(0) < 0 then - h(0) = 0 -end if -h(1) = 18392 + f(0) * (126) + f(1) * (-23) + f(2) * (117) + f(3) * (-25) + f(4) * (585) + f(5) * (-305) + f(6) * (-320) + f(7) * (118) + f(8) * (235) + f(9) * (32) + f(10) * (21) + f(11) * (-234) + f(12) * (656) + f(13) * (93) + f(14) * (602) + f(15) * (414) + f(16) * (439) + f(17) * (243) + f(18) * (243) + f(19) * (-340) + f(20) * (187) + f(21) * (-78) + f(22) * (72) + f(23) * (-522) + f(24) * (315) -if h(1) < 0 then - h(1) = 0 -end if -h(2) = 3752 + f(0) * (-346) + f(1) * (10) + f(2) * (-181) + f(3) * (180) + f(4) * (64) + f(5) * (725) + f(6) * (-25) + f(7) * (71) + f(8) * (104) + f(9) * (-523) + f(10) * (-232) + f(11) * (-80) + f(12) * (-27) + f(13) * (-30) + f(14) * (-313) + f(15) * (-350) + f(16) * (-320) + f(17) * (-11) + f(18) * (493) + f(19) * (-48) + f(20) * (-128) + f(21) * (182) + f(22) * (-73) + f(23) * (-520) + f(24) * (-23) -if h(2) < 0 then - h(2) = 0 -end if -h(3) = 19627 + f(0) * (190) + f(1) * (158) + f(2) * (-294) + f(3) * (61) + f(4) * (-155) + f(5) * (-122) + f(6) * (280) + f(7) * (-28) + f(8) * (-22) + f(9) * (396) + f(10) * (438) + f(11) * (38) + f(12) * (385) + f(13) * (238) + f(14) * (-15) + f(15) * (-727) + f(16) * (119) + f(17) * (363) + f(18) * (-197) + f(19) * (-128) + f(20) * (-135) + f(21) * (-497) + f(22) * (121) + f(23) * (-12) + f(24) * (-15) -if h(3) < 0 then - h(3) = 0 -end if -h(4) = 18521 + f(0) * (431) + f(1) * (-81) + f(2) * (108) + f(3) * (-107) + f(4) * (178) + f(5) * (-317) + f(6) * (-339) + f(7) * (286) + f(8) * (230) + f(9) * (-103) + f(10) * (-293) + f(11) * (-141) + f(12) * (714) + f(13) * (388) + f(14) * (-197) + f(15) * (-213) + f(16) * (109) + f(17) * (384) + f(18) * (214) + f(19) * (562) + f(20) * (195) + f(21) * (323) + f(22) * (25) + f(23) * (668) + f(24) * (-158) -if h(4) < 0 then - h(4) = 0 -end if -h(5) = 18435 + f(0) * (360) + f(1) * (159) + f(2) * (398) + f(3) * (339) + f(4) * (-52) + f(5) * (-357) + f(6) * (-172) + f(7) * (110) + f(8) * (-22) + f(9) * (646) + f(10) * (125) + f(11) * (-453) + f(12) * (-365) + f(13) * (177) + f(14) * (230) + f(15) * (-367) + f(16) * (408) + f(17) * (465) + f(18) * (703) + f(19) * (61) + f(20) * (136) + f(21) * (543) + f(22) * (41) + f(23) * (-54) + f(24) * (54) -if h(5) < 0 then - h(5) = 0 -end if -h(6) = 23325 + f(0) * (122) + f(1) * (-158) + f(2) * (-130) + f(3) * (204) + f(4) * (-64) + f(5) * (-508) + f(6) * (639) + f(7) * (551) + f(8) * (299) + f(9) * (239) + f(10) * (-38) + f(11) * (-406) + f(12) * (-41) + f(13) * (-158) + f(14) * (-309) + f(15) * (353) + f(16) * (131) + f(17) * (-140) + f(18) * (-48) + f(19) * (244) + f(20) * (161) + f(21) * (174) + f(22) * (-9) + f(23) * (91) + f(24) * (715) -if h(6) < 0 then - h(6) = 0 -end if -h(7) = 19286 + f(0) * (-90) + f(1) * (489) + f(2) * (349) + f(3) * (361) + f(4) * (116) + f(5) * (-315) + f(6) * (-91) + f(7) * (-101) + f(8) * (238) + f(9) * (-164) + f(10) * (238) + f(11) * (-133) + f(12) * (125) + f(13) * (458) + f(14) * (371) + f(15) * (3) + f(16) * (30) + f(17) * (203) + f(18) * (-106) + f(19) * (482) + f(20) * (-345) + f(21) * (302) + f(22) * (932) + f(23) * (147) + f(24) * (505) -if h(7) < 0 then - h(7) = 0 -end if -h(8) = 18159 + f(0) * (101) + f(1) * (49) + f(2) * (180) + f(3) * (-178) + f(4) * (-311) + f(5) * (502) + f(6) * (-51) + f(7) * (361) + f(8) * (179) + f(9) * (638) + f(10) * (-61) + f(11) * (240) + f(12) * (314) + f(13) * (248) + f(14) * (-8) + f(15) * (483) + f(16) * (730) + f(17) * (314) + f(18) * (155) + f(19) * (291) + f(20) * (-257) + f(21) * (241) + f(22) * (543) + f(23) * (9) + f(24) * (-66) -if h(8) < 0 then - h(8) = 0 -end if -h(9) = 19073 + f(0) * (411) + f(1) * (470) + f(2) * (170) + f(3) * (61) + f(4) * (50) + f(5) * (-20) + f(6) * (-192) + f(7) * (-211) + f(8) * (60) + f(9) * (68) + f(10) * (-32) + f(11) * (-109) + f(12) * (-126) + f(13) * (-404) + f(14) * (49) + f(15) * (543) + f(16) * (-90) + f(17) * (554) + f(18) * (445) + f(19) * (341) + f(20) * (-324) + f(21) * (-471) + f(22) * (-27) + f(23) * (378) + f(24) * (114) -if h(9) < 0 then - h(9) = 0 -end if -h(10) = 18190 + f(0) * (861) + f(1) * (381) + f(2) * (130) + f(3) * (413) + f(4) * (-252) + f(5) * (152) + f(6) * (159) + f(7) * (421) + f(8) * (-140) + f(9) * (-151) + f(10) * (669) + f(11) * (-70) + f(12) * (515) + f(13) * (338) + f(14) * (102) + f(15) * (453) + f(16) * (-71) + f(17) * (383) + f(18) * (255) + f(19) * (-158) + f(20) * (195) + f(21) * (441) + f(22) * (133) + f(23) * (189) + f(24) * (264) -if h(10) < 0 then - h(10) = 0 -end if -h(11) = 19629 + f(0) * (82) + f(1) * (-702) + f(2) * (275) + f(3) * (91) + f(4) * (-190) + f(5) * (47) + f(6) * (-505) + f(7) * (-288) + f(8) * (-299) + f(9) * (-56) + f(10) * (716) + f(11) * (-16) + f(12) * (78) + f(13) * (18) + f(14) * (95) + f(15) * (6) + f(16) * (302) + f(17) * (-370) + f(18) * (419) + f(19) * (284) + f(20) * (2) + f(21) * (-157) + f(22) * (129) + f(23) * (332) + f(24) * (261) -if h(11) < 0 then - h(11) = 0 -end if -h(12) = 19929 + f(0) * (-187) + f(1) * (342) + f(2) * (-126) + f(3) * (-271) + f(4) * (-109) + f(5) * (-323) + f(6) * (149) + f(7) * (43) + f(8) * (-690) + f(9) * (236) + f(10) * (-95) + f(11) * (383) + f(12) * (276) + f(13) * (62) + f(14) * (485) + f(15) * (215) + f(16) * (-249) + f(17) * (-34) + f(18) * (413) + f(19) * (130) + f(20) * (137) + f(21) * (562) + f(22) * (61) + f(23) * (214) + f(24) * (123) -if h(12) < 0 then - h(12) = 0 -end if -h(13) = 21374 + f(0) * (-49) + f(1) * (121) + f(2) * (69) + f(3) * (190) + f(4) * (856) + f(5) * (44) + f(6) * (-31) + f(7) * (-132) + f(8) * (-120) + f(9) * (458) + f(10) * (407) + f(11) * (-483) + f(12) * (6) + f(13) * (396) + f(14) * (-271) + f(15) * (452) + f(16) * (40) + f(17) * (-47) + f(18) * (-98) + f(19) * (-226) + f(20) * (-24) + f(21) * (342) + f(22) * (192) + f(23) * (508) + f(24) * (-284) -if h(13) < 0 then - h(13) = 0 -end if -h(14) = 18212 + f(0) * (432) + f(1) * (481) + f(2) * (400) + f(3) * (-699) + f(4) * (108) + f(5) * (103) + f(6) * (-31) + f(7) * (-289) + f(8) * (548) + f(9) * (208) + f(10) * (338) + f(11) * (67) + f(12) * (157) + f(13) * (80) + f(14) * (-99) + f(15) * (-136) + f(16) * (179) + f(17) * (-206) + f(18) * (274) + f(19) * (-139) + f(20) * (86) + f(21) * (412) + f(22) * (33) + f(23) * (278) + f(24) * (594) -if h(14) < 0 then - h(14) = 0 -end if -h(15) = 18656 + f(0) * (651) + f(1) * (160) + f(2) * (-271) + f(3) * (341) + f(4) * (228) + f(5) * (-548) + f(6) * (-380) + f(7) * (170) + f(8) * (260) + f(9) * (78) + f(10) * (188) + f(11) * (579) + f(12) * (-65) + f(13) * (58) + f(14) * (-197) + f(15) * (173) + f(16) * (208) + f(17) * (-353) + f(18) * (354) + f(19) * (-48) + f(20) * (-264) + f(21) * (293) + f(22) * (413) + f(23) * (-53) + f(24) * (-198) -if h(15) < 0 then - h(15) = 0 -end if -decision = 0 -bestScore = -2147483647 -score = 387878561 + h(0) * (-128) + h(1) * (-127) + h(2) * (-9) + h(3) * (-160) + h(4) * (-125) + h(5) * (-151) + h(6) * (-198) + h(7) * (-129) + h(8) * (-126) + h(9) * (-140) + h(10) * (-111) + h(11) * (-199) + h(12) * (-146) + h(13) * (-192) + h(14) * (-125) + h(15) * (-129) -if score > bestScore then - bestScore = score - decision = 0 -end if -score = 8621915 + h(0) * (113) + h(1) * (102) + h(2) * (10) + h(3) * (106) + h(4) * (96) + h(5) * (110) + h(6) * (83) + h(7) * (86) + h(8) * (101) + h(9) * (96) + h(10) * (95) + h(11) * (62) + h(12) * (124) + h(13) * (115) + h(14) * (101) + h(15) * (103) -if score > bestScore then - bestScore = score - decision = 1 -end if -score = 9365853 + h(0) * (114) + h(1) * (122) + h(2) * (25) + h(3) * (121) + h(4) * (105) + h(5) * (113) + h(6) * (104) + h(7) * (113) + h(8) * (112) + h(9) * (113) + h(10) * (102) + h(11) * (86) + h(12) * (104) + h(13) * (132) + h(14) * (98) + h(15) * (113) -if score > bestScore then - bestScore = score - decision = 2 -end if -score = 10032520 + h(0) * (124) + h(1) * (135) + h(2) * (-1) + h(3) * (134) + h(4) * (118) + h(5) * (129) + h(6) * (105) + h(7) * (120) + h(8) * (110) + h(9) * (115) + h(10) * (108) + h(11) * (65) + h(12) * (116) + h(13) * (119) + h(14) * (124) + h(15) * (124) -if score > bestScore then - bestScore = score - decision = 3 -end if -score = 10514535 + h(0) * (111) + h(1) * (115) + h(2) * (18) + h(3) * (135) + h(4) * (112) + h(5) * (136) + h(6) * (94) + h(7) * (112) + h(8) * (107) + h(9) * (125) + h(10) * (98) + h(11) * (77) + h(12) * (105) + h(13) * (139) + h(14) * (114) + h(15) * (111) -if score > bestScore then - bestScore = score - decision = 4 -end if -score = 10182099 + h(0) * (118) + h(1) * (123) + h(2) * (-6) + h(3) * (122) + h(4) * (94) + h(5) * (112) + h(6) * (107) + h(7) * (125) + h(8) * (116) + h(9) * (113) + h(10) * (102) + h(11) * (68) + h(12) * (107) + h(13) * (137) + h(14) * (113) + h(15) * (113) -if score > bestScore then - bestScore = score - decision = 5 -end if -score = 9061429 + h(0) * (102) + h(1) * (104) + h(2) * (17) + h(3) * (120) + h(4) * (107) + h(5) * (107) + h(6) * (99) + h(7) * (105) + h(8) * (107) + h(9) * (108) + h(10) * (94) + h(11) * (94) + h(12) * (107) + h(13) * (134) + h(14) * (96) + h(15) * (112) -if score > bestScore then - bestScore = score - decision = 6 -end if -score = 10778495 + h(0) * (127) + h(1) * (114) + h(2) * (43) + h(3) * (132) + h(4) * (111) + h(5) * (125) + h(6) * (106) + h(7) * (120) + h(8) * (132) + h(9) * (123) + h(10) * (118) + h(11) * (89) + h(12) * (113) + h(13) * (133) + h(14) * (117) + h(15) * (131) -if score > bestScore then - bestScore = score - decision = 7 -end if -score = 9785349 + h(0) * (112) + h(1) * (119) + h(2) * (1) + h(3) * (125) + h(4) * (110) + h(5) * (130) + h(6) * (105) + h(7) * (116) + h(8) * (115) + h(9) * (109) + h(10) * (102) + h(11) * (95) + h(12) * (107) + h(13) * (142) + h(14) * (105) + h(15) * (115) -if score > bestScore then - bestScore = score - decision = 8 -end if -score = 387767339 + h(0) * (-137) + h(1) * (-135) + h(2) * (-16) + h(3) * (-166) + h(4) * (-133) + h(5) * (-152) + h(6) * (-209) + h(7) * (-142) + h(8) * (-122) + h(9) * (-142) + h(10) * (-121) + h(11) * (-185) + h(12) * (-152) + h(13) * (-198) + h(14) * (-139) + h(15) * (-141) -if score > bestScore then - bestScore = score - decision = 9 -end if -score = 9125238 + h(0) * (115) + h(1) * (113) + h(2) * (36) + h(3) * (104) + h(4) * (98) + h(5) * (120) + h(6) * (76) + h(7) * (94) + h(8) * (107) + h(9) * (122) + h(10) * (97) + h(11) * (66) + h(12) * (133) + h(13) * (122) + h(14) * (107) + h(15) * (112) -if score > bestScore then - bestScore = score - decision = 10 -end if -score = 9920025 + h(0) * (107) + h(1) * (128) + h(2) * (-22) + h(3) * (122) + h(4) * (105) + h(5) * (128) + h(6) * (116) + h(7) * (113) + h(8) * (113) + h(9) * (109) + h(10) * (99) + h(11) * (103) + h(12) * (97) + h(13) * (152) + h(14) * (108) + h(15) * (119) -if score > bestScore then - bestScore = score - decision = 11 -end if -score = 9813639 + h(0) * (116) + h(1) * (134) + h(2) * (8) + h(3) * (128) + h(4) * (110) + h(5) * (125) + h(6) * (89) + h(7) * (117) + h(8) * (109) + h(9) * (106) + h(10) * (100) + h(11) * (83) + h(12) * (115) + h(13) * (114) + h(14) * (114) + h(15) * (111) -if score > bestScore then - bestScore = score - decision = 12 -end if -score = 11515426 + h(0) * (125) + h(1) * (129) + h(2) * (-7) + h(3) * (136) + h(4) * (117) + h(5) * (137) + h(6) * (124) + h(7) * (118) + h(8) * (119) + h(9) * (119) + h(10) * (108) + h(11) * (83) + h(12) * (129) + h(13) * (149) + h(14) * (122) + h(15) * (130) -if score > bestScore then - bestScore = score - decision = 13 -end if -score = 9815518 + h(0) * (120) + h(1) * (121) + h(2) * (-14) + h(3) * (127) + h(4) * (100) + h(5) * (106) + h(6) * (113) + h(7) * (117) + h(8) * (110) + h(9) * (112) + h(10) * (106) + h(11) * (76) + h(12) * (119) + h(13) * (129) + h(14) * (112) + h(15) * (106) -if score > bestScore then - bestScore = score - decision = 14 -end if -score = 11022095 + h(0) * (119) + h(1) * (118) + h(2) * (-26) + h(3) * (137) + h(4) * (124) + h(5) * (135) + h(6) * (105) + h(7) * (124) + h(8) * (123) + h(9) * (129) + h(10) * (114) + h(11) * (132) + h(12) * (121) + h(13) * (145) + h(14) * (113) + h(15) * (121) -if score > bestScore then - bestScore = score - decision = 15 -end if -score = 10193590 + h(0) * (122) + h(1) * (97) + h(2) * (-11) + h(3) * (122) + h(4) * (110) + h(5) * (109) + h(6) * (97) + h(7) * (118) + h(8) * (112) + h(9) * (106) + h(10) * (111) + h(11) * (73) + h(12) * (133) + h(13) * (130) + h(14) * (112) + h(15) * (123) -if score > bestScore then - bestScore = score - decision = 16 -end if -score = 10897366 + h(0) * (133) + h(1) * (140) + h(2) * (12) + h(3) * (135) + h(4) * (112) + h(5) * (131) + h(6) * (116) + h(7) * (125) + h(8) * (125) + h(9) * (129) + h(10) * (114) + h(11) * (121) + h(12) * (106) + h(13) * (153) + h(14) * (120) + h(15) * (119) -if score > bestScore then - bestScore = score - decision = 17 -end if -end if -if decision = 18 then - combatDecision = 8 -else -combatDecision = decision -end if -objectiveBuild = 0 -if decision >= 9 then - combatDecision = decision - 9 - objectiveBuild = 1 -end if - -routeFallback = 0 -if combatDecision = 2 or (combatDecision = 8 and bestId = 0) then - if (objectiveId = 0 or objectiveDistance > 300) and (heroId = 0 or heroDistance > 700) then - routeFallback = 1 - end if -end if -routeDx = selfX - routeOpeningX -routeDy = selfY - routeOpeningY -if battleTick < 3500 and routeFallback and routeGroupCount >= 3 then - if routeDx * routeDx + routeDy * routeDy <= 16 then - groupDeparture = 1 - end if -end if - -if combatDecision = 8 then - if objectiveId <> 0 and objectiveDistance <= 300 then - attackTarget(objectiveId) - else - if heroId <> 0 and heroDistance <= 700 then - attackTarget(heroId) - else - if bestId <> 0 then - attackTarget(bestId) - else - if selfTeam = 0 then - if battleTick < 3500 and groupDeparture = 0 then - walkTo(71, 12) - else - if battleTick < 4500 then - walkTo(9, 33) - else - walkTo(11, 106) - end if - end if - else - if battleTick < 3500 and groupDeparture = 0 then - walkTo(45, 104) - else - if battleTick < 4500 then - walkTo(107, 83) - else - walkTo(105, 10) - end if - end if - end if - end if - end if - end if -else - if bestId <> 0 then - attackTarget(bestId) - else - walkTo(64, 64) - end if -end if -if selfClass = 7 and heroId <> 0 and ringHeroTarget = selfId then - if heroDistance > 25 and heroDistance <= 49 then - castPoint(3, (selfX + ringHeroX) \ 2, (selfY + ringHeroY) \ 2) - end if -end if -hasHeal = 0 -elixirCount = 0 -hasMana = 0 -hasPoison = 0 -hasGear = 0 -hasDagger = 0 -hasSword = 0 -hasArmor = 0 -hasAxe = 0 -hasBook = 0 -emptySlot = 0 -slot = 0 -while slot < 6 - id = itemId(slot) - if id = 0 then - emptySlot = 1 - end if - if id = 1 or id = 2 then - hasHeal = 1 - if selfHp * 5 < selfMaxHp * 3 and itemCooldown(slot) = 0 then - useItem(slot) - end if - end if - if id = 2 then - elixirCount = elixirCount + itemCount(slot) - end if - if id = 3 or id = 22 then - hasMana = 1 - if objectiveBuild = 0 and selfMana * 5 < selfMaxMana * 2 and itemCooldown(slot) = 0 then - useItem(slot) - end if - end if - if id = 4 then - hasPoison = 1 - if objectiveBuild = 0 and bestId <> 0 then - useItem(slot) - end if - end if - if id >= 5 and id <= 20 then - hasGear = 1 - end if - if id = 11 then - hasDagger = 1 - end if - if id = 13 then - hasSword = 1 - end if - if id = 16 then - hasArmor = 1 - end if - if id = 18 then - hasAxe = 1 - end if - if id = 20 then - hasBook = 1 - end if - slot = slot + 1 -wend -if canShop() then -' Stock immediate healing after the opening build. Damage interrupts -' ordinary potion recovery, and a full six-slot pack cannot buy a cure. -if selfClass = 2 and selfLevel >= 4 and elixirCount < 3 then - if selfGold >= 75 and (elixirCount > 0 or emptySlot <> 0) then - buyItem(2) - hasHeal = 1 - end if -end if -if selfHp * 2 < selfMaxHp and hasHeal = 0 then - if selfGold >= 50 then - buyItem(2) - end if - if selfGold >= 30 then - buyItem(1) - end if -end if -if objectiveBuild = 0 then -if selfMaxMana > 0 then - if selfMana * 2 < selfMaxMana and hasMana = 0 then - if selfGold >= 45 then - buyItem(22) - end if - end if -end if -if bestId <> 0 and hasPoison = 0 then - if selfGold >= 40 then - buyItem(4) - end if -end if -if emptySlot <> 0 then - melee = 0 - ranged = 0 - magic = 0 - if selfClass = 0 or selfClass = 4 or selfClass = 5 or selfClass = 9 then - melee = 1 - end if - if selfClass = 1 or selfClass = 6 then - ranged = 1 - end if - if selfClass = 2 or selfClass = 3 or selfClass = 7 or selfClass = 8 then - magic = 1 - end if - if melee = 1 then - if hasGear = 0 and selfGold >= 70 then - buyItem(7) - end if - if selfGold >= 80 then - buyItem(5) - end if - if selfGold >= 110 then - buyItem(11) - end if - if selfGold >= 150 then - buyItem(13) - end if - if selfGold >= 180 then - buyItem(18) - end if - end if - if ranged = 1 then - if hasGear = 0 and selfGold >= 100 then - buyItem(8) - end if - if selfGold >= 150 then - buyItem(14) - end if - if selfGold >= 180 then - buyItem(19) - end if - end if - if magic = 1 then - if hasGear = 0 and selfGold >= 140 then - buyItem(12) - end if - if selfGold >= 120 then - buyItem(10) - end if - if selfGold >= 170 then - buyItem(17) - end if - if selfGold >= 190 then - buyItem(20) - end if - end if - if selfGold >= 90 then - buyItem(6) - end if - if selfGold >= 120 then - buyItem(9) - end if -end if -else - if emptySlot <> 0 then - if hasDagger = 0 and selfGold >= 110 then - buyItem(11) - end if - if hasSword = 0 and selfGold >= 150 then - buyItem(13) - end if - if hasArmor = 0 and selfGold >= 160 then - buyItem(16) - end if - if hasAxe = 0 and selfGold >= 180 then - buyItem(18) - end if - if hasBook = 0 and selfGold >= 190 then - buyItem(20) - end if - end if -end if -end if -if combatDecision = 1 then - if selfTeam = 0 then - walkTo(mapWidth - 10, 10) - else - walkTo(10, mapHeight - 10) - end if -end if -if combatDecision = 2 then - if objectiveId <> 0 and objectiveDistance <= 300 then - attackTarget(objectiveId) - else - if heroId <> 0 and heroDistance <= 700 then - attackTarget(heroId) - else - if selfTeam = 0 then - if battleTick < 3500 and groupDeparture = 0 then - walkTo(71, 12) - else - if battleTick < 4500 then - walkTo(9, 33) - else - walkTo(11, 106) - end if - end if - else - if battleTick < 3500 and groupDeparture = 0 then - walkTo(45, 104) - else - if battleTick < 4500 then - walkTo(107, 83) - else - walkTo(105, 10) - end if - end if - end if - end if - end if -end if -if combatDecision >= 3 and combatDecision <= 6 then - if bestId <> 0 then - castTarget(combatDecision - 3, bestId) - else - castTarget(combatDecision - 3, selfId) - end if -end if -if combatDecision = 7 then - walkTo(allyX, allyY) -end if - -if decision = 18 and (bestId = 0 or bestDistance > 64) then - specialistX = mapWidth - 1 - gateHomeX - specialistY = gateHomeY - specialistDx = selfX - specialistX - specialistDy = selfY - specialistY - if specialistDx * specialistDx + specialistDy * specialistDy <= 64 then - perimeterStage = 1 - end if - if perimeterStage <> 0 then - specialistY = mapHeight - 1 - gateHomeY - end if - walkTo(specialistX, specialistY) -end if - -if (combatDecision = 2 or combatDecision = 8) and objectiveKind = 4 and objectiveDistance <= 300 and siegeThreatId <> 0 then - attackTarget(siegeThreatId) -end if - -if reserveHome <> 0 then - reserveGuards = 0 - reserveGuardCritical = 0 - reserveNearest = 1 - reserveDx = selfX - reserveX - reserveDy = selfY - reserveY - reserveDistance = reserveDx * reserveDx + reserveDy * reserveDy - reserveTarget = 0 - reserveTargetDistance = 401 - reserveDirect = 0 - reserveDirectDistance = 401 - reserveHero = 0 - reserveHeroDistance = 401 - reserveFinish = 0 - reserveIndex = 0 - while reserveIndex < objectCount() - reserveDx = objectX(reserveIndex) - reserveX - reserveDy = objectY(reserveIndex) - reserveY - reserveObjectHome = reserveDx * reserveDx + reserveDy * reserveDy - if objectTeam(reserveIndex) = selfTeam then - if objectKind(reserveIndex) = 4 and objectHp(reserveIndex) > 0 and reserveObjectHome <= 100 then - reserveGuards = reserveGuards + 1 - if objectHp(reserveIndex) <= 780 then - reserveGuardCritical = 1 - end if - end if - if objectKind(reserveIndex) = 2 and objectAlive(reserveIndex) and reserveObjectHome < reserveDistance then - reserveNearest = 0 - end if - else - if objectAlive(reserveIndex) then - if (objectKind(reserveIndex) = 2 or objectKind(reserveIndex) = 3) and reserveObjectHome < reserveDirectDistance then - if objectTarget(reserveIndex) = reserveHomeId then - reserveDirect = objectId(reserveIndex) - reserveDirectDistance = reserveObjectHome - end if - end if - if objectKind(reserveIndex) = 2 and reserveObjectHome < reserveHeroDistance then - reserveHero = objectId(reserveIndex) - reserveHeroDistance = reserveObjectHome - end if - if (objectKind(reserveIndex) = 2 or objectKind(reserveIndex) = 3) and reserveObjectHome < reserveTargetDistance then - reserveTargetDistance = reserveObjectHome - reserveTarget = objectId(reserveIndex) - end if - if objectKind(reserveIndex) = 1 and objectHp(reserveIndex) <= selfAttackDamage then - reserveDx = objectX(reserveIndex) - selfX - reserveDy = objectY(reserveIndex) - selfY - reserveRange = selfAttackRange \ 1000 - if (reserveDx * reserveDx + reserveDy * reserveDy) * 3600 <= reserveRange * reserveRange then - reserveFinish = 1 - end if - end if - end if - end if - reserveIndex = reserveIndex + 1 - wend - if reserveHero <> 0 then - reserveTarget = reserveHero - end if - if reserveDirect <> 0 then - reserveTarget = reserveDirect - end if - if (reserveGuards <= 1 or (reserveGuardCritical <> 0 and reserveDistance <= 900)) and reserveNearest <> 0 and reserveFinish = 0 then - if reserveDistance <= 400 and reserveTarget <> 0 then - attackTarget(reserveTarget) - else - walkTo(reserveX, reserveY) - end if - end if -end if - -if recoveryInitialized <> 0 and selfAttacksLanded > recoveryLastHit then - walkTo(selfX, selfY) -end if -recoveryLastHit = selfAttacksLanded -recoveryInitialized = 1 diff --git a/experiments/neural-policy/results.md b/experiments/neural-policy/results.md index 5a59dc42..edd13ef0 100644 --- a/experiments/neural-policy/results.md +++ b/experiments/neural-policy/results.md @@ -12,7 +12,7 @@ No actions or state hashes are normalized, and neither recording is modified. ## Policy and environment -- [Original BASIC](original.bas): 1,147 lines, 46,457 bytes, unchanged. +- Original BASIC in local, ignored `tmp/neural-parity/original.bas`: 1,147 lines, 46,457 bytes, unchanged. - [Converted BASIC](../../examples/gods_of_the_arena/players/neural.bas): 828 lines, 24,944 bytes. - Source: [co-gas at 131b8fde](https://github.com/Metta-AI/co-gas/blob/131b8fde058561cf369449ee2854579f02c64676/players/users/relh/co-gas/polyworld-basic/gods_of_the_arena_neural_v63_arcanist_elixir_stock.bas). - Original source SHA-256: `d5514435f27c400a53bf026b338e7786114f5a4948e9a99d5dd1c3dea9fd1f3a`. @@ -81,17 +81,21 @@ Checks passed: ## Reproduce -From the repository root, after installing the dependencies in `nimby.lock`: +From the repository root, after installing the dependencies in `nimby.lock`. +The original policy is a temporary local artifact and is not included in the +repository. Conversion, baseline replay generation, and the benchmark require +`tmp/neural-parity/original.bas`; restore it from the source link above if the +temporary directory has been cleared. ```sh mkdir -p tmp/neural-parity python3 experiments/neural-policy/convert.py \ - experiments/neural-policy/original.bas \ + tmp/neural-parity/original.bas \ examples/gods_of_the_arena/players/neural.bas nim c -d:headless -o:tmp/neural-parity/gota-native \ examples/gods_of_the_arena/gota.nim tmp/neural-parity/gota-native \ - --bot experiments/neural-policy/original.bas:5 \ + --bot tmp/neural-parity/original.bas:5 \ --bot examples/gods_of_the_arena/players/base.bas:5 \ --seed 20260924 --ticks 28800 \ --record tmp/neural-parity/original.replay @@ -107,7 +111,7 @@ nim r experiments/neural-policy/bench_neural.nim The commands above compare both policies on the new runtime. To rerun the historical baseline, use the preserved `gota-original` binary -with `--bot experiments/neural-policy/original.bas:5`, the same opponent, +with `--bot tmp/neural-parity/original.bas:5`, the same opponent, seed, and tick limit. To rebuild that binary, use the baseline commits above without the Bassy override. Names in metadata follow the supplied BAS filename. From c1b2aaa6a930c8977dfbe7a45db3f33dc2e98b27 Mon Sep 17 00:00:00 2001 From: treeform Date: Thu, 24 Sep 2026 11:14:58 -0700 Subject: [PATCH 4/7] Keep neural policy fixtures in tmp --- docs/neural-arrays.md | 5 +- examples/gods_of_the_arena/players/neural.bas | 828 ------------------ experiments/neural-policy/bench_neural.nim | 56 -- experiments/neural-policy/compare.nim | 29 - experiments/neural-policy/convert.py | 109 --- experiments/neural-policy/results.md | 126 --- tests/test_arrays.nim | 6 +- 7 files changed, 5 insertions(+), 1154 deletions(-) delete mode 100644 examples/gods_of_the_arena/players/neural.bas delete mode 100644 experiments/neural-policy/bench_neural.nim delete mode 100644 experiments/neural-policy/compare.nim delete mode 100644 experiments/neural-policy/convert.py delete mode 100644 experiments/neural-policy/results.md diff --git a/docs/neural-arrays.md b/docs/neural-arrays.md index 62b9861e..ac909c3e 100644 --- a/docs/neural-arrays.md +++ b/docs/neural-arrays.md @@ -87,7 +87,7 @@ Source bytes and compiled instruction counts retain their separate limits. GotA's existing limits remain 64 KiB of source, 32 arrays, 4,096 total array elements, 2 MiB of logical runtime memory, 20,000 instructions and 50,000 work -units per decision. This policy fits without raising any of them. +units per decision. ## Implementation and dependency @@ -107,5 +107,4 @@ nim check tests/tests.nim nim r tests/tests.nim ``` -See the [converted policy](../examples/gods_of_the_arena/players/neural.bas) and -[recorded comparison](../experiments/neural-policy/results.md). +The examples and test fixtures use hand-written synthetic values. diff --git a/examples/gods_of_the_arena/players/neural.bas b/examples/gods_of_the_arena/players/neural.bas deleted file mode 100644 index 6ad70d92..00000000 --- a/examples/gods_of_the_arena/players/neural.bas +++ /dev/null @@ -1,828 +0,0 @@ -' Richard's v63 policy using native, metered array arithmetic. -' Generated by convert.py. Game observations and commands are unchanged. -' DATA initializes once. DIM retains its inclusive BASIC upper bound. -' Matrices are row-major: weights(outputIndex * inputCount + inputIndex). - -DATA draftWeights AS fixed32 = _ - -0.252276, 0.208388, 0.165655, -0.077038, -0.307499, 0.393190, _ - -0.041722, -0.247450, -0.267264, 0.060881, 0.000000, -0.056464, _ - -0.558547, 0.238349, 0.148874, -0.351042, 0.022783, 0.133934, _ - -0.148994, -0.127461, 0.000000, -0.404200, 0.140616, -0.413587, _ - -0.093651, -0.294424, -0.233337, -0.138760, 0.163981, -0.185696, _ - -0.139714, -0.291812, 0.252661, 0.220766, -0.066995, 0.046584, _ - -0.074833, 0.282953, -0.316809, 0.260309, 0.498069, 0.607530, _ - -0.054461, 0.000000, 0.185443, 0.307806, 0.073798, 0.095548, _ - -0.624112, -0.128341, 0.116202, -0.113738, 0.022566, 0.000000, _ - 0.102318, -0.133625, 0.221801, -0.157735, 1.161687, 0.088798, _ - -0.393505, -0.273026, -0.028371, 0.127856, 0.160424, -0.457565, _ - 0.306957, -0.351070, 0.501679, 0.772043, -0.220549, -0.161796, _ - 0.448017, -0.150996, 0.136125, 0.178068, 0.000000, 0.247534, _ - -0.188988, -0.238955, 0.883443, 0.034082, 0.088084, 0.000000, _ - 0.010408, 0.586454, 0.000000, 0.410493, -0.005734, -0.226131, _ - -0.355937, 0.434671, -0.229144, 0.457953, 0.227879, 0.216607, _ - 0.191406, -0.378401, -0.681596, -0.102972, 0.400384, -0.198847, _ - -0.095249, -0.499520, 0.154836, 0.124309, -0.098351, 0.079512, _ - 0.896409, 0.000000, -0.105452, -0.086497, -0.053812, 0.204039, _ - -0.676251, 0.027087, -0.196028, 0.195917, -0.317785, 0.000000, _ - -0.397904, 0.182371, 0.046089, 0.192509, 0.418443, -0.254368, _ - 0.037337, -0.026151, -0.615030, 0.124070, -0.215150, -0.227015, _ - -0.279421, -0.805966, -0.051813, 0.012569, -0.390292, -0.465863, _ - 0.349372, 0.302593, -0.114656, -0.015071, 0.000000, 0.281769, _ - -0.040930, 0.011989, -0.098396, -0.247384, -0.013763, -0.706084, _ - 0.050385, -0.037334, 0.000000, 0.244777, -0.186678, -0.303979, _ - 0.209267, 0.433826, 0.085857, -0.061916, -0.232312, -0.183645, _ - -0.270940, -0.249030, -0.064811, -0.242457, -0.040038, -0.037761, _ - -0.901328, 0.027995, -0.392084, -0.497526, 0.312257, 0.104801, _ - -0.183950, 0.000000, 0.045312, 0.127481, 0.460752, -0.051012, _ - 0.381939, 0.438713, -0.283774, -0.098228, 0.006305, 0.000000, _ - -0.247731, -0.332176, 0.198120, -0.219440, -0.014794, -0.031255, _ - 0.727924, 0.905780, 0.451952, 0.593039, 0.580693, 0.784972, _ - 0.714471, 0.539523, 0.567051, 0.609534, 0.213345, -0.291630, _ - 0.069507, 0.000000, 0.103936, 0.023463, 0.000000, 0.043553, _ - 0.061384, 0.061384, -0.226798, 0.100322, 0.193012, 0.121433, _ - 0.029843, -0.093964, 0.000000, 0.131395, 0.086036, 0.043553, _ - 0.056989, 0.076966, 0.168151, -0.445483, -0.279111, 0.122942, _ - -0.494774, -0.299634, -0.131236, 0.092192, 0.164071, -0.176015, _ - 0.002602, 0.310725, -0.538125, 0.274210, 0.488313, -0.038538, _ - 0.069213, 0.000000, 0.434066, 0.183095, -0.078561, 0.226950, _ - 0.909428, -0.304960, 0.098653, 0.430365, 0.154216, 0.000000, _ - -0.505944, 0.085113, 0.237112, 0.203052, -0.294455, 0.111733, _ - 0.787697, 0.595129, 0.080587, -0.130948, 0.222401, 0.450339, _ - 0.041381, -0.138733, -0.194858, 0.316302, -0.090298, -1.080760, _ - -0.178166, -0.011497, -0.011433, -0.360402, 0.000000, 0.268030, _ - 0.530694, -0.386654, -0.656018, 0.527012, 0.138960, 0.183827, _ - -0.169586, -0.048555, 0.000000, -0.087915, -0.268616, 0.006353, _ - -0.106492, -0.105644, -0.198469, -0.350568, -0.342004, -0.555305, _ - -0.039455, -0.408405, -0.314541, -0.498641, 0.038310, -0.341580, _ - -0.161989, -0.076870, -0.158366, 0.325749, -0.290464, -0.066335, _ - -0.236848, 0.000000, -0.430459, -0.051418, -0.138183, -0.071204, _ - 0.001728, -0.269085, -0.168722, -0.023902, 0.052747, 0.000000, _ - -0.193678, -0.219699 - -DATA draftBias AS fixed32 = _ - -0.252276, 0.220766, 0.306957, -0.102972, -0.279421, -0.242457, _ - 0.714471, 0.092192, 0.041381, -0.498641 - -DATA encoder AS int32 = _ - 263, 222, -62, -8, 156, 176, _ - -660, 470, 178, 648, 309, 38, _ - 78, -150, 200, 5, -491, 183, _ - -26, 311, 646, 80, 544, 39, _ - 255, 126, -23, 117, -25, 585, _ - -305, -320, 118, 235, 32, 21, _ - -234, 656, 93, 602, 414, 439, _ - 243, 243, -340, 187, -78, 72, _ - -522, 315, -346, 10, -181, 180, _ - 64, 725, -25, 71, 104, -523, _ - -232, -80, -27, -30, -313, -350, _ - -320, -11, 493, -48, -128, 182, _ - -73, -520, -23, 190, 158, -294, _ - 61, -155, -122, 280, -28, -22, _ - 396, 438, 38, 385, 238, -15, _ - -727, 119, 363, -197, -128, -135, _ - -497, 121, -12, -15, 431, -81, _ - 108, -107, 178, -317, -339, 286, _ - 230, -103, -293, -141, 714, 388, _ - -197, -213, 109, 384, 214, 562, _ - 195, 323, 25, 668, -158, 360, _ - 159, 398, 339, -52, -357, -172, _ - 110, -22, 646, 125, -453, -365, _ - 177, 230, -367, 408, 465, 703, _ - 61, 136, 543, 41, -54, 54, _ - 122, -158, -130, 204, -64, -508, _ - 639, 551, 299, 239, -38, -406, _ - -41, -158, -309, 353, 131, -140, _ - -48, 244, 161, 174, -9, 91, _ - 715, -90, 489, 349, 361, 116, _ - -315, -91, -101, 238, -164, 238, _ - -133, 125, 458, 371, 3, 30, _ - 203, -106, 482, -345, 302, 932, _ - 147, 505, 101, 49, 180, -178, _ - -311, 502, -51, 361, 179, 638, _ - -61, 240, 314, 248, -8, 483, _ - 730, 314, 155, 291, -257, 241, _ - 543, 9, -66, 411, 470, 170, _ - 61, 50, -20, -192, -211, 60, _ - 68, -32, -109, -126, -404, 49, _ - 543, -90, 554, 445, 341, -324, _ - -471, -27, 378, 114, 861, 381, _ - 130, 413, -252, 152, 159, 421, _ - -140, -151, 669, -70, 515, 338, _ - 102, 453, -71, 383, 255, -158, _ - 195, 441, 133, 189, 264, 82, _ - -702, 275, 91, -190, 47, -505, _ - -288, -299, -56, 716, -16, 78, _ - 18, 95, 6, 302, -370, 419, _ - 284, 2, -157, 129, 332, 261, _ - -187, 342, -126, -271, -109, -323, _ - 149, 43, -690, 236, -95, 383, _ - 276, 62, 485, 215, -249, -34, _ - 413, 130, 137, 562, 61, 214, _ - 123, -49, 121, 69, 190, 856, _ - 44, -31, -132, -120, 458, 407, _ - -483, 6, 396, -271, 452, 40, _ - -47, -98, -226, -24, 342, 192, _ - 508, -284, 432, 481, 400, -699, _ - 108, 103, -31, -289, 548, 208, _ - 338, 67, 157, 80, -99, -136, _ - 179, -206, 274, -139, 86, 412, _ - 33, 278, 594, 651, 160, -271, _ - 341, 228, -548, -380, 170, 260, _ - 78, 188, 579, -65, 58, -197, _ - 173, 208, -353, 354, -48, -264, _ - 293, 413, -53, -198 - -DATA encoderBias AS int32 = _ - 18208, 18392, 3752, 19627, 18521, 18435, _ - 23325, 19286, 18159, 19073, 18190, 19629, _ - 19929, 21374, 18212, 18656 - -DATA decoder AS int32 = _ - -128, -127, -9, -160, -125, -151, _ - -198, -129, -126, -140, -111, -199, _ - -146, -192, -125, -129, 113, 102, _ - 10, 106, 96, 110, 83, 86, _ - 101, 96, 95, 62, 124, 115, _ - 101, 103, 114, 122, 25, 121, _ - 105, 113, 104, 113, 112, 113, _ - 102, 86, 104, 132, 98, 113, _ - 124, 135, -1, 134, 118, 129, _ - 105, 120, 110, 115, 108, 65, _ - 116, 119, 124, 124, 111, 115, _ - 18, 135, 112, 136, 94, 112, _ - 107, 125, 98, 77, 105, 139, _ - 114, 111, 118, 123, -6, 122, _ - 94, 112, 107, 125, 116, 113, _ - 102, 68, 107, 137, 113, 113, _ - 102, 104, 17, 120, 107, 107, _ - 99, 105, 107, 108, 94, 94, _ - 107, 134, 96, 112, 127, 114, _ - 43, 132, 111, 125, 106, 120, _ - 132, 123, 118, 89, 113, 133, _ - 117, 131, 112, 119, 1, 125, _ - 110, 130, 105, 116, 115, 109, _ - 102, 95, 107, 142, 105, 115, _ - -137, -135, -16, -166, -133, -152, _ - -209, -142, -122, -142, -121, -185, _ - -152, -198, -139, -141, 115, 113, _ - 36, 104, 98, 120, 76, 94, _ - 107, 122, 97, 66, 133, 122, _ - 107, 112, 107, 128, -22, 122, _ - 105, 128, 116, 113, 113, 109, _ - 99, 103, 97, 152, 108, 119, _ - 116, 134, 8, 128, 110, 125, _ - 89, 117, 109, 106, 100, 83, _ - 115, 114, 114, 111, 125, 129, _ - -7, 136, 117, 137, 124, 118, _ - 119, 119, 108, 83, 129, 149, _ - 122, 130, 120, 121, -14, 127, _ - 100, 106, 113, 117, 110, 112, _ - 106, 76, 119, 129, 112, 106, _ - 119, 118, -26, 137, 124, 135, _ - 105, 124, 123, 129, 114, 132, _ - 121, 145, 113, 121, 122, 97, _ - -11, 122, 110, 109, 97, 118, _ - 112, 106, 111, 73, 133, 130, _ - 112, 123, 133, 140, 12, 135, _ - 112, 131, 116, 125, 125, 129, _ - 114, 121, 106, 153, 120, 119 - -DATA decoderBias AS int32 = _ - 387878561, 8621915, 9365853, 10032520, 10514535, 10182099, _ - 9061429, 10778495, 9785349, 387767339, 9125238, 9920025, _ - 9813639, 11515426, 9815518, 11022095, 10193590, 10897366 - -dim draftScores(9) -dim logits(17) - -' Gods of the Arena 2026.9.21.3 compatibility layer. -' Draft a balanced roster, spend ability points, buy back, and keep the -' previously trained neural clock relative to the start of combat. -dim draftF(31) -sub chooseHero() - if draftTurnId <> selfId then - exit sub - end if - for draftClass = 0 to 9 - draftF(draftClass) = heroAvailable(draftClass) - draftF(10 + draftClass) = 0 - draftF(20 + draftClass) = 0 - next draftClass - draftAllyCount = 0 - draftEnemyCount = 0 - for draftPlayer = 0 to draftPlayerCount() - 1 - draftPicked = draftedClass(draftPlayerId(draftPlayer)) - if draftPicked >= 0 then - if draftPlayerTeam(draftPlayer) = selfTeam then - draftF(10 + draftPicked) = 1 - draftAllyCount = draftAllyCount + 1 - else - draftF(20 + draftPicked) = 1 - draftEnemyCount = draftEnemyCount + 1 - end if - end if - next draftPlayer - draftF(30) = draftAllyCount / 5 - draftF(31) = draftEnemyCount / 5 - linear(draftF, draftWeights, draftBias, draftScores, 32, 10) - draftBestClass = argmaxMasked(draftScores, draftF, 10) - if draftBestClass >= 0 then - draftHero(draftBestClass) - end if -end sub - -if drafting then - chooseHero() - end -end if - -if battleStarted = 0 then - battleStartTick = worldTick - battleStarted = 1 -end if -battleTick = worldTick - battleStartTick - -if selfHp <= 0 then - buybackCost = buybackPrice() - if buybackCost > 0 and selfGold >= buybackCost then - buyback() - end if - end -end if - -' Spend every currently legal point. The trained actor favors W, then E/Q; -' take the ultimate whenever its level gate opens. -for upgrade = 1 to 4 - if canLevelAbility(3) then - levelAbility(3) - elseif canLevelAbility(1) then - levelAbility(1) - elseif canLevelAbility(2) then - levelAbility(2) - elseif canLevelAbility(0) then - levelAbility(0) - end if -next upgrade - -dim f(27) -bestId = 0 -bestDistance = 2147483647 -objectiveId = 0 -objectiveKind = 0 -siegeThreatId = 0 -siegeThreatHp = 2147483647 -objectiveDistance = 2147483647 -heroId = 0 -heroDistance = 2147483647 -enemyX = selfX -enemyY = selfY -enemyHp = 0 -enemyKind = 0 -enemyAttackTarget = 0 -routeOpeningX = 71 -routeOpeningY = 12 -if selfTeam = 1 then - routeOpeningX = 45 - routeOpeningY = 104 -end if -routeGroupCount = 0 -if selfTeam = 0 then - gateHomeX = 105 - gateHomeY = 10 -else - gateHomeX = 10 - gateHomeY = 105 -end if -gateThreatDistance = 2147483647 -gateThreatSeen = 0 -gateThreatX = gateHomeX -reserveHome = 0 -allyCount = 0 -allyX = 0 -allyY = 0 -maxAllyDistance = 0 -index = 0 -while index < objectCount() - if objectAlive(index) then - if objectTeam(index) = selfTeam and objectKind(index) = 1 then - reserveHome = 1 - reserveHomeId = objectId(index) - reserveX = objectX(index) - reserveY = objectY(index) - end if - if objectTeam(index) = selfTeam and objectKind(index) = 2 then - allyCount = allyCount + 1 - routeDx = objectX(index) - routeOpeningX - routeDy = objectY(index) - routeOpeningY - if routeDx * routeDx + routeDy * routeDy <= 16 then - routeGroupCount = routeGroupCount + 1 - end if - allyX = allyX + objectX(index) - allyY = allyY + objectY(index) - dx = objectX(index) - selfX - dy = objectY(index) - selfY - distance = dx * dx + dy * dy - if distance > maxAllyDistance then - maxAllyDistance = distance - end if - end if - if objectTeam(index) <> selfTeam then - if battleTick < 1000 and (objectKind(index) = 2 or objectKind(index) = 3) then - gateDx = objectX(index) - gateHomeX - gateDy = objectY(index) - gateHomeY - gateDistance = gateDx * gateDx + gateDy * gateDy - if gateDistance < gateThreatDistance then - gateThreatDistance = gateDistance - gateThreatSeen = 1 - gateThreatX = objectX(index) - end if - end if - dx = objectX(index) - selfX - dy = objectY(index) - selfY - distance = dx * dx + dy * dy - if distance < bestDistance then - bestDistance = distance - bestId = objectId(index) - enemyX = objectX(index) - enemyY = objectY(index) - enemyHp = objectHp(index) - enemyKind = objectKind(index) - enemyAttackTarget = objectTarget(index) - end if - if objectKind(index) = 2 and objectTarget(index) = selfId then - siegeRange = selfAttackRange \ 1000 - if distance * 3600 <= siegeRange * siegeRange and objectHp(index) < siegeThreatHp then - siegeThreatId = objectId(index) - siegeThreatHp = objectHp(index) - end if - end if - if objectKind(index) = 1 or objectKind(index) = 4 then - if distance < objectiveDistance then - objectiveDistance = distance - objectiveId = objectId(index) - objectiveKind = objectKind(index) - end if - end if - if objectKind(index) = 2 and distance < heroDistance then - heroDistance = distance - heroId = objectId(index) - ringHeroX = objectX(index) - ringHeroY = objectY(index) - ringHeroTarget = objectTarget(index) - end if - end if - end if - index = index + 1 -wend -if allyCount > 0 then - allyX = allyX \ allyCount - allyY = allyY \ allyCount -else - allyX = selfX - allyY = selfY -end if -f(0) = selfHp * 100 \ selfMaxHp -f(1) = 0 -if selfMaxMana > 0 then - f(1) = selfMana * 100 \ selfMaxMana -end if -f(2) = maxAllyDistance \ 100 -f(3) = selfLevel * 5 -f(4) = allyX - selfX -f(5) = allyY - selfY -f(6) = enemyX - selfX -f(7) = enemyY - selfY -f(8) = enemyHp \ 10 -f(9) = enemyKind * 25 -if selfTeam = 1 then - f(4) = 0 - f(4) - f(5) = 0 - f(5) - f(6) = 0 - f(6) - f(7) = 0 - f(7) -end if -f(10) = battleTick \ 288 -f(11) = abilityCharges(0) * 25 -f(12) = abilityCharges(1) * 25 -f(13) = abilityCharges(2) * 25 -f(14) = abilityCharges(3) * 25 -f(15 + selfClass) = 100 -f(25) = battleTick \ 16 -if battleTick < 1000 and gateThreatSeen = 1 then - gateEarlyThreatX = gateThreatX - gateEarlyThreatSeen = 1 -end if -f(26) = 0 -if gateEarlyThreatSeen = 1 then - f(26) = gateEarlyThreatX - gateHomeX - if selfTeam = 1 then - f(26) = 0 - f(26) - end if -end if -index = 0 -while index < 27 - if f(index) > 100 then - f(index) = 100 - end if - if f(index) < -100 then - f(index) = -100 - end if - index = index + 1 -wend -dim h(16) -neuralActionCountdown = neuralActionCountdown - 1 -if neuralActionCountdown <= 0 then - neuralActionCountdown = 4 - linear(f, encoder, encoderBias, h, 25, 16) - relu(h, 16) - linear(h, decoder, decoderBias, logits, 16, 18) - decision = argmax(logits, 18) - ' Preserve the original sentinel edge case for int32 wraparound. - if logits(decision) <= -2147483647 then - decision = 0 - end if -end if -if decision = 18 then - combatDecision = 8 -else -combatDecision = decision -end if -objectiveBuild = 0 -if decision >= 9 then - combatDecision = decision - 9 - objectiveBuild = 1 -end if - -routeFallback = 0 -if combatDecision = 2 or (combatDecision = 8 and bestId = 0) then - if (objectiveId = 0 or objectiveDistance > 300) and (heroId = 0 or heroDistance > 700) then - routeFallback = 1 - end if -end if -routeDx = selfX - routeOpeningX -routeDy = selfY - routeOpeningY -if battleTick < 3500 and routeFallback and routeGroupCount >= 3 then - if routeDx * routeDx + routeDy * routeDy <= 16 then - groupDeparture = 1 - end if -end if - -if combatDecision = 8 then - if objectiveId <> 0 and objectiveDistance <= 300 then - attackTarget(objectiveId) - else - if heroId <> 0 and heroDistance <= 700 then - attackTarget(heroId) - else - if bestId <> 0 then - attackTarget(bestId) - else - if selfTeam = 0 then - if battleTick < 3500 and groupDeparture = 0 then - walkTo(71, 12) - else - if battleTick < 4500 then - walkTo(9, 33) - else - walkTo(11, 106) - end if - end if - else - if battleTick < 3500 and groupDeparture = 0 then - walkTo(45, 104) - else - if battleTick < 4500 then - walkTo(107, 83) - else - walkTo(105, 10) - end if - end if - end if - end if - end if - end if -else - if bestId <> 0 then - attackTarget(bestId) - else - walkTo(64, 64) - end if -end if -if selfClass = 7 and heroId <> 0 and ringHeroTarget = selfId then - if heroDistance > 25 and heroDistance <= 49 then - castPoint(3, (selfX + ringHeroX) \ 2, (selfY + ringHeroY) \ 2) - end if -end if -hasHeal = 0 -elixirCount = 0 -hasMana = 0 -hasPoison = 0 -hasGear = 0 -hasDagger = 0 -hasSword = 0 -hasArmor = 0 -hasAxe = 0 -hasBook = 0 -emptySlot = 0 -slot = 0 -while slot < 6 - id = itemId(slot) - if id = 0 then - emptySlot = 1 - end if - if id = 1 or id = 2 then - hasHeal = 1 - if selfHp * 5 < selfMaxHp * 3 and itemCooldown(slot) = 0 then - useItem(slot) - end if - end if - if id = 2 then - elixirCount = elixirCount + itemCount(slot) - end if - if id = 3 or id = 22 then - hasMana = 1 - if objectiveBuild = 0 and selfMana * 5 < selfMaxMana * 2 and itemCooldown(slot) = 0 then - useItem(slot) - end if - end if - if id = 4 then - hasPoison = 1 - if objectiveBuild = 0 and bestId <> 0 then - useItem(slot) - end if - end if - if id >= 5 and id <= 20 then - hasGear = 1 - end if - if id = 11 then - hasDagger = 1 - end if - if id = 13 then - hasSword = 1 - end if - if id = 16 then - hasArmor = 1 - end if - if id = 18 then - hasAxe = 1 - end if - if id = 20 then - hasBook = 1 - end if - slot = slot + 1 -wend -if canShop() then -' Stock immediate healing after the opening build. Damage interrupts -' ordinary potion recovery, and a full six-slot pack cannot buy a cure. -if selfClass = 2 and selfLevel >= 4 and elixirCount < 3 then - if selfGold >= 75 and (elixirCount > 0 or emptySlot <> 0) then - buyItem(2) - hasHeal = 1 - end if -end if -if selfHp * 2 < selfMaxHp and hasHeal = 0 then - if selfGold >= 50 then - buyItem(2) - end if - if selfGold >= 30 then - buyItem(1) - end if -end if -if objectiveBuild = 0 then -if selfMaxMana > 0 then - if selfMana * 2 < selfMaxMana and hasMana = 0 then - if selfGold >= 45 then - buyItem(22) - end if - end if -end if -if bestId <> 0 and hasPoison = 0 then - if selfGold >= 40 then - buyItem(4) - end if -end if -if emptySlot <> 0 then - melee = 0 - ranged = 0 - magic = 0 - if selfClass = 0 or selfClass = 4 or selfClass = 5 or selfClass = 9 then - melee = 1 - end if - if selfClass = 1 or selfClass = 6 then - ranged = 1 - end if - if selfClass = 2 or selfClass = 3 or selfClass = 7 or selfClass = 8 then - magic = 1 - end if - if melee = 1 then - if hasGear = 0 and selfGold >= 70 then - buyItem(7) - end if - if selfGold >= 80 then - buyItem(5) - end if - if selfGold >= 110 then - buyItem(11) - end if - if selfGold >= 150 then - buyItem(13) - end if - if selfGold >= 180 then - buyItem(18) - end if - end if - if ranged = 1 then - if hasGear = 0 and selfGold >= 100 then - buyItem(8) - end if - if selfGold >= 150 then - buyItem(14) - end if - if selfGold >= 180 then - buyItem(19) - end if - end if - if magic = 1 then - if hasGear = 0 and selfGold >= 140 then - buyItem(12) - end if - if selfGold >= 120 then - buyItem(10) - end if - if selfGold >= 170 then - buyItem(17) - end if - if selfGold >= 190 then - buyItem(20) - end if - end if - if selfGold >= 90 then - buyItem(6) - end if - if selfGold >= 120 then - buyItem(9) - end if -end if -else - if emptySlot <> 0 then - if hasDagger = 0 and selfGold >= 110 then - buyItem(11) - end if - if hasSword = 0 and selfGold >= 150 then - buyItem(13) - end if - if hasArmor = 0 and selfGold >= 160 then - buyItem(16) - end if - if hasAxe = 0 and selfGold >= 180 then - buyItem(18) - end if - if hasBook = 0 and selfGold >= 190 then - buyItem(20) - end if - end if -end if -end if -if combatDecision = 1 then - if selfTeam = 0 then - walkTo(mapWidth - 10, 10) - else - walkTo(10, mapHeight - 10) - end if -end if -if combatDecision = 2 then - if objectiveId <> 0 and objectiveDistance <= 300 then - attackTarget(objectiveId) - else - if heroId <> 0 and heroDistance <= 700 then - attackTarget(heroId) - else - if selfTeam = 0 then - if battleTick < 3500 and groupDeparture = 0 then - walkTo(71, 12) - else - if battleTick < 4500 then - walkTo(9, 33) - else - walkTo(11, 106) - end if - end if - else - if battleTick < 3500 and groupDeparture = 0 then - walkTo(45, 104) - else - if battleTick < 4500 then - walkTo(107, 83) - else - walkTo(105, 10) - end if - end if - end if - end if - end if -end if -if combatDecision >= 3 and combatDecision <= 6 then - if bestId <> 0 then - castTarget(combatDecision - 3, bestId) - else - castTarget(combatDecision - 3, selfId) - end if -end if -if combatDecision = 7 then - walkTo(allyX, allyY) -end if - -if decision = 18 and (bestId = 0 or bestDistance > 64) then - specialistX = mapWidth - 1 - gateHomeX - specialistY = gateHomeY - specialistDx = selfX - specialistX - specialistDy = selfY - specialistY - if specialistDx * specialistDx + specialistDy * specialistDy <= 64 then - perimeterStage = 1 - end if - if perimeterStage <> 0 then - specialistY = mapHeight - 1 - gateHomeY - end if - walkTo(specialistX, specialistY) -end if - -if (combatDecision = 2 or combatDecision = 8) and objectiveKind = 4 and objectiveDistance <= 300 and siegeThreatId <> 0 then - attackTarget(siegeThreatId) -end if - -if reserveHome <> 0 then - reserveGuards = 0 - reserveGuardCritical = 0 - reserveNearest = 1 - reserveDx = selfX - reserveX - reserveDy = selfY - reserveY - reserveDistance = reserveDx * reserveDx + reserveDy * reserveDy - reserveTarget = 0 - reserveTargetDistance = 401 - reserveDirect = 0 - reserveDirectDistance = 401 - reserveHero = 0 - reserveHeroDistance = 401 - reserveFinish = 0 - reserveIndex = 0 - while reserveIndex < objectCount() - reserveDx = objectX(reserveIndex) - reserveX - reserveDy = objectY(reserveIndex) - reserveY - reserveObjectHome = reserveDx * reserveDx + reserveDy * reserveDy - if objectTeam(reserveIndex) = selfTeam then - if objectKind(reserveIndex) = 4 and objectHp(reserveIndex) > 0 and reserveObjectHome <= 100 then - reserveGuards = reserveGuards + 1 - if objectHp(reserveIndex) <= 780 then - reserveGuardCritical = 1 - end if - end if - if objectKind(reserveIndex) = 2 and objectAlive(reserveIndex) and reserveObjectHome < reserveDistance then - reserveNearest = 0 - end if - else - if objectAlive(reserveIndex) then - if (objectKind(reserveIndex) = 2 or objectKind(reserveIndex) = 3) and reserveObjectHome < reserveDirectDistance then - if objectTarget(reserveIndex) = reserveHomeId then - reserveDirect = objectId(reserveIndex) - reserveDirectDistance = reserveObjectHome - end if - end if - if objectKind(reserveIndex) = 2 and reserveObjectHome < reserveHeroDistance then - reserveHero = objectId(reserveIndex) - reserveHeroDistance = reserveObjectHome - end if - if (objectKind(reserveIndex) = 2 or objectKind(reserveIndex) = 3) and reserveObjectHome < reserveTargetDistance then - reserveTargetDistance = reserveObjectHome - reserveTarget = objectId(reserveIndex) - end if - if objectKind(reserveIndex) = 1 and objectHp(reserveIndex) <= selfAttackDamage then - reserveDx = objectX(reserveIndex) - selfX - reserveDy = objectY(reserveIndex) - selfY - reserveRange = selfAttackRange \ 1000 - if (reserveDx * reserveDx + reserveDy * reserveDy) * 3600 <= reserveRange * reserveRange then - reserveFinish = 1 - end if - end if - end if - end if - reserveIndex = reserveIndex + 1 - wend - if reserveHero <> 0 then - reserveTarget = reserveHero - end if - if reserveDirect <> 0 then - reserveTarget = reserveDirect - end if - if (reserveGuards <= 1 or (reserveGuardCritical <> 0 and reserveDistance <= 900)) and reserveNearest <> 0 and reserveFinish = 0 then - if reserveDistance <= 400 and reserveTarget <> 0 then - attackTarget(reserveTarget) - else - walkTo(reserveX, reserveY) - end if - end if -end if - -if recoveryInitialized <> 0 and selfAttacksLanded > recoveryLastHit then - walkTo(selfX, selfY) -end if -recoveryLastHit = selfAttacksLanded -recoveryInitialized = 1 diff --git a/experiments/neural-policy/bench_neural.nim b/experiments/neural-policy/bench_neural.nim deleted file mode 100644 index ebead925..00000000 --- a/experiments/neural-policy/bench_neural.nim +++ /dev/null @@ -1,56 +0,0 @@ -import - std/[os, strutils], - bassy, benchy, - polyworld/arrays - -const Directory = currentSourcePath().parentDir - -let - original = readFile(Directory / "../../tmp/neural-parity/original.bas") - converted = readFile( - Directory / "../../examples/gods_of_the_arena/players/neural.bas" - ) - first = original.find("h(0) = ") - last = original.find("\nend if\nend if\nif decision = 18", first) - scalarSource = "dim f(27)\ndim h(16)\n" & - original[first ..< last + "\nend if".len] - nativeSource = converted[0 ..< converted.find("dim draftScores(9)")] & """ -dim f(27) -dim h(16) -dim logits(17) -linear(f, encoder, encoderBias, h, 25, 16) -relu(h, 16) -linear(h, decoder, decoderBias, logits, 16, 18) -decision = argmax(logits, 18) -if logits(decision) <= -2147483647 then decision = 0 -""" -var schema = initHost() -schema.addArrayFunctions() -var - scalar = initRuntime(compile(scalarSource)) - native = initRuntime(compile(nativeSource, schema), schema) -for trial in 0 ..< 256: - for i in 0 ..< 25: - let value = int32((trial * 97 + i * 37) mod 257 - 128) - scalar.setArray("f", int32(i), value) - native.setArray("f", int32(i), value) - scalar.restart() - native.restart() - discard scalar.run() - discard native.run() - doAssert scalar.getGlobal("decision") == native.getGlobal("decision") - for i in 0 ..< 16: - doAssert scalar.getArray("h", int32(i)) == native.getArray("h", int32(i)) -echo "Combat inference parity: 256 feature vectors" -echo "Scalar instructions/work: ", scalar.instructionsUsed, "/", scalar.workUsed -echo "Native instructions/work: ", native.instructionsUsed, "/", native.workUsed -echo "Scalar/native VM bytes: ", scalar.memoryBytes, "/", native.memoryBytes - -timeIt "1000 interpreted combat inferences", 20: - for i in 0 ..< 1000: - scalar.restart() - discard scalar.run() -timeIt "1000 native combat inferences", 20: - for i in 0 ..< 1000: - native.restart() - discard native.run() diff --git a/experiments/neural-policy/compare.nim b/experiments/neural-policy/compare.nim deleted file mode 100644 index c10220d5..00000000 --- a/experiments/neural-policy/compare.nim +++ /dev/null @@ -1,29 +0,0 @@ -import - std/[os, strutils], - ../../examples/gods_of_the_arena/replays - -let paths = commandLineParams() -doAssert paths.len == 2, "pass the original and converted replay paths" -let - original = loadReplay(paths[0]) - converted = loadReplay(paths[1]) -doAssert original.header == converted.header, "match setup differs" -doAssert original.actions == converted.actions, "action tapes differ" -doAssert original.hashes == converted.hashes, "per-tick state hashes differ" -doAssert original.metrics.tickRate == converted.metrics.tickRate -doAssert original.metrics.interval == converted.metrics.interval -doAssert original.metrics.frames.len == converted.metrics.frames.len -doAssert original.metrics.final.len == converted.metrics.final.len -for i, frame in original.metrics.frames: - doAssert frame.tick == converted.metrics.frames[i].tick - doAssert frame.rows.len == converted.metrics.frames[i].rows.len -var comparable = converted -comparable.config.players = original.config.players -comparable.metrics = original.metrics -doAssert original == comparable, "unexpected non-telemetry difference" -echo "Identical actions: ", original.actions.len -echo "Identical per-tick hashes: ", original.hashes.len -echo "Final state hash: ", original.hashes[^1].toHex(16) -echo "Only player labels and instruction telemetry may differ." -echo "Original final CPU percentages: ", original.metrics.final -echo "Converted final CPU percentages: ", converted.metrics.final diff --git a/experiments/neural-policy/convert.py b/experiments/neural-policy/convert.py deleted file mode 100644 index b5dec31c..00000000 --- a/experiments/neural-policy/convert.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Extract the supplied v63 policy's literals without changing its game code.""" - -import argparse -import hashlib -from pathlib import Path -import re - - -def data(name, kind, rows): - """Format one row-major DATA array using exact source literal strings.""" - values = [value for row in rows for value in row] - lines = [f"DATA {name} AS {kind} = _"] - for offset in range(0, len(values), 6): - line = " " + ", ".join(values[offset:offset + 6]) - if offset + 6 < len(values): - line += ", _" - lines.append(line) - return "\n".join(lines) + "\n" - - -def convert(source): - """Convert only the three affine layers, ReLU, and score selection.""" - draft_start = source.index(" draftBestClass = -1\n") - draft_end = source.index(" if draftBestClass >= 0 then\n") - blocks = re.findall( - r" if draftF\((\d+)\) then\n(.*?)\n end if", - source[draft_start:draft_end], re.S, - ) - assert [int(index) for index, _ in blocks] == list(range(10)) - draft_weights, draft_biases = [], [] - for _, body in blocks: - draft_biases.append(re.search(r"draftScore = (-?[\d.]+)", body)[1]) - terms = re.findall( - r"draftScore = draftScore \+ draftF\((\d+)\) \* (-?[\d.]+)", - body, - ) - indices = [int(index) for index, _ in terms] - assert indices == sorted(set(indices)) and max(indices) < 32 - row = ["0.000000"] * 32 - for index, value in terms: - row[int(index)] = value - draft_weights.append(row) - - encoder_rows = re.findall(r"^h\((\d+)\) = (-?\d+)(.*)$", source, re.M) - decoder_rows = re.findall(r"^score = (-?\d+)(.*)$", source, re.M) - assert [int(index) for index, _, _ in encoder_rows] == list(range(16)) - assert len(decoder_rows) == 18 - encoder_weights, encoder_biases = [], [] - decoder_weights, decoder_biases = [], [] - for _, bias, body in encoder_rows: - terms = re.findall(r"f\((\d+)\) \* \((-?\d+)\)", body) - assert [int(index) for index, _ in terms] == list(range(25)) - encoder_weights.append([value for _, value in terms]) - encoder_biases.append(bias) - for bias, body in decoder_rows: - terms = re.findall(r"h\((\d+)\) \* \((-?\d+)\)", body) - assert [int(index) for index, _ in terms] == list(range(16)) - decoder_weights.append([value for _, value in terms]) - decoder_biases.append(bias) - - draft = """ linear(draftF, draftWeights, draftBias, draftScores, 32, 10) - draftBestClass = argmaxMasked(draftScores, draftF, 10) -""" - source = source[:draft_start] + draft + source[draft_end:] - start = source.index("h(0) = ") - end = source.index("\nend if", source.index(" decision = 17\n", start)) - # Keep the END IF for the four-tick inference countdown. - end = source.index("\nend if", end + 1) - combat = """ linear(f, encoder, encoderBias, h, 25, 16) - relu(h, 16) - linear(h, decoder, decoderBias, logits, 16, 18) - decision = argmax(logits, 18) - ' Preserve the original sentinel edge case for int32 wraparound. - if logits(decision) <= -2147483647 then - decision = 0 - end if""" - source = source[:start] + combat + source[end:] - header = """' Richard's v63 policy using native, metered array arithmetic. -' Generated by convert.py. Game observations and commands are unchanged. -' DATA initializes once. DIM retains its inclusive BASIC upper bound. -' Matrices are row-major: weights(outputIndex * inputCount + inputIndex). - -""" - for name, kind, rows in [ - ("draftWeights", "fixed32", draft_weights), - ("draftBias", "fixed32", [draft_biases]), - ("encoder", "int32", encoder_weights), - ("encoderBias", "int32", [encoder_biases]), - ("decoder", "int32", decoder_weights), - ("decoderBias", "int32", [decoder_biases]), - ]: - header += data(name, kind, rows) + "\n" - header += "dim draftScores(9)\ndim logits(17)\n\n" - return header + source - - -def main(): - """Convert an unchanged input file and print its provenance digest.""" - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("source", type=Path) - parser.add_argument("destination", type=Path) - arguments = parser.parse_args() - source = arguments.source.read_bytes() - arguments.destination.write_text(convert(source.decode("utf-8"))) - print("Source SHA-256:", hashlib.sha256(source).hexdigest()) - - -if __name__ == "__main__": - main() diff --git a/experiments/neural-policy/results.md b/experiments/neural-policy/results.md deleted file mode 100644 index edd13ef0..00000000 --- a/experiments/neural-policy/results.md +++ /dev/null @@ -1,126 +0,0 @@ -# Richard's policy conversion results - -The original and converted policy produced identical gameplay in the recorded -GotA match: all 129,497 actions and all 28,909 per-tick state hashes match. -Both ended with state hash **`00000000491E52B0`**. All 10 scripts remained -active through 288,010 decisions. The match reached its 20-minute battle limit. - -The raw replay files differ because their player labels and instruction-use -telemetry differ. `compare.nim` verifies setup, every action, every tick hash, -and every other field after allowing only those two metadata differences. -No actions or state hashes are normalized, and neither recording is modified. - -## Policy and environment - -- Original BASIC in local, ignored `tmp/neural-parity/original.bas`: 1,147 lines, 46,457 bytes, unchanged. -- [Converted BASIC](../../examples/gods_of_the_arena/players/neural.bas): 828 lines, 24,944 bytes. -- Source: [co-gas at 131b8fde](https://github.com/Metta-AI/co-gas/blob/131b8fde058561cf369449ee2854579f02c64676/players/users/relh/co-gas/polyworld-basic/gods_of_the_arena_neural_v63_arcanist_elixir_stock.bas). -- Original source SHA-256: `d5514435f27c400a53bf026b338e7786114f5a4948e9a99d5dd1c3dea9fd1f3a`. -- Polyworld baseline: `8b31ddd`, gameplay version 63. -- Bassy baseline: `b25e0efef3fec0bd86ed3154659c0762a7158bd3`. -- Fixxy: `05e5446dffb70093056cebb0c57721a60deaf52a`. -- Local validation: macOS, Nim 2.2.6, native release build. -- Match seed: `20260924`. Map seed: `54`, map hash: `0000000099EBA3A6`. -- Seats 1-5: Richard's policy. Seats 6-10: the unchanged GotA base policy. -- Battle: 28,800 ticks. Draft: 109 ticks. Total: 28,909 ticks. - -The original binary was built and the baseline recorded before modifying -Bassy or Polyworld. It remains at `../../tmp/neural-parity/gota-original`. - -## What changed in the policy - -All three affine layers now call `linear`, followed by `relu` and `argmax` as -appropriate. The draft model is 32 to 10 with availability masking. The combat -model is 25 to 16, ReLU, then 16 to 18. All 1,052 matrix and bias entries are -inline DATA, including zero padding in the sparse draft matrix. No binary or -ZIP file is needed. - -The converter extracts the exact literal strings without rounding them in -Python. Draft values use fixed-point arithmetic, and combat values retain -int32 arithmetic. The original feature extraction, action mapping, shopping, -draft eligibility, and four-tick inference cadence are preserved. The combat -selection also preserves the original sentinel behavior at the bottom of the -int32 range. - -The inference itself is now: - -```basic -linear(f, encoder, encoderBias, h, 25, 16) -relu(h, 16) -linear(h, decoder, decoderBias, logits, 16, 18) -decision = argmax(logits, 18) -``` - -## Performance and validation - -`bench_neural.nim` additionally compares all 16 hidden outputs and the selected -action across 256 deterministic combat feature vectors. All match. - -| Isolated combat inference | Interpreted | Native | -| --- | ---: | ---: | -| Mean milliseconds per 1,000 evaluations, 20 samples | 12.295 | 4.180 | -| Charged instructions for the last sample | 3,693 | 1,521 | -| Charged work units for the last sample | 4,421 | 1,598 | - -That local kernel benchmark is about 2.9 times faster. Full-match elapsed times -were 20.03 and 20.61 seconds; those include the whole simulation and do not -demonstrate a whole-game speedup. - -Checks passed: - -- Bassy `nim check tests/tests.nim` and `nim r tests/tests.nim`. -- Polyworld `nim check tests/tests.nim` and `nim r tests/tests.nim`. -- AWM host `nim check`, in addition to the games covered by the main suite. -- Playback of the converted recording consumed all 129,497 actions with no - state-hash mismatches and ended at the same final state hash. -- DATA syntax, integer/fixed coercion, immutability, reset/restart behavior, - context callback binding, array-handle boundaries, and memory limits. -- Scalar/native arithmetic equivalence, int32 wraparound, fixed rounding, - argmax ties/masks, alias and shape rejection, instruction/work exhaustion - before kernel mutation, and stable memory across repeated inference. - -## Reproduce - -From the repository root, after installing the dependencies in `nimby.lock`. -The original policy is a temporary local artifact and is not included in the -repository. Conversion, baseline replay generation, and the benchmark require -`tmp/neural-parity/original.bas`; restore it from the source link above if the -temporary directory has been cleared. - -```sh -mkdir -p tmp/neural-parity -python3 experiments/neural-policy/convert.py \ - tmp/neural-parity/original.bas \ - examples/gods_of_the_arena/players/neural.bas -nim c -d:headless -o:tmp/neural-parity/gota-native \ - examples/gods_of_the_arena/gota.nim -tmp/neural-parity/gota-native \ - --bot tmp/neural-parity/original.bas:5 \ - --bot examples/gods_of_the_arena/players/base.bas:5 \ - --seed 20260924 --ticks 28800 \ - --record tmp/neural-parity/original.replay -tmp/neural-parity/gota-native \ - --bot examples/gods_of_the_arena/players/neural.bas:5 \ - --bot examples/gods_of_the_arena/players/base.bas:5 \ - --seed 20260924 --ticks 28800 \ - --record tmp/neural-parity/converted.replay -nim r experiments/neural-policy/compare.nim \ - tmp/neural-parity/original.replay tmp/neural-parity/converted.replay -nim r experiments/neural-policy/bench_neural.nim -``` - -The commands above compare both policies on the new runtime. To rerun the -historical baseline, use the preserved `gota-original` binary -with `--bot tmp/neural-parity/original.bas:5`, the same opponent, -seed, and tick limit. To rebuild that binary, use the baseline commits above -without the Bassy override. Names in metadata follow the supplied BAS filename. - -## Saved artifacts - -Both replay files remain under the worktree's ignored `tmp/neural-parity/` -directory, along with build, test, comparison, and benchmark logs. - -| Replay | Bytes | SHA-256 of raw file | -| --- | ---: | --- | -| `tmp/neural-parity/original.replay` | 4,438,360 | `d2e07478a5fa8bf510de9c3ad84f74164bec466c8610ce7834905b741ab01e52` | -| `tmp/neural-parity/converted.replay` | 4,438,365 | `f54b3d9c22364727977dc1b7c264c3585f3e48b5a647444d8becdbdb56a21ec1` | diff --git a/tests/test_arrays.nim b/tests/test_arrays.nim index 96335564..10316426 100644 --- a/tests/test_arrays.nim +++ b/tests/test_arrays.nim @@ -25,10 +25,10 @@ for representation in ["int32", "fixed32"]: if representation == "int32": "2147483647, -25000, 13, -2147483648, 7654, -99" else: - "0.220766, -0.498641, 0.306957, 1.125, -0.000031, 0.75" + "0.15625, -0.46875, 0.3125, 1.125, -0.000031, 0.75" biases = - if representation == "int32": "387878561, 10897366" - else: "-0.252276, 0.714471" + if representation == "int32": "400000001, 12000007" + else: "-0.375, 0.625" source = "data weights as " & representation & " = " & weights & "\ndata biases as " & representation & " = " & biases & "\n" & """ dim features(2) From 6539753ad2d53ab4383c7e0e3f6bd1edc7839f00 Mon Sep 17 00:00:00 2001 From: treeform Date: Thu, 24 Sep 2026 11:20:35 -0700 Subject: [PATCH 5/7] Expose array operations as direct functions --- src/polyworld/arrays.nim | 181 +++++++++++++++++++++++---------------- 1 file changed, 106 insertions(+), 75 deletions(-) diff --git a/src/polyworld/arrays.nim b/src/polyworld/arrays.nim index 3ed91c41..269ad55d 100644 --- a/src/polyworld/arrays.nim +++ b/src/polyworld/arrays.nim @@ -2,10 +2,6 @@ import bassy -type - ArrayOperation = enum - Add, Multiply, Copy, Fill, Dot - proc count(value: Value): int = ## Accepts a positive, exact element count without narrowing first. let size = value.asInt @@ -56,80 +52,115 @@ proc relu(runtime: Runtime, arguments: openArray[Value]): Value = values[i] = toValue(0) toValue(0) -proc maximum(masked: bool): ContextHostProc = - ## Creates a first-index argmax with optional nonzero eligibility masks. - result = proc(runtime: Runtime, arguments: openArray[Value]): Value = - ## Validates and meters a maximum search before reading its elements. - let - values = runtime.arrayView(arguments[0]) - size = count(arguments[if masked: 2 else: 1]) - mask = - if masked: - runtime.arrayView(arguments[1]) - else: - values - values.requireLength(size) - if masked: - mask.requireLength(size) - runtime.chargeOperations(int64(size) * (if masked: 2 else: 1)) - var best = -1 - for i in 0 ..< size: - if masked and not mask[i].asBool: - continue - if best < 0 or values[best] < values[i]: - best = i - toValue(best) +proc argmax(runtime: Runtime, arguments: openArray[Value]): Value = + ## Returns the first index with the greatest value in a numeric prefix. + let + values = runtime.arrayView(arguments[0]) + size = count(arguments[1]) + values.requireLength(size) + runtime.chargeOperations(int64(size)) + var best = 0 + for i in 1 ..< size: + if values[best] < values[i]: + best = i + toValue(best) + +proc argmaxMasked(runtime: Runtime, arguments: openArray[Value]): Value = + ## Returns the first greatest eligible index, or -1 for an empty mask. + let + values = runtime.arrayView(arguments[0]) + mask = runtime.arrayView(arguments[1]) + size = count(arguments[2]) + values.requireLength(size) + mask.requireLength(size) + runtime.chargeOperations(2 * int64(size)) + var best = -1 + for i in 0 ..< size: + if not mask[i].asBool: + continue + if best < 0 or values[best] < values[i]: + best = i + toValue(best) + +proc dataAdd(runtime: Runtime, arguments: openArray[Value]): Value = + ## Adds two numeric prefixes after reserving their work. + let + size = count(arguments[3]) + left = runtime.arrayView(arguments[0]) + right = runtime.arrayView(arguments[1]) + outputs = runtime.arrayView(arguments[2], writable = true) + left.requireLength(size) + right.requireLength(size) + outputs.requireLength(size) + runtime.chargeOperations(int64(size)) + for i in 0 ..< size: + outputs[i] = left[i] + right[i] + toValue(0) + +proc dataMultiply(runtime: Runtime, arguments: openArray[Value]): Value = + ## Multiplies two numeric prefixes after reserving their work. + let + size = count(arguments[3]) + left = runtime.arrayView(arguments[0]) + right = runtime.arrayView(arguments[1]) + outputs = runtime.arrayView(arguments[2], writable = true) + left.requireLength(size) + right.requireLength(size) + outputs.requireLength(size) + runtime.chargeOperations(int64(size)) + for i in 0 ..< size: + outputs[i] = left[i] * right[i] + toValue(0) + +proc dataCopy(runtime: Runtime, arguments: openArray[Value]): Value = + ## Copies a numeric prefix after reserving its work. + let + size = count(arguments[2]) + source = runtime.arrayView(arguments[0]) + outputs = runtime.arrayView(arguments[1], writable = true) + source.requireLength(size) + outputs.requireLength(size) + runtime.chargeOperations(int64(size)) + for i in 0 ..< size: + outputs[i] = source[i] + toValue(0) -proc arithmetic(operation: ArrayOperation): ContextHostProc = - ## Creates a bounded elementwise kernel or ordered dot product. - result = proc(runtime: Runtime, arguments: openArray[Value]): Value = - ## Reserves all work before touching the destination array. - let - binary = operation in {Add, Multiply} - size = count(arguments[if binary: 3 else: 2]) - left = runtime.arrayView(arguments[0], operation == Fill) - right = - if operation in {Add, Multiply, Dot}: - runtime.arrayView(arguments[1]) - else: - left - outputs = - case operation - of Add, Multiply: - runtime.arrayView(arguments[2], writable = true) - of Copy: - runtime.arrayView(arguments[1], writable = true) - of Fill, Dot: - left - left.requireLength(size) - right.requireLength(size) - outputs.requireLength(size) - if operation == Fill and arguments[1].kind == StringValue: - raise newException(BasicError, "dataFill requires a number") - runtime.chargeOperations(int64(size) * (if operation == Dot: 2 else: 1)) - var total = toValue(0) - for i in 0 ..< size: - case operation - of Add: - outputs[i] = left[i] + right[i] - of Multiply: - outputs[i] = left[i] * right[i] - of Copy: - outputs[i] = left[i] - of Fill: - outputs[i] = arguments[1] - of Dot: - total = total + left[i] * right[i] - total +proc dataFill(runtime: Runtime, arguments: openArray[Value]): Value = + ## Fills a mutable numeric prefix after reserving its work. + let + size = count(arguments[2]) + outputs = runtime.arrayView(arguments[0], writable = true) + value = arguments[1] + outputs.requireLength(size) + if value.kind == StringValue: + raise newException(BasicError, "dataFill requires a number") + runtime.chargeOperations(int64(size)) + for i in 0 ..< size: + outputs[i] = value + toValue(0) + +proc dataDot(runtime: Runtime, arguments: openArray[Value]): Value = + ## Computes an ordered dot product after reserving its work. + let + size = count(arguments[2]) + left = runtime.arrayView(arguments[0]) + right = runtime.arrayView(arguments[1]) + left.requireLength(size) + right.requireLength(size) + runtime.chargeOperations(2 * int64(size)) + var total = toValue(0) + for i in 0 ..< size: + total = total + left[i] * right[i] + total proc addArrayFunctions*(host: var Host) = ## Registers generic numeric operations without prescribing a network. discard host.addFunction("linear", 6, linear) discard host.addFunction("relu", 2, relu) - discard host.addFunction("argmax", 2, maximum(false)) - discard host.addFunction("argmaxMasked", 3, maximum(true)) - discard host.addFunction("dataAdd", 4, arithmetic(Add)) - discard host.addFunction("dataMultiply", 4, arithmetic(Multiply)) - discard host.addFunction("dataCopy", 3, arithmetic(Copy)) - discard host.addFunction("dataFill", 3, arithmetic(Fill)) - discard host.addFunction("dataDot", 3, arithmetic(Dot)) + discard host.addFunction("argmax", 2, argmax) + discard host.addFunction("argmaxMasked", 3, argmaxMasked) + discard host.addFunction("dataAdd", 4, dataAdd) + discard host.addFunction("dataMultiply", 4, dataMultiply) + discard host.addFunction("dataCopy", 3, dataCopy) + discard host.addFunction("dataFill", 3, dataFill) + discard host.addFunction("dataDot", 3, dataDot) From 4fd88075b5f76ab38a8722acfca3c2df0f60cc67 Mon Sep 17 00:00:00 2001 From: treeform Date: Thu, 24 Sep 2026 11:23:15 -0700 Subject: [PATCH 6/7] Pin merged Bassy array support --- coworld/dependencies.lock | 2 +- nimby.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coworld/dependencies.lock b/coworld/dependencies.lock index 9c9eb24f..29ff8316 100644 --- a/coworld/dependencies.lock +++ b/coworld/dependencies.lock @@ -1,4 +1,4 @@ -bassy 0.1.0 https://github.com/treeform/bassy 78b3f3c9538cf1512ae99188d412cfffa134259a +bassy 0.1.0 https://github.com/treeform/bassy 2c54d822f775ce92d8b109f2f8aedb4c4b56b29d fixxy 0.1.0 https://github.com/treeform/fixxy 05e5446dffb70093056cebb0c57721a60deaf52a silky 0.2.0 https://github.com/treeform/silky fb9b13910edd66cf1751056784c2f7d2932a59fc pixie 6.1.0 https://github.com/treeform/pixie 87cecced5c4c6f311c658a5f3ca0c9b43edb6aa7 diff --git a/nimby.lock b/nimby.lock index 9c6ba79a..9f194070 100644 --- a/nimby.lock +++ b/nimby.lock @@ -1,4 +1,4 @@ -bassy 0.1.0 https://github.com/treeform/bassy 78b3f3c9538cf1512ae99188d412cfffa134259a +bassy 0.1.0 https://github.com/treeform/bassy 2c54d822f775ce92d8b109f2f8aedb4c4b56b29d fixxy 0.1.0 https://github.com/treeform/fixxy 05e5446dffb70093056cebb0c57721a60deaf52a silky 0.2.0 https://github.com/treeform/silky fb9b13910edd66cf1751056784c2f7d2932a59fc pixie 6.1.0 https://github.com/treeform/pixie 87cecced5c4c6f311c658a5f3ca0c9b43edb6aa7 From 6ff3c02e25cad982bda31ffb528d55aade23220f Mon Sep 17 00:00:00 2001 From: treeform Date: Thu, 24 Sep 2026 11:25:04 -0700 Subject: [PATCH 7/7] Rename neural operations module and helpers --- docs/neural-arrays.md | 2 +- examples/awm/awmbots.nim | 4 ++-- examples/call_to_adventure/bots.nim | 4 ++-- examples/gods_of_the_arena/bots.nim | 4 ++-- examples/heartleaf/bots.nim | 4 ++-- examples/light_vs_dark/bots.nim | 4 ++-- src/polyworld/{arrays.nim => neural.nim} | 4 ++-- tests/{test_arrays.nim => test_neural.nim} | 6 +++--- tests/tests.nim | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) rename src/polyworld/{arrays.nim => neural.nim} (98%) rename tests/{test_arrays.nim => test_neural.nim} (98%) diff --git a/docs/neural-arrays.md b/docs/neural-arrays.md index ac909c3e..e63f066b 100644 --- a/docs/neural-arrays.md +++ b/docs/neural-arrays.md @@ -92,7 +92,7 @@ units per decision. ## Implementation and dependency Bassy implements DATA parsing, initialization, checked array views, and -`ContextHostProc` callbacks. Polyworld's `src/polyworld/arrays.nim` implements +`ContextHostProc` callbacks. Polyworld's `src/polyworld/neural.nim` implements the arithmetic and registers it in all five BASIC game hosts. Game observations and commands keep their existing APIs. diff --git a/examples/awm/awmbots.nim b/examples/awm/awmbots.nim index cab80177..1cd722c6 100644 --- a/examples/awm/awmbots.nim +++ b/examples/awm/awmbots.nim @@ -3,7 +3,7 @@ ## Each invocation plays at most one card; the game loop calls repeatedly ## until the bot ends its turn. import bassy -import polyworld/arrays +import polyworld/neural import awmsim type @@ -57,7 +57,7 @@ proc botLimits(): Limits = proc buildBotHost(playerId: int32): Host = result = initHost() - result.addArrayFunctions() + result.addNeuralFunctions() for name in DataSlotNames: discard result.addData(name) diff --git a/examples/call_to_adventure/bots.nim b/examples/call_to_adventure/bots.nim index 784483c5..8bf4b9de 100644 --- a/examples/call_to_adventure/bots.nim +++ b/examples/call_to_adventure/bots.nim @@ -5,7 +5,7 @@ ## cannot write world fields directly. import - polyworld/arrays, + polyworld/neural, bassy, polyworld/[bodies, metrics, cli, controllers, pathing, profiles], content, @@ -115,7 +115,7 @@ proc heroLimits(): Limits = proc buildHeroHost(heroId: int32): Host = ## Builds the world-query and high-level action API for one hero. result = initHost() - result.addArrayFunctions() + result.addNeuralFunctions() for name in HeroDataNames: discard result.addData(name) diff --git a/examples/gods_of_the_arena/bots.nim b/examples/gods_of_the_arena/bots.nim index 58e39831..29be517e 100644 --- a/examples/gods_of_the_arena/bots.nim +++ b/examples/gods_of_the_arena/bots.nim @@ -2,7 +2,7 @@ ## on the simulation. import - polyworld/arrays, + polyworld/neural, bassy, fixxy, polyworld/[metrics, bodies, cli, controllers, pathing, profiles, tapes], content, @@ -263,7 +263,7 @@ proc abilityProc(heroId: int32, field: AbilityField): HostProc = proc initHeroHost(heroId: int32): Host = ## Builds the bounded world-query and action interface for one hero. result = initHost() - result.addArrayFunctions() + result.addNeuralFunctions() for error in ActionError: discard result.addData($error, error.ord.int32) for class in HeroClass: diff --git a/examples/heartleaf/bots.nim b/examples/heartleaf/bots.nim index 06047e97..7cdb8f55 100644 --- a/examples/heartleaf/bots.nim +++ b/examples/heartleaf/bots.nim @@ -9,7 +9,7 @@ ## commands only queue orders. import - polyworld/arrays, + polyworld/neural, bassy, polyworld/[bodies, profiles], content, @@ -138,7 +138,7 @@ proc buildVillagerHost*(slot: int32): Host = ## live instance, because `initRuntime` validates every binding's arity ## and work cost against what the program was compiled with. result = initHost() - result.addArrayFunctions() + result.addNeuralFunctions() for name in VillagerDataNames: discard result.addData(name) diff --git a/examples/light_vs_dark/bots.nim b/examples/light_vs_dark/bots.nim index b0ce3f66..4bc0456b 100644 --- a/examples/light_vs_dark/bots.nim +++ b/examples/light_vs_dark/bots.nim @@ -11,7 +11,7 @@ ## kill things, and it is where fog of war is applied. import - polyworld/arrays, + polyworld/neural, bassy, polyworld/[bodies, metrics, profiles], content, @@ -281,7 +281,7 @@ proc buildOverlordHost*(playerId: int32): Host = ## cost far more than their own cycles, so a script's budget prices its ## demand on the simulation rather than only its own arithmetic. result = initHost() - result.addArrayFunctions() + result.addNeuralFunctions() for name in OverlordDataNames: discard result.addData(name) diff --git a/src/polyworld/arrays.nim b/src/polyworld/neural.nim similarity index 98% rename from src/polyworld/arrays.nim rename to src/polyworld/neural.nim index 269ad55d..9b15df0a 100644 --- a/src/polyworld/arrays.nim +++ b/src/polyworld/neural.nim @@ -1,4 +1,4 @@ -## Deterministic native array arithmetic for every Polyworld BASIC host. +## Deterministic native neural operations for every Polyworld BASIC host. import bassy @@ -153,7 +153,7 @@ proc dataDot(runtime: Runtime, arguments: openArray[Value]): Value = total = total + left[i] * right[i] total -proc addArrayFunctions*(host: var Host) = +proc addNeuralFunctions*(host: var Host) = ## Registers generic numeric operations without prescribing a network. discard host.addFunction("linear", 6, linear) discard host.addFunction("relu", 2, relu) diff --git a/tests/test_arrays.nim b/tests/test_neural.nim similarity index 98% rename from tests/test_arrays.nim rename to tests/test_neural.nim index 10316426..2fbe91e8 100644 --- a/tests/test_arrays.nim +++ b/tests/test_neural.nim @@ -1,12 +1,12 @@ import std/strutils, bassy, - polyworld/arrays + polyworld/neural proc host(): Host = ## Creates the same native array function set used by game hosts. result = initHost() - result.addArrayFunctions() + result.addNeuralFunctions() proc rejects(action: proc() {.closure.}, message: string) = ## Requires an explicit BASIC error instead of a defect or silent failure. @@ -163,4 +163,4 @@ linear(x, weights, biases, output, 2, 2) discard full.run() doAssert full.memoryBytes == bytes -echo "test_arrays: all checks passed" +echo "test_neural: all checks passed" diff --git a/tests/tests.nim b/tests/tests.nim index 16e75b54..bbf5dd9d 100644 --- a/tests/tests.nim +++ b/tests/tests.nim @@ -2,7 +2,7 @@ {.warning[UnusedImport]: off.} import - test_arrays, + test_neural, test_assetpacks, test_actioncam, test_animblend_controls,