Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d75e75b
Add player mailboxes to BASIC games
treeform Sep 24, 2026
66656a9
Reclaim temporary strings between BASIC decisions
treeform Sep 24, 2026
2a2dcb2
Test default mailboxes across all games
treeform Sep 24, 2026
a889214
Preallocate mailbox buffers and script scratch
treeform Sep 24, 2026
9481dd4
Simplify inboxes and move routing into games
treeform Sep 24, 2026
dd18d74
Give each game its own chat rules
treeform Sep 24, 2026
a2b4e12
Merge pull request #73 from Metta-AI/polyworld-mailboxes
treeform Sep 24, 2026
f4456be
Fix tower range and homing attacks
treeform Sep 24, 2026
23f3384
Record tower fix release and round
treeform Sep 24, 2026
e309ea5
Merge upstream/main into the neural tier
daveey Sep 25, 2026
1a96459
gota: neural policies on the shared neural tier
daveey Sep 25, 2026
1404659
gota: an invalid defer-seat override falls back to the script
daveey Sep 25, 2026
5e8dc81
gota: per-world vision blockers (no cross-world camp sight leak), thr…
daveey Sep 25, 2026
0540def
gota: package seats keep their manifest goal in the native env; neura…
daveey Sep 25, 2026
f9a93bb
gota: goal test verifies the native replay on the binary
daveey Sep 25, 2026
f402098
gota: concurrent-create test compares like with like (config and acti…
daveey Sep 25, 2026
7991759
gota: native env freezes the observation frame before neural seats ob…
daveey Sep 25, 2026
a7efc35
gota: package seats read the match length at decision time (hosted se…
daveey Sep 25, 2026
2f374f4
gota: docs: native frame freeze and match length (native == hosted)
daveey Sep 25, 2026
9f9d86c
coworld: runtime test covers a rejected neural package (player failur…
daveey Sep 25, 2026
a7a84ca
gota: test_defer d, invalid overrides fall back to the script (== pla…
daveey Sep 25, 2026
1554904
gota: canary checks the telemetry line format (decisions/invalid, def…
daveey Sep 25, 2026
736d86a
gota: tool and doc wording (no plan item numbers, no fork wording)
daveey Sep 25, 2026
feeca58
gota: test_native_env covers the ABI error paths (net_infer lastError…
daveey Sep 25, 2026
61ec724
gota: golden-hash check tolerates a CRLF checkout (Windows CI)
daveey Sep 25, 2026
db052bf
Merge branch 'refresh-fix78' into refresh-fix76
daveey Sep 27, 2026
af519f7
Review fixes: gota_reset lastError, package policy-compile routing, w…
daveey Sep 27, 2026
5fabd60
Merge branch 'refresh-fix78' into refresh-fix76
daveey Sep 27, 2026
7863481
Merge branch 'refresh-fix78' into refresh-fix76
daveey Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
run: nim r tests/test_neural_cases.nim tmp/neural_cases
- name: coworld.nim glue unit tests (readPlayerSource, failPlayer)
run: nim r coworld/tools/test_coworld_glue.nim
- run: nim r -d:nimAllocStats tests/test_mailbox_allocations.nim
- run: nim r tests/test_gota_events.nim
- run: nim r -d:replayEvents tests/test_gota_controls.nim
- run: nim r -d:replayEvents tests/test_gota_camps.nim
Expand Down Expand Up @@ -60,3 +61,13 @@ jobs:
- run: nim r -d:headless -d:recordGota tests/test_recordings.nim --bot examples/gods_of_the_arena/players/base.bas:10
- run: nim r -d:headless -d:recordHlf tests/test_recordings.nim --bot examples/heartleaf/players/base.bas:9
- run: nim r -d:headless -d:recordLvd tests/test_recordings.nim --bot examples/light_vs_dark/players/base.bas:2
- run: nim r -d:headless tests/test_gota_neural.nim
- name: No-neural parity with main (golden hashes)
shell: bash
run: bash tests/gota_golden.sh
- name: Native training library compiles
if: runner.os == 'Linux'
run: >-
nim c --app:lib -d:headless -d:gotaTrainingStats --mm:atomicArc
--threads:on -d:useMalloc -u:nimTypeNames -o:tmp/libgota_env.so
examples/gods_of_the_arena/native_env.nim
2 changes: 1 addition & 1 deletion coworld/dependencies.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
bassy 0.1.0 https://github.com/treeform/bassy 2c54d822f775ce92d8b109f2f8aedb4c4b56b29d
bassy 0.1.0 https://github.com/treeform/bassy 669a7c4b94e3d5b0a38dc9557608a7e58c2a764d
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
Expand Down
2 changes: 1 addition & 1 deletion coworld/gota/coworld_manifest_template.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion coworld/gota/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ the last hit. This is awarded once on the final tick and included in lifetime XP
before calculating scores. It offsets 5 minutes of the 200-XP-per-minute time
penalty. Timeouts grant no god reward.
Destroying a tower or barracks grants its killer 200 XP and 75 gold.
Towers fire once per second, and reload continues when they lose or switch
targets. Their homing fireballs deal damage on arrival and follow the original
target beyond range and into fog, even if the tower is destroyed. Shots disappear
if their target dies before impact.

The gods are the objectives: Hades for Red and Zeus for Blue. Each god has two level-3 guard towers. Clearing all three towers in any one lane exposes the guards. The god cannot take damage from attacks or spells until both of its guards are destroyed. Guards have the same 3900 HP and 60 damage as level-3 lane towers.

Expand Down Expand Up @@ -237,7 +241,8 @@ Buy item **21** for **100 gold**. Scrolls stack to eight per slot. Call
`useItemAt(slot, x, y)` with whole or fractional map coordinates to consume
one scroll and begin a **3-second** channel. The destination is the nearest
visible, walkable point inside a living allied tower's sight radius:
7 tiles for outer/inner towers, 8 for gate/guard towers. Barracks are not
9 tiles for outer towers, 9.5 for inner towers, and 10 for gate/guard towers.
Attack range, vision, and portal landing range share this same radius. Barracks are not
anchors. A distant requested point is clamped into this area; there is no
travel-distance limit. The selected tower must survive until arrival.

Expand Down
156 changes: 156 additions & 0 deletions coworld/gota/runtime/neural_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""Staging validator and builder for GotA neural packages (gota-neural-basic/1).

GotA's contract on top of the shared polyworld validator (coworld/runtime/neural_package.py),
which mirrors src/polyworld/neural_package.nim; the GotA extension keys (goal, decoder.defer_script,
decoder.mask_empty_targets, decoder.mask_mode) mirror parseGotaOptions in
examples/gods_of_the_arena/neural_contract.nim. Plain .bas submissions are not packages and are
untouched.

python3 neural_package.py validate PACKAGE.zip [--obs-hash H --action-hash H]
python3 neural_package.py build OUT.zip --policy policy.bas --model model.bin
[--period 4] [--decoder argmax|sample --temperature T] [--goal-red 16 floats] [--goal-blue ...]
"""
import argparse, dataclasses, importlib.util, os, sys

_SHARED = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../runtime/neural_package.py")
_spec = importlib.util.spec_from_file_location("polyworld_neural_package", _SHARED)
tier = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(tier)

PackageError = tier.PackageError
is_package = tier.is_package
SCHEMA = "gota-neural-basic/1"
FILES = tier.FILES
MAX_PACKAGE = tier.MAX_PACKAGE
MAX_POLICY = tier.MAX_POLICY
HEADS = [8, 25, 49, 4, 6]
OBS_SIZE = 1407
MASK_SIZE = 187
GOAL_SIZE = 16
WIDTHS = tier.WIDTHS
MAGIC = tier.MAGIC
OP_BUDGET = tier.DEFAULT_OP_BUDGET
MAX_PARAMS = tier.MAX_PARAMS
# Contract v1 hashes (examples/gods_of_the_arena/neural_contract.nim; gota_*_contract_hash).
OBS_HASH = "ae4046e83cc02e861f9c8cc32550c6cc4d6f9c161c9225a9b34a314d310ea991"
ACTION_HASH = "ecc7d53c11a9db0912467c66ecb3e65b60b3e71ef14dad1442ba3b4f6ac14697"


def _goal(node, where):
if not isinstance(node, list) or len(node) != GOAL_SIZE or not all(tier.is_number(v) for v in node):
raise PackageError(f"{where} must be 16 numbers")
if any(v < -1 or v > 1 for v in node):
raise PackageError(f"{where} values must be in [-1, 1]")
if node[15] != 0:
raise PackageError(f"{where} w_reserved must be 0")


def parse_options(m):
"""GotA manifest keys: goal {red, blue}; decoder.defer_script / mask_empty_targets / mask_mode."""
options = {"defer_script": False, "mask_mode": None}
if "goal" in m:
tier.require_keys(m["goal"], {"red", "blue"}, "goal")
if set(m["goal"]) != {"red", "blue"}:
raise PackageError("goal needs red and blue")
_goal(m["goal"]["red"], "goal.red")
_goal(m["goal"]["blue"], "goal.blue")
if "decoder" in m:
d = m["decoder"]
masked = tier.require_bool(d, "mask_empty_targets", "decoder")
if masked:
options["mask_mode"] = "conditional"
if "mask_mode" in d:
if not masked:
raise PackageError("decoder.mask_mode needs mask_empty_targets true")
if d["mask_mode"] not in ("conditional", "static"):
raise PackageError("decoder.mask_mode must be conditional or static")
options["mask_mode"] = d["mask_mode"]
options["defer_script"] = tier.require_bool(d, "defer_script", "decoder")
return options


GOTA = tier.Contract(schema=SCHEMA, obs_hash=OBS_HASH, action_hash=ACTION_HASH, obs_size=OBS_SIZE,
heads=HEADS, max_period=24, op_budget=OP_BUDGET, manifest_keys=("goal",),
decoder_keys=("defer_script", "mask_empty_targets", "mask_mode"),
parse_options=parse_options)


def contract(obs_hash=OBS_HASH, action_hash=ACTION_HASH):
if obs_hash == OBS_HASH and action_hash == ACTION_HASH:
return GOTA
return dataclasses.replace(GOTA, obs_hash=obs_hash, action_hash=action_hash)


def check_model(data: bytes, obs_hash=OBS_HASH, action_hash=ACTION_HASH):
"""Validates a GOTANET1 model.bin; returns (hidden, operations)."""
return tier.check_model(data, contract(obs_hash, action_hash))


def validate(data: bytes, obs_hash=OBS_HASH, action_hash=ACTION_HASH) -> dict:
"""Raises PackageError with the reason; returns the manifest on success."""
manifest, _ = tier.validate(data, contract(obs_hash, action_hash))
return manifest


def encode_model(weights, hidden, obs_hash=OBS_HASH, action_hash=ACTION_HASH, inputs=OBS_SIZE, heads=HEADS):
"""GOTANET1 bytes from a flat float32 list: W_enc[H][I], W_rec[3H][H], W_dec[O][H]."""
return tier.encode_model(weights, hidden, contract(obs_hash, action_hash), inputs, heads)


def build(policy: bytes, model: bytes, period=4, decoder=None, goal=None) -> bytes:
return tier.build(policy, model, GOTA, period, decoder, {"goal": goal} if goal else None)


def main():
ap = argparse.ArgumentParser()
sub = ap.add_subparsers(dest="cmd", required=True)
v = sub.add_parser("validate")
v.add_argument("package")
v.add_argument("--obs-hash", default=OBS_HASH)
v.add_argument("--action-hash", default=ACTION_HASH)
b = sub.add_parser("build")
b.add_argument("out")
b.add_argument("--policy", required=True)
b.add_argument("--model", required=True)
b.add_argument("--period", type=int, default=4)
b.add_argument("--decoder", choices=["argmax", "sample"])
b.add_argument("--temperature", type=float)
b.add_argument("--defer-script", action="store_true",
help="decoder.defer_script: verb 0 defers to policy.bas (e.g. base.bas verbatim)")
b.add_argument("--mask-empty-targets", action="store_true",
help="decoder.mask_empty_targets: host applies the gota_action_mask before decode")
b.add_argument("--mask-mode", choices=["conditional", "static"])
b.add_argument("--goal-red", type=float, nargs=16)
b.add_argument("--goal-blue", type=float, nargs=16)
a = ap.parse_args()
if a.cmd == "validate":
data = open(a.package, "rb").read()
if not is_package(data):
print("plain BASIC submission (not a package): unchanged")
return
try:
m = validate(data, a.obs_hash, a.action_hash)
except PackageError as e:
print(f"REJECTED: {e}")
sys.exit(1)
print(f"OK {SCHEMA} hidden={m['model']['hidden']} period={m['decision_period']}")
else:
decoder = None
if a.decoder:
decoder = {"mode": a.decoder}
if a.temperature is not None:
decoder["temperature"] = a.temperature
if a.defer_script:
decoder = dict(decoder or {}, defer_script=True)
if a.mask_empty_targets:
decoder = dict(decoder or {}, mask_empty_targets=True)
if a.mask_mode:
decoder["mask_mode"] = a.mask_mode
goal = {"red": a.goal_red, "blue": a.goal_blue} if a.goal_red and a.goal_blue else None
data = build(open(a.policy, "rb").read(), open(a.model, "rb").read(), a.period, decoder, goal)
open(a.out, "wb").write(data)
print(f"wrote {a.out} ({len(data)} bytes)")


if __name__ == "__main__":
main()
74 changes: 74 additions & 0 deletions coworld/releases/2026-09-24-gota-towers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"date": "2026-09-24",
"version": "2026.9.24.2",
"url": "https://softmax.com/gods-of-the-arena",
"coworld_id": "cow_e282a46f-31c4-43b1-a9e2-aaa31d3aaed4",
"canonical": true,
"source_commit": "f4456bea13d967cb3f188df357656e1410cb0a40",
"asset_commit": "32946b35b92e2b33f38945e96b293eb6e2c98ac9",
"replay_version": 64,
"changes": [
"Increase tower attack, vision, and portal range together to 9, 9.5, and 10 tiles.",
"Keep tower reload progress when targets leave range or change.",
"Make tower fireballs track their original target beyond range and into fog, dealing damage on arrival.",
"Restore in-flight projectiles during replay seeking and support only gameplay replay version 64.",
"Include the mailbox changes already merged on main."
],
"checks": [
"Nim checks and the complete test suite passed after rebasing onto current main.",
"Tower range, reload, homing, target death, and mid-flight snapshot regressions passed.",
"Native and WASM replay verification matched, including four backward seeks and a mid-flight snapshot.",
"All ten local certification checks and hosted certification passed.",
"All five hosted smoke games completed with verified integer scores.",
"Served JavaScript, WASM, and data SHA-256 hashes match the production build.",
"The Linux certification replay and a replay from the forced league round reproduced exactly on the native client."
],
"browser_verification": {
"episode_id": "08e742c8-c109-448d-b580-0ca8edfb3e19",
"replay_version": 64,
"end_tick": 30049,
"visible_tick_label": "Tick 30049 / 30049",
"browser_errors": [],
"method": "Deployed viewer rendered combat, sought through the final tick, and displayed the final scoreboard.",
"verified_at": "2026-09-24T22:58:15.877536+00:00"
},
"league_settings_preserved": true,
"known_issues": [],
"native_league_replay": {
"round_number": 742,
"id": "ereq_64ec48a7-6c9b-4816-a536-f3ab3fea3aff",
"episode_id": "08e742c8-c109-448d-b580-0ca8edfb3e19",
"replay_version": 64,
"sha256": "70e00c59901c88151b72806e2560ab29b3c3553c16c7ac981e02769a995ae906",
"ticks": 30049,
"outcome": "time_limit"
},
"verification_round": {
"round_id": "round_d745770e-56e3-4c76-bcfc-6c70df21ffc8",
"round_number": 742,
"status": "completed",
"games": 24,
"completed_games": 24,
"failed_games": 0,
"failures": [],
"coworld_id": "cow_e282a46f-31c4-43b1-a9e2-aaa31d3aaed4",
"version": "2026.9.24.2",
"integer_scores_verified": 240,
"outcomes": {
"time_limit": 22,
"BlueTeam": 1,
"RedTeam": 1
},
"replay_version": 64,
"watch_url": "https://softmax.com/gods-of-the-arena?e=90f3176a-9090-40ff-b1c2-9430401fb6a6",
"verified_at": "2026-09-24T23:00:37.226208+00:00"
},
"manifest_hash": "sha256:d9be78bff6cc9c74d3de3f9e55ccb5bd20b3f181523b6ec88d90a38d68435a78",
"viewer_sha256": {
"wasm": "de36c8625be15bd101dc82ea4e1f4dedef0b65981dcd3e8420ba3cb35a362000",
"data": "6a1d07f38179cd0ce3498dbaefb725c634d74a7705426ded9a119a83dd98e3f0",
"js": "12e3b4c6d0d346db5689253a768a9c6ba4f8c68d7743311272439875577c97ca"
},
"ci_url": "https://github.com/Metta-AI/polyworld/actions/runs/36069192354",
"verified_at": "2026-09-24T23:00:55.692025+00:00"
}
Loading