Conversation
Add player mailboxes to BASIC games
Gods of the Arena's neural seats, rebuilt as the first client of the polyworld neural tier (src/polyworld/neural_*.nim). Behaviour is byte-identical to the previous GotA-only implementation. - neural_contract.nim: observation v1, action v1, the demonstration encoder, and the GotA package options (goal, defer_script, a MaskMode enum for mask_empty_targets / mask_mode). - neural_host_hooks.nim: GotaContract built with initNeuralContract; NeuralSeat derives from NeuralBrain; the observe/decode/mask callbacks; learner, capture, override, shadow, defer and mask seats. The enum value NeuralPackage is now NeuralHosted. - bots.nim / sim.nim: package seats through parseGotaPackage; a rejected package ends a hosted episode with its real reason (failPlayer); deferConsult runs inside runHeroVm's error handling. - native_env.nim / .h: the training ABI with opaque handle types and magic checks, gota_mask_size, contract-checked gota_net_load, lastError on every failure path, rejection reasons kept across reset. - coworld/gota/runtime/neural_package.py: a thin GotA wrapper over the shared validator; tools/native_env.py raises on every negative return code and asserts its sizes against the library. - tests: test_gota_neural.nim (contract identity, GotA corruption cases), gota_golden.sh (no-neural final hashes recorded from main), and a compile check of the native library in CI. - tools/parity_tier.py: the byte-identity battery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A defer seat whose network picks an override that decodes to nothing (e.g. an empty target slot) used to absorb the script's commands for the whole decision window, so the hero idled. It now counts as invalid and defers for that window. Always-defer seats and seats without decoder.defer_script are unchanged. The GotA telemetry line gains decisions= and invalid= counts, plus defer= and override= for defer seats. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ead-safe gota_create, isolation tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l seats get the plain per-tick BASIC budget; metrics use each seat's own limit; lastError thread-local doc; goal parity test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on RNG by kind) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…serve (hosted order; fixes native != hosted package play); replay_diff tool; goal/hosted parity test covers default goals Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ats saw maxTicks 0: time features and telemetry wrong) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e with the package reason) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in base.bas) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er/override in defer mode) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, policy script message, reasons kept across reset); binding types gota_set_policy_script Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
daveey
force-pushed
the
daveey/neural-policies-upstream
branch
from
September 25, 2026 22:36
1898578 to
feeca58
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # .github/workflows/build.yml # coworld/tools/test_runtime.nim
…idth carry, native_env.py check=True - examples/gods_of_the_arena/native_env.nim: resetEnv now sets the thread-local lastError to a message naming every seat that failed to compile and its reason before returning -2 from gota_reset (it only updated per-seat status before). tools/native_env.py's NativeEnvError now carries that message as `.message`. - examples/gods_of_the_arena/bots.nim: installPackageSeat compiles a package's policy.bas outside compilePlayer's own failPlayer call, so a valid manifest with a broken policy.bas is reported as "neural package rejected for player slot N: policy.bas failed to compile: <reason>" through failPlayer, instead of the generic "BASIC compilation failed for player slot N" (compilePlayer's own message, which ran unconditionally regardless of why compilation was being attempted). - coworld/tools/test_runtime.nim: a new case next to the existing package-rejection one builds a valid GotA package (manifest + model.bin) with a syntactically broken policy.bas and checks the new message and failPlayer's status.json output; failureMessage is now a startsWith check so it can assert a prefix when the compiler's own error text varies. Verified against a real -d:coworld gota binary on the cluster. - examples/gods_of_the_arena/tools/native_env.py: set_package/set_script take `check=True` and raise NativeEnvError (carrying the status message) on any nonzero return by default; `check=False` keeps the old return-code behavior for the tests that intentionally probe rejection. Updated the three callers that rely on inspecting a rejection. - Width support carried into GotA: examples/gods_of_the_arena/neural_basic.md (widths, op counts, w512 model size) and examples/gods_of_the_arena/tools/test_native_env.py (ported test_widths: w64-w512 accepted, 15 neighboring widths rejected by both the Python validator and the raw Nim loader, plus the hosted seat). GotA's own Python validator (coworld/gota/runtime/neural_package.py) already inherits WIDTHS from the shared tier validator fixed in Metta-AI#78, so no separate edit was needed there. Rebases/merges cleanly onto the updated polyworld-neural-tier (Metta-AI#78 fixes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds neural policies to Gods of the Arena: a package seat runs a small FP32 network next to its BASIC program. It also adds a native training environment with a C ABI. GotA is the first client of the tier in #78: the package loader, model format, op budget, telemetry line, recurrent-state lifecycle and neural BASIC functions all live there. Plain
.bassubmissions are unchanged, and matches without neural seats replay byte-identically tomain(a new CI step checks this).Specs:
examples/gods_of_the_arena/neural_basic.md(GotA) anddocs/neural-policies.md(tier). C ABI:examples/gods_of_the_arena/native_env.h.What's here
neural_contract.nim): observation v1 (1407 floats, ego-centric), action v1 (heads verb 8, target 25, point 49, ability 4, item 6), the demonstration encoder, and the GotA package keys:goal,decoder.defer_script,decoder.mask_empty_targets+mask_mode(aMaskModeenum).neural_host_hooks.nim):GotaContractis built with the tier'sinitNeuralContract, andNeuralSeatderives from the tier'sNeuralBrain. Seat kinds: hosted package, learner, capture, override, shadow, defer and mask. Every neural seat gets the same per-tick BASIC budget as a plain.basseat (20,000 instructions, 50,000 work units); the network runs on its own 4,000,000-op budget.native_env.nim/.h): a thread-safe C ABI for PufferLib-style trainers. Any learner/script mix, BC labels, DAgger shadows, defer and override seats, action masks, package seats, andgota_net_*for bit-exact actor checks.GotaEnv/GotaNethandles with magic checks,gota_mask_size, and a contract-checkedgota_net_load.gota_last_erroris thread-local and set on every failure path, includinggota_net_inferandgota_set_policy_script(which keeps the compile message).gota_reset. Aftergota_stepreturns -3 the caller mustgota_resetbefore stepping again (documented).gota_create.coworld/gota/runtime/neural_package.pyis a thin wrapper over the shared validator.tools/native_env.pyraisesNativeEnvErroron every negative return code and checks 1407/92/187/24 against the library.sim.nim/bots.nim: behaviour-preserving refactors.deferConsultruns insiderunHeroVm's error handling.HeroVm.neuralstaysRootRef, sosim.nimdoesn't import float code.parity_tier.py(byte-identity battery between two libraries),parity_upstream.sh(no-neural parity withmain),test_defer.py,test_mask.py,test_package_goal.py,test_thread_isolation.py,test_native_env.py,test_native_concurrency.py,mapping_ceiling.py,canary.py,replay_diff.nim.build.yml):tests/test_gota_neural.nim, a golden-hash no-neural parity check againstmain(tests/gota_golden.sh, base/puller/rusher, hashes recorded frommain), and a compile-only build of the native library.Intentional behaviour changes (each with its own proof)
Everything else is byte-identical to the previous implementation of this PR (see Evidence).
test_defer.py d: a defer seat fed only invalid overrides (62,110 decisions over 10 full matches) plays byte-identical to plain base.bas on 10/10, with 0 overrides; the previous library matches on 0/10... ticks=<t> inferences=<n>decisions=<n> invalid=<n>, plusdefer=<n> override=<n>on defer seatscanary.pyon the-d:coworldserver, full 28,800-tick matches, 5 package seats each: argmax w128 and defer w64. Every line matches the format, every seat exits 0, and the replays re-simulate with 0 mismatchesfailure.jsonsaid "BASIC compilation failed for player slot N"failure.jsonsays "neural package rejected for player slot N"; the reason is in the seat logcoworld/tools/test_runtime.nim: passes here. The same test against the previous implementation fails with "BASIC compilation failed for player slot 0"runHeroVmgota_net_inferleftgota_last_errorempty; an invalid policy script lost its message;gota_resetcould drop a package rejection reasontest_native_env.py: pass here; the previous library fails exactly these threeEvidence
Linux, Nim 2.2.10. "Previous implementation" is this PR's code before it moved onto the tier.
build.ymltest list,test_gota_neural.nim, golden hashes, native library build,coworld/tools/test_runtime.nimbuild.ymlat this PR's head (ubuntu, macOS, Windows; dispatched, since the workflow only runs on PRs intomain)parity_tier.py), 20 seeds per lineup, full 28,800 ticks. Compares the state hash after every step, the replay bytes, seat stats, and defer/override countsmain(parity_upstream.sh), 20 seeds, full lengthmain's binary 20/20test_native_env.py: a package seat equals a learner driven bygota_net_inferat w64/w128/w256.test_package_goal.py: native package seat == hosted server, 20/20 (10 seeds x manifest goal / default goal): state hash and action streams, final hash, and the native replay verified by the binary.test_defer.py b: ABI defer seat == package defer seat on a mixed net, 10/10test_defer.py a: an always-defer net equals plain base.bas, ABI and package, 20/20 (hashes + replay).c: seats without the option match the previous implementation 10/10temperature: true, a non-finite goal, bad JSON, truncation), plus the tier's 50-case suitetest_native_env.py)gota_net_inferon a NaN observation returns -2 with "nonfinite neural input"; an invalid policy script returns 1 with its compile message; a package rejection reason and a script compile message are still reported aftergota_reset; the binding raisesNativeEnvErroron the -2 reset. All pass here; the previous library fails the first threeNot in this PR
Caveats
mapping_ceiling.py(100 seeds, in neural_basic.md's Evidence) shows base.bas loses nothing when routed through it.🤖 Generated with Claude Code
Review fixes
(After first rebasing/merging onto the updated
polyworld-neural-tierwith #78's review fixes.)gota_resetreturned -2 on a seat compile failure but never setlastErrornative_env.nim'sresetEnv: when the result is -2, builds"seat compilation failed: seat N: <reason>; seat M: <reason>..."from every seat whosestatus.code == 2and sets the thread-locallastError.tools/native_env.py'sNativeEnvErrornow stores the message as.message. Verified intest_native_env.py'stest_error_paths: "reset with a failed seat raises NativeEnvError" passes.policy.baswas reported as "BASIC compilation failed", not "neural package rejected"bots.nim'sinstallPackageSeatnow compilespolicy.basitself (not throughcompilePlayer, whose ownfailPlayercall ran unconditionally with the wrong message) and reports "neural package rejected for player slot N: policy.bas failed to compile:<reason>" through the samefailPlayerpath. New case incoworld/tools/test_runtime.nim, next to the existing package-rejection case: builds a valid GotA package (real manifest + model.bin) with a syntactically brokenpolicy.basand checks the new message plusfailPlayer'sstatus.jsonoutput (reason, exit code, other slots untouched). Run against a real-d:coworldgota binary built on the cluster: passes.tools/native_env.py'sset_package/set_scriptreturned a status code silently on failurecheck=Trueand raiseNativeEnvError(carrying the status message) on any nonzero return by default;check=Falseopts back into the old return-code behavior for the three call sites that intentionally probe a rejection (test_native_env.py's validation-suite loop and the two explicit "-> 1"/"-> 2" error-path checks). All other callers already asserted== 0, so their behavior on success is unchanged, and now fails loudly instead of via a bareassertif a package they expect to succeed doesn't.coworld/gota/runtime/neural_package.py'sWIDTHSalready inherits from the shared tier validator (fixed in #78), so no edit was needed there.examples/gods_of_the_arena/neural_basic.md: width list (64/128/256/384/512) and op counts (218,496 / 486,144 / 1,168,896 / 2,048,256 / 3,124,224), plus the w512 model size note.tools/test_native_env.py: portedtest_widths(ACCEPT_WIDTHS/REJECT_WIDTHS, 15 neighboring widths rejected by both the Python validator and the raw Nim loader, and each accepted width also checked through a hosted seat), and widenedtest_package_parity's width list to include w384/w512.Invariants (with evidence)
h <= 256);test_native_env.py's "package seat == ABI-driven seat" passes at w64/w128/w256 with the same op counts as before (218,496 / 486,144 / 1,168,896)..basseats stay byte-identical:bash tests/gota_golden.sh(no-neural parity againstmain) passes 3/3 seeds on the built library at this PR's head.test_native_env.py's existing suites (labels, capture identity, shadow, goals, rewards) are unaffected and pass.tests/test_gota_neural.nim, the native training library build (--app:lib -d:headless -d:gotaTrainingStats --mm:atomicArc --threads:on -d:useMalloc -u:nimTypeNames),tests/gota_golden.sh(3/3), andexamples/gods_of_the_arena/tools/test_native_env.py(quick mode,ALL PASSincluding the new width and error-path checks) all pass on the metta cluster against a fullynimby.lock-pinned dependency graph.coworld/tools/test_runtime.nim's gota case (including the new package-policy-compile-failure case) passes against a real-d:coworldgota binary built on the cluster. GitHub Actions dispatched at this PR's head (7863481): pass on ubuntu-latest, macos-latest and windows-latest — https://github.com/daveey/polyworld/actions/runs/36342890904Note on test robustness
test_widths's width-640 case (deliberately far outsideActorWidths) is rejected by both validators for different, equally correct reasons: the package-level Python validator hits model.bin's own size cap first (a ZIP entry's declared size is checked before it's parsed), while the rawgota_net_loadcall used in this test (no package wrapper) only ever reaches the dimensions check. The assertion accepts either failure mode; the 13 other neighboring widths (1, 32, 63, 65, 96, 192, 255, 257, 320, 383, 385, 448, 511, 513) are all rejected by the dimensions check on both sides, as expected.