From ca3a26f07fee5781db4419519c1cf21709078a21 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Tue, 17 Mar 2026 18:46:20 -0500 Subject: [PATCH 01/14] feat: add Phase 1 game engine for Battle City Tank Arena 31x31 symmetric arena with brick/steel walls, tank breeds with cardinal movement, bullet mechanics (2 patches/tick, wall damage), explosion visuals, scripted bot AI (patrol/chase/attack/retreat), 3 stage setups (solo nav, team battle, team+comms), win conditions, scoring system, and full interface widgets. LLM action selection is stubbed with random choices for testing. --- .../battle-city-tank-arena.nlogox | 977 ++++++++++++++++++ 1 file changed, 977 insertions(+) create mode 100644 demos/battle-city-tank-arena/battle-city-tank-arena.nlogox diff --git a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox new file mode 100644 index 0000000..071aed5 --- /dev/null +++ b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox @@ -0,0 +1,977 @@ + + + + let col-idx 0 + let chars-remaining row-str + while [col-idx < 31 and col-idx < length row-str] [ + let ch item col-idx row-str + let px col-idx - 15 + let py row-idx - 15 + + if px >= min-pxcor and px <= max-pxcor and py >= min-pycor and py <= max-pycor [ + ask patch px py [ + if ch = "." [ + set terrain-type "open" + set pcolor gray - 3 + ] + if ch = "W" [ + set terrain-type "brick" + set wall-hp 2 + set pcolor brown + 1 + ] + if ch = "S" [ + set terrain-type "steel" + set wall-hp -1 ;; indestructible + set pcolor gray - 1 + ] + if ch = "R" [ + set terrain-type "base-red" + set base-team 1 + set base-hp 3 + set pcolor red - 2 + ] + if ch = "B" [ + set terrain-type "base-blue" + set base-team 2 + set base-hp 3 + set pcolor blue - 2 + ] + if ch = "~" [ + set terrain-type "water" + set pcolor cyan - 1 + ] + ] + ] + set col-idx col-idx + 1 + ] + set row-idx row-idx + 1 + ] + + ;; Store base HP in globals + set red-base-hp 3 + set blue-base-hp 3 +end + +;; ============================================================ +;; STAGE SETUP PROCEDURES +;; ============================================================ +to setup-stage-1 + ;; Solo navigation: 1 LLM tank (blue) must reach the flag + ;; Place flag near center-top + ask patch 0 10 [ + set terrain-type "flag" + set pcolor green + 1 + ] + create-flags 1 [ + setxy 0 10 + set shape "flag" + set color green + set size 2 + ] + + ;; Spawn 1 LLM tank at red base area (bottom) + create-tanks 1 [ + set team-id 2 + set color blue + 1 + set shape "arrow" + set size 1.8 + set heading 0 + set tank-direction 0 + set tank-health 3 + set is-llm? true + set last-action "none" + set my-observation "" + set ally-message "" + set my-message "" + set message-timer 0 + set fire-cooldown 0 + setxy 0 -13 + set spawn-x 0 + set spawn-y -13 + ] +end + +to setup-stage-2 + ;; Team battle: 2 LLM tanks (blue) vs 2 scripted bots (red), no comms + ;; Blue LLM tanks + create-tanks 1 [ + setup-tank-defaults 2 true + setxy -3 -13 + set spawn-x -3 + set spawn-y -13 + ] + create-tanks 1 [ + setup-tank-defaults 2 true + setxy 3 -13 + set spawn-x 3 + set spawn-y -13 + ] + + ;; Red bot tanks + create-tanks 1 [ + setup-tank-defaults 1 false + setxy -3 13 + set spawn-x -3 + set spawn-y 13 + set heading 180 + set tank-direction 180 + ] + create-tanks 1 [ + setup-tank-defaults 1 false + setxy 3 13 + set spawn-x 3 + set spawn-y 13 + set heading 180 + set tank-direction 180 + ] +end + +to setup-stage-3 + ;; Same as stage 2 but communication enabled + setup-stage-2 + set communication-enabled? comms-override? +end + +to setup-tank-defaults [team llm?] + set team-id team + ifelse team = 1 + [ set color red + 1 ] + [ set color blue + 1 ] + set shape "arrow" + set size 1.8 + set heading 0 + set tank-direction 0 + set tank-health 3 + set is-llm? llm? + set last-action "none" + set my-observation "" + set ally-message "" + set my-message "" + set message-timer 0 + set fire-cooldown 0 +end + +;; ============================================================ +;; GO — Main tick loop +;; ============================================================ +to go + if game-over? [ stop ] + if ticks >= max-ticks [ resolve-timeout stop ] + + ;; Update communication toggle live + if current-stage = 3 [ + set communication-enabled? comms-override? + ] + + ;; 1. Decrease cooldowns + ask tanks with [tank-health > 0] [ + if fire-cooldown > 0 [ set fire-cooldown fire-cooldown - 1 ] + if message-timer > 0 [ + set message-timer message-timer - 1 + if message-timer = 0 [ set label "" ] + ] + ] + + ;; 2. Build observations for LLM tanks + ask tanks with [is-llm? and tank-health > 0] [ + set my-observation build-observation + ] + + ;; 3. Communicate (Stage 3 only) + if current-stage = 3 and communication-enabled? [ + ask tanks with [is-llm? and tank-health > 0] [ generate-message ] + deliver-messages + ] + + ;; 4. Decide actions + ask tanks with [is-llm? and tank-health > 0] [ + choose-action + ] + ask tanks with [not is-llm? and tank-health > 0] [ + bot-decide + ] + + ;; 5. Execute actions + ask tanks with [tank-health > 0] [ + execute-action last-action + ] + + ;; 6. Move bullets and resolve collisions + move-bullets + check-collisions + + ;; 7. Update explosions + update-explosions + + ;; 8. Check win condition + check-win-condition + + tick +end + +;; ============================================================ +;; OBSERVATION BUILDER +;; ============================================================ +to-report build-observation + let obs "" + + ;; Ego info + set obs (word obs "You are at (" xcor "," ycor ") facing " direction-name tank-direction + " with " tank-health " HP.\n") + + ;; Terrain ahead (5 patches) + set obs (word obs "Ahead: ") + let i 1 + while [i <= 5] [ + let p patch-ahead i + if p != nobody [ + set obs (word obs "[" i ":" [terrain-type] of p "]") + ] + set i i + 1 + ] + set obs (word obs "\n") + + ;; 7x7 ASCII minimap centered on self + set obs (word obs "Map (7x7, you=X):\n") + let my-x pxcor + let my-y pycor + let dy 3 + while [dy >= -3] [ + let dx -3 + while [dx <= 3] [ + let p patch-at dx dy + ifelse p = nobody [ + set obs (word obs "#") + ] [ + ifelse dx = 0 and dy = 0 [ + set obs (word obs "X") + ] [ + let ch map-char p + set obs (word obs ch) + ] + ] + set dx dx + 1 + ] + set obs (word obs "\n") + set dy dy - 1 + ] + + ;; Visible enemies (in-cone 8 90) + let visible-enemies other tanks with [tank-health > 0 and team-id != [team-id] of myself] in-cone 8 90 + ifelse any? visible-enemies [ + set obs (word obs "Enemies visible: ") + ask visible-enemies [ + set obs (word obs "(" xcor "," ycor ") ") + ] + set obs (word obs "\n") + ] [ + set obs (word obs "No enemies visible.\n") + ] + + ;; Ally info (stages 2-3) + if current-stage >= 2 [ + let allies other tanks with [tank-health > 0 and team-id = [team-id] of myself] + if any? allies [ + set obs (word obs "Allies: ") + ask allies [ + set obs (word obs "(" xcor "," ycor " dir=" direction-name tank-direction " hp=" tank-health ") ") + ] + set obs (word obs "\n") + ] + ] + + ;; Ally message (stage 3) + if current-stage = 3 and communication-enabled? and ally-message != "" [ + set obs (word obs "Ally says: \"" ally-message "\"\n") + ] + + ;; Stage-specific objective + if current-stage = 1 [ + ;; Distance to flag + let flag-target one-of flags + if flag-target != nobody [ + set obs (word obs "Objective: Reach the flag at (" [xcor] of flag-target "," [ycor] of flag-target "). " + "Distance: " precision distance flag-target 1 "\n") + ] + ] + if current-stage >= 2 [ + set obs (word obs "Objective: Destroy enemy base and tanks. Your base HP: " + (ifelse-value (team-id = 1) [red-base-hp] [blue-base-hp]) + " Enemy base HP: " + (ifelse-value (team-id = 1) [blue-base-hp] [red-base-hp]) "\n") + ] + + ;; Last action result + if last-action != "none" [ + set obs (word obs "Last action: " last-action "\n") + ] + + report obs +end + +;; Map character for ASCII minimap +to-report map-char [p] + let tt [terrain-type] of p + if tt = "brick" [ report "W" ] + if tt = "steel" [ report "S" ] + if tt = "water" [ report "~" ] + if tt = "base-red" [ report "R" ] + if tt = "base-blue" [ report "B" ] + if tt = "flag" [ report "F" ] + + ;; Check for tanks/bullets on patch + if any? tanks-on p with [tank-health > 0 and team-id != [team-id] of myself] [ report "E" ] + if any? tanks-on p with [tank-health > 0 and team-id = [team-id] of myself and self != myself] [ report "A" ] + + report "." +end + +;; Direction name helper +to-report direction-name [dir] + if dir = 0 [ report "North" ] + if dir = 90 [ report "East" ] + if dir = 180 [ report "South" ] + if dir = 270 [ report "West" ] + report "North" +end + +;; ============================================================ +;; ACTION SELECTION — LLM (placeholder, filled in Phase 2) +;; ============================================================ +to choose-action + ;; Phase 1: random action for testing game engine + ;; Will be replaced with llm:choose in Phase 2 + let actions ["move-forward" "turn-left" "turn-right" "fire" "stay"] + set last-action one-of actions +end + +;; ============================================================ +;; MESSAGE GENERATION — placeholder for Phase 3 +;; ============================================================ +to generate-message + ;; Will be replaced with llm:chat-with-template in Phase 3 + set my-message "" +end + +to deliver-messages + ;; Swap messages between allied LLM tanks + let blue-llm-tanks sort tanks with [is-llm? and team-id = 2 and tank-health > 0] + if length blue-llm-tanks = 2 [ + let t1 item 0 blue-llm-tanks + let t2 item 1 blue-llm-tanks + let msg1 [my-message] of t1 + let msg2 [my-message] of t2 + ask t1 [ + set ally-message msg2 + if msg2 != "" [ set message-timer 3 ] + ] + ask t2 [ + set ally-message msg1 + if msg1 != "" [ set message-timer 3 ] + ] + ] +end + +;; ============================================================ +;; BOT AI — State machine: patrol → chase → attack → retreat +;; ============================================================ +to bot-decide + ;; Retreat if low health + if tank-health = 1 [ + bot-retreat + stop + ] + + ;; Attack: enemy aligned on same row/col within 6 patches + let target-enemy nobody + ;; Check in current facing direction + let ahead-patches patches-ahead-n 6 + let enemies-ahead tanks with [tank-health > 0 and team-id != [team-id] of myself and member? patch-here ahead-patches] + if any? enemies-ahead [ + set last-action "fire" + stop + ] + + ;; Chase: enemy visible in cone + let visible-enemies other tanks with [tank-health > 0 and team-id != [team-id] of myself] in-cone 8 120 + if any? visible-enemies [ + let nearest min-one-of visible-enemies [distance myself] + bot-chase nearest + stop + ] + + ;; Default: patrol + bot-patrol +end + +to bot-retreat + ;; Move toward own base + let target-x spawn-x + let target-y spawn-y + let desired-heading towardsxy target-x target-y + let desired-dir round-to-cardinal desired-heading + ifelse desired-dir != tank-direction [ + set last-action ifelse-value (turn-direction-for desired-dir = "left") ["turn-left"] ["turn-right"] + ] [ + ifelse can-move-forward? [ + set last-action "move-forward" + ] [ + set last-action one-of ["turn-left" "turn-right"] + ] + ] +end + +to bot-chase [enemy] + let desired-heading towards enemy + let desired-dir round-to-cardinal desired-heading + ifelse desired-dir != tank-direction [ + set last-action ifelse-value (turn-direction-for desired-dir = "left") ["turn-left"] ["turn-right"] + ] [ + ;; If aligned, fire; otherwise move forward + ifelse aligned-with? enemy [ + set last-action "fire" + ] [ + ifelse can-move-forward? [ + set last-action "move-forward" + ] [ + set last-action one-of ["turn-left" "turn-right"] + ] + ] + ] +end + +to bot-patrol + ;; Simple patrol: move forward, turn randomly when blocked + ifelse can-move-forward? [ + ;; Occasionally turn for variety + ifelse random 100 < 15 [ + set last-action one-of ["turn-left" "turn-right"] + ] [ + set last-action "move-forward" + ] + ] [ + set last-action one-of ["turn-left" "turn-right"] + ] + + ;; Occasionally fire while patrolling + if random 100 < 10 [ + set last-action "fire" + ] +end + +;; Check if enemy is aligned on same row/col in facing direction +to-report aligned-with? [enemy] + let ex [xcor] of enemy + let ey [ycor] of enemy + if tank-direction = 0 and pxcor = [pxcor] of enemy and ey > ycor [ report true ] + if tank-direction = 180 and pxcor = [pxcor] of enemy and ey < ycor [ report true ] + if tank-direction = 90 and pycor = [pycor] of enemy and ex > xcor [ report true ] + if tank-direction = 270 and pycor = [pycor] of enemy and ex < xcor [ report true ] + report false +end + +;; Patches ahead in facing direction, up to n +to-report patches-ahead-n [n] + let result patch-set nobody + let i 1 + while [i <= n] [ + let p patch-ahead i + if p != nobody [ set result (patch-set result p) ] + set i i + 1 + ] + report result +end + +;; Round heading to nearest cardinal direction +to-report round-to-cardinal [h] + ;; Normalize to 0-359 + set h h mod 360 + if h < 0 [ set h h + 360 ] + if h >= 315 or h < 45 [ report 0 ] + if h >= 45 and h < 135 [ report 90 ] + if h >= 135 and h < 225 [ report 180 ] + report 270 +end + +;; Determine whether to turn left or right to reach desired direction +to-report turn-direction-for [desired-dir] + let diff (desired-dir - tank-direction) mod 360 + if diff < 0 [ set diff diff + 360 ] + ifelse diff <= 180 + [ report "right" ] + [ report "left" ] +end + +;; Check if tank can move one patch forward +to-report can-move-forward? + let p patch-ahead 1 + if p = nobody [ report false ] + if [terrain-type] of p = "brick" [ report false ] + if [terrain-type] of p = "steel" [ report false ] + if [terrain-type] of p = "water" [ report false ] + if any? other tanks-on p with [tank-health > 0] [ report false ] + report true +end + +;; ============================================================ +;; ACTION EXECUTION +;; ============================================================ +to execute-action [action-name] + if action-name = "move-forward" [ + if can-move-forward? [ + fd 1 + ] + stop + ] + if action-name = "turn-left" [ + set tank-direction (tank-direction - 90) mod 360 + if tank-direction < 0 [ set tank-direction tank-direction + 360 ] + set heading tank-direction + stop + ] + if action-name = "turn-right" [ + set tank-direction (tank-direction + 90) mod 360 + set heading tank-direction + stop + ] + if action-name = "fire" [ + if fire-cooldown <= 0 [ + fire-bullet + set fire-cooldown 3 + ] + stop + ] + ;; "stay" or unknown: do nothing +end + +;; ============================================================ +;; BULLET MECHANICS +;; ============================================================ +to fire-bullet + hatch-bullets 1 [ + set shape "circle" + set size 0.5 + set color yellow + set heading [tank-direction] of myself + set bullet-team [team-id] of myself + set bullet-direction [tank-direction] of myself + set bullet-distance 0 + ] +end + +to move-bullets + ask bullets [ + ;; Bullets move 2 patches per tick + let steps 0 + while [steps < 2] [ + let next-p patch-ahead 1 + ifelse next-p = nobody [ + die ;; hit world edge + stop + ] [ + ;; Check for wall collision + let tt [terrain-type] of next-p + if tt = "steel" [ + ;; Bounce off steel, destroy bullet + spawn-explosion xcor ycor + die + stop + ] + if tt = "brick" [ + ;; Damage brick wall + ask next-p [ + set wall-hp wall-hp - 1 + ifelse wall-hp <= 0 [ + set terrain-type "open" + set pcolor gray - 3 + ] [ + set pcolor brown + 3 ;; damaged wall is lighter + ] + ] + spawn-explosion [pxcor] of next-p [pycor] of next-p + die + stop + ] + + ;; Check for tank collision + let hit-tanks tanks-on next-p with [tank-health > 0 and team-id != [bullet-team] of myself] + if any? hit-tanks [ + ask one-of hit-tanks [ + set tank-health tank-health - 1 + if tank-health <= 0 [ + ;; Tank destroyed + ifelse team-id = 1 + [ set blue-score blue-score + 1 ] + [ set red-score red-score + 1 ] + spawn-explosion xcor ycor + set shape "x" + set size 1 + set color gray + ] + ] + spawn-explosion [pxcor] of next-p [pycor] of next-p + die + stop + ] + + ;; Check for base collision + if tt = "base-red" or tt = "base-blue" [ + let bt [base-team] of next-p + if bt != bullet-team [ + ask next-p [ + set base-hp base-hp - 1 + ifelse bt = 1 + [ set red-base-hp red-base-hp - 1 ] + [ set blue-base-hp blue-base-hp - 1 ] + if base-hp <= 0 [ + set terrain-type "open" + set pcolor gray - 3 + ] + ] + ;; Score for base damage + ifelse bt = 1 + [ set blue-score blue-score + 3 ] + [ set red-score red-score + 3 ] + spawn-explosion [pxcor] of next-p [pycor] of next-p + die + stop + ] + ] + + ;; Move bullet forward + fd 1 + set bullet-distance bullet-distance + 1 + + ;; Despawn after 15 patches + if bullet-distance >= 15 [ + die + stop + ] + ] + set steps steps + 1 + ] + ] +end + +to check-collisions + ;; Stage 1: check if LLM tank reached flag + if current-stage = 1 [ + ask tanks with [is-llm? and tank-health > 0] [ + if any? flags-here [ + set game-over? true + set winner "Blue (LLM)" + output-print "LLM tank reached the flag!" + ] + ] + ] +end + +;; ============================================================ +;; EXPLOSIONS +;; ============================================================ +to spawn-explosion [ex ey] + create-explosions 1 [ + setxy ex ey + set shape "star" + set color orange + set size 1.0 + set explosion-timer 3 + ] +end + +to update-explosions + ask explosions [ + set explosion-timer explosion-timer - 1 + ;; Pulse size + set size 1.0 + (3 - explosion-timer) * 0.3 + set color orange - (3 - explosion-timer) + if explosion-timer <= 0 [ die ] + ] +end + +;; ============================================================ +;; WIN CONDITION CHECKS +;; ============================================================ +to check-win-condition + if current-stage = 1 [ stop ] ;; handled in check-collisions + + ;; Base destroyed + if red-base-hp <= 0 [ + set game-over? true + set winner "Blue" + output-print "Blue wins! Red base destroyed." + ] + if blue-base-hp <= 0 [ + set game-over? true + set winner "Red" + output-print "Red wins! Blue base destroyed." + ] + + ;; All enemy tanks eliminated + let red-alive count tanks with [team-id = 1 and tank-health > 0] + let blue-alive count tanks with [team-id = 2 and tank-health > 0] + if red-alive = 0 and blue-alive > 0 [ + set game-over? true + set winner "Blue" + output-print "Blue wins! All red tanks eliminated." + ] + if blue-alive = 0 and red-alive > 0 [ + set game-over? true + set winner "Red" + output-print "Red wins! All blue tanks eliminated." + ] +end + +to resolve-timeout + set game-over? true + ifelse red-score > blue-score [ + set winner "Red" + output-print (word "Time's up! Red wins " red-score " to " blue-score) + ] [ + ifelse blue-score > red-score [ + set winner "Blue" + output-print (word "Time's up! Blue wins " blue-score " to " red-score) + ] [ + set winner "Draw" + output-print (word "Time's up! Draw at " red-score " each") + ] + ] +end + +;; ============================================================ +;; REPORTERS for monitors +;; ============================================================ +to-report red-score-report + report red-score +end + +to-report blue-score-report + report blue-score +end + +to-report llm-calls-report + report total-llm-calls +end + +to-report winner-report + report winner +end + +to-report red-base-hp-report + report red-base-hp +end + +to-report blue-base-hp-report + report blue-base-hp +end + +to-report game-status + if game-over? [ report (word "GAME OVER: " winner " wins!") ] + report (word "Stage " current-stage " | Tick " ticks "/" max-ticks) +end +]]> + + + + + + + + + + + + + + + + + + + + red-score-report + blue-score-report + + red-base-hp-report + blue-base-hp-report + + llm-calls-report + ticks + + game-status + winner-report + + + plot red-score + plot blue-score + + + + plot total-llm-calls + + + + + From c43d4338f75978df791bed29408bcdcef8b04ec3 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Tue, 17 Mar 2026 18:47:23 -0500 Subject: [PATCH 02/14] feat: add LLM integration for action selection via llm:choose Replace random action stub with llm:choose that sends tank observations (position, minimap, enemies, objectives) and picks from [move-forward, turn-left, turn-right, fire, stay]. Adds config.txt (openai/gpt-4o-mini/0.3), llm:load-config in setup, llm:clear-history per decision, error handling with carefully block, and LLM call counter. --- .../battle-city-tank-arena.nlogox | 21 ++++++++++++++----- demos/battle-city-tank-arena/config.txt | 3 +++ 2 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 demos/battle-city-tank-arena/config.txt diff --git a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox index 071aed5..590fe74 100644 --- a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox +++ b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox @@ -82,6 +82,9 @@ to setup ;; Communication only in stage 3 (or overridden) set communication-enabled? (current-stage = 3 and comms-override?) + ;; Load LLM config + llm:load-config "config.txt" + ;; Build the arena map build-arena-map setup-map @@ -498,13 +501,21 @@ to-report direction-name [dir] end ;; ============================================================ -;; ACTION SELECTION — LLM (placeholder, filled in Phase 2) +;; ACTION SELECTION — LLM via llm:choose ;; ============================================================ to choose-action - ;; Phase 1: random action for testing game engine - ;; Will be replaced with llm:choose in Phase 2 - let actions ["move-forward" "turn-left" "turn-right" "fire" "stay"] - set last-action one-of actions + set total-llm-calls total-llm-calls + 1 + carefully [ + llm:clear-history + let action llm:choose my-observation ["move-forward" "turn-left" "turn-right" "fire" "stay"] + set last-action action + if show-observations? [ + output-print (word "Tank " who " chose: " action) + ] + ] [ + set last-action "stay" + output-print (word "LLM error for tank " who ": " error-message) + ] end ;; ============================================================ diff --git a/demos/battle-city-tank-arena/config.txt b/demos/battle-city-tank-arena/config.txt new file mode 100644 index 0000000..b0a3ba5 --- /dev/null +++ b/demos/battle-city-tank-arena/config.txt @@ -0,0 +1,3 @@ +provider=openai +model=gpt-4o-mini +temperature=0.3 From 222a781ef670842eaf79a675550674697a9ab596 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Tue, 17 Mar 2026 18:48:42 -0500 Subject: [PATCH 03/14] feat: add Stage 3 communication and show-thinking mode Stage 3 inter-tank messaging via llm:chat-with-template with message-template.yaml. Ally messages injected into decision context via llm:set-history before llm:choose. Speech bubbles via label/message-timer. show-thinking? toggle uses llm:chat-with-thinking to expose reasoning chains. Adds action-template.yaml for thinking mode fallback parsing. --- .../action-template.yaml | 9 +++ .../battle-city-tank-arena.nlogox | 67 +++++++++++++++++-- .../message-template.yaml | 11 +++ 3 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 demos/battle-city-tank-arena/action-template.yaml create mode 100644 demos/battle-city-tank-arena/message-template.yaml diff --git a/demos/battle-city-tank-arena/action-template.yaml b/demos/battle-city-tank-arena/action-template.yaml new file mode 100644 index 0000000..fcb1ef6 --- /dev/null +++ b/demos/battle-city-tank-arena/action-template.yaml @@ -0,0 +1,9 @@ +system: | + You are a tank commander in a Battle City arena. Analyze your situation and pick the best action. + You MUST respond with EXACTLY one of the allowed actions, nothing else. + Allowed actions: move-forward, turn-left, turn-right, fire, stay + +template: | + {observation} + + Choose your action. Respond with EXACTLY one action name from: move-forward, turn-left, turn-right, fire, stay diff --git a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox index 590fe74..5184ddd 100644 --- a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox +++ b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox @@ -501,16 +501,41 @@ to-report direction-name [dir] end ;; ============================================================ -;; ACTION SELECTION — LLM via llm:choose +;; ACTION SELECTION — LLM via llm:choose (or llm:chat-with-thinking) ;; ============================================================ to choose-action set total-llm-calls total-llm-calls + 1 carefully [ llm:clear-history - let action llm:choose my-observation ["move-forward" "turn-left" "turn-right" "fire" "stay"] - set last-action action + + ;; Stage 3: inject ally message into history context before deciding + if current-stage = 3 and communication-enabled? and ally-message != "" [ + llm:set-history (list + (list "user" (word "Your ally sent this message: \"" ally-message "\"")) + (list "assistant" "Understood, I'll factor that into my decision.") + ) + ] + + ifelse show-thinking? [ + ;; Use chat-with-thinking to expose reasoning chain + let prompt (word my-observation "\nChoose EXACTLY one action: move-forward, turn-left, turn-right, fire, stay") + let response llm:chat-with-thinking prompt + let action-text item 0 response + let thinking-text item 1 response + if thinking-text != "" [ + output-print (word "--- Tank " who " thinking ---") + output-print thinking-text + output-print "---" + ] + set last-action parse-action-response action-text + ] [ + ;; Standard llm:choose + let action llm:choose my-observation ["move-forward" "turn-left" "turn-right" "fire" "stay"] + set last-action action + ] + if show-observations? [ - output-print (word "Tank " who " chose: " action) + output-print (word "Tank " who " chose: " last-action) ] ] [ set last-action "stay" @@ -518,12 +543,40 @@ to choose-action ] end +;; Parse action from free-text response (for show-thinking mode) +to-report parse-action-response [response] + let r lower-case (word response) + if position "move-forward" r != false [ report "move-forward" ] + if position "turn-left" r != false [ report "turn-left" ] + if position "turn-right" r != false [ report "turn-right" ] + if position "fire" r != false [ report "fire" ] + if position "stay" r != false [ report "stay" ] + report "stay" +end + ;; ============================================================ -;; MESSAGE GENERATION — placeholder for Phase 3 +;; MESSAGE GENERATION — Stage 3 communication via llm:chat-with-template ;; ============================================================ to generate-message - ;; Will be replaced with llm:chat-with-template in Phase 3 - set my-message "" + set total-llm-calls total-llm-calls + 1 + carefully [ + llm:clear-history + let vars (list + (list "observation" my-observation) + ) + let msg llm:chat-with-template "message-template.yaml" vars + ;; Truncate to keep messages brief + if length msg > 80 [ set msg substring msg 0 80 ] + set my-message msg + set label msg + set message-timer 3 + if show-observations? [ + output-print (word "Tank " who " says: " msg) + ] + ] [ + set my-message "" + output-print (word "Message error for tank " who ": " error-message) + ] end to deliver-messages diff --git a/demos/battle-city-tank-arena/message-template.yaml b/demos/battle-city-tank-arena/message-template.yaml new file mode 100644 index 0000000..9d80e74 --- /dev/null +++ b/demos/battle-city-tank-arena/message-template.yaml @@ -0,0 +1,11 @@ +system: | + You are a tank commander communicating with your ally in a Battle City arena. + Send a brief tactical message (one sentence max) about what you see and plan to do. + Focus on: enemy positions, threats, suggested coordination, or requests for help. + Be concise — your ally needs actionable intel, not essays. + +template: | + Your situation: + {observation} + + Write one short tactical message to your ally. From c33f83768a56af911286acb5e0db0fb6c61f5ef1 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Tue, 17 Mar 2026 18:49:09 -0500 Subject: [PATCH 04/14] docs: add README with hypothesis, setup, and expected results Documents the BattleAgentBench communication paradox hypothesis, three stages, setup instructions with config placeholder, expected results per model tier, and all LLM primitives used. --- demos/battle-city-tank-arena/README.md | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 demos/battle-city-tank-arena/README.md diff --git a/demos/battle-city-tank-arena/README.md b/demos/battle-city-tank-arena/README.md new file mode 100644 index 0000000..2a0c463 --- /dev/null +++ b/demos/battle-city-tank-arena/README.md @@ -0,0 +1,59 @@ +# Battle City Tank Arena + +A NetLogo LLM extension demo that replicates the key finding from [BattleAgentBench](https://arxiv.org/abs/2408.15971): **communication between LLM agents hurts most models — only strong models benefit from messaging**. + +## Hypothesis + +When LLM-controlled tanks can send tactical messages to allies: +- **Weak models** (gpt-4o-mini, etc.) perform *worse* due to message noise, hallucinated coordination, and context overload +- **Strong models** (gpt-4o, claude-3.5-sonnet, etc.) perform *better* by leveraging ally intel for coordinated tactics + +## Stages + +| Stage | Setup | What It Tests | +|-------|-------|---------------| +| 1: Solo Navigation | 1 LLM tank reaches a flag | Spatial reasoning with `llm:choose` | +| 2: Team Battle | 2 LLM tanks vs 2 bots (no comms) | Implicit cooperation via `llm:choose` | +| 3: Team + Comms | Same + messaging channel | Communication paradox via `llm:choose` + `llm:chat-with-template` + `llm:set-history` | + +## Setup + +1. Install the LLM extension in NetLogo 7.0.3+ +2. Edit `config.txt` with your provider and API key: + ``` + provider=openai + model=gpt-4o-mini + temperature=0.3 + api_key=YOUR_KEY_HERE + ``` +3. Open `battle-city-tank-arena.nlogox` in NetLogo +4. Select a stage, click Setup, then Go + +## Running the Experiment + +1. Set stage to "2: Team Battle", click Setup → Go. Note the final score. +2. Set stage to "3: Team + Comms", click Setup → Go. Compare scores. +3. Toggle `comms-override?` mid-run to see the effect live. +4. Change `config.txt` to a stronger model (e.g., `gpt-4o`) and repeat. + +## Expected Results + +- **gpt-4o-mini**: Stage 3 (with comms) performs *equal or worse* than Stage 2 +- **gpt-4o / claude-3.5-sonnet**: Stage 3 performs *better* than Stage 2 +- `show-thinking?` reveals the reasoning chain differences + +## LLM Primitives Used + +- `llm:load-config` — Load provider/model settings +- `llm:choose` — Select action from fixed options given observations +- `llm:clear-history` — Fresh context each decision tick +- `llm:chat-with-template` — Generate tactical messages (Stage 3) +- `llm:set-history` — Inject ally messages into decision context +- `llm:chat-with-thinking` — Expose reasoning chains (show-thinking mode) + +## Files + +- `battle-city-tank-arena.nlogox` — Main model +- `config.txt` — LLM provider configuration +- `action-template.yaml` — Action decision prompt (thinking mode) +- `message-template.yaml` — Communication prompt (Stage 3) From 87b3004c4d3a53d13b304b0b1b0cff69c9884f74 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Tue, 17 Mar 2026 18:52:32 -0500 Subject: [PATCH 05/14] fix: correct arena map strings to exactly 31 chars each All 31 row strings now verified at exactly 31 characters to match the 31x31 world grid. Removed unused variable. --- .../battle-city-tank-arena.nlogox | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox index 5184ddd..12585e4 100644 --- a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox +++ b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox @@ -114,39 +114,39 @@ to build-arena-map ;; 31x31 symmetric arena inspired by Battle City ;; Legend: . = open, W = brick wall, S = steel, R = red base, B = blue base, ~ = water ;; Row 0 is bottom, Row 30 is top + ;; Each row is exactly 31 characters (columns 0-30, mapping to x: -15 to +15) set arena-map (list - ;; Row 0 (bottom) - Red base area - "...............R..............." ;; 0 - "...............R..............." ;; 1 - "..WWW...WWW.........WWW...WWW.." ;; 2 - "..W.W...W.W...WWW...W.W...W.W.." ;; 3 - "..WWW...WWW...W.W...WWW...WWW.." ;; 4 - "..............WWW.............." ;; 5 - "..WW..WWWW..........WWWW..WW.." ;; 6 - "..WW..W..............W....WW.." ;; 7 - "......W....SS....SS...W......." ;; 8 - "..WWW......SS....SS......WWW.." ;; 9 - "..W........SS....SS........W.." ;; 10 - "..W....WW............WW....W.." ;; 11 - "..W....WW............WW....W.." ;; 12 - ".......WW....WWWW...WW........" ;; 13 - "..SS.........W..W.........SS.." ;; 14 - "..SS.........W..W.........SS.." ;; 15 center - "..SS.........WWWW.........SS.." ;; 16 - ".......WW............WW......." ;; 17 - "..W....WW............WW....W.." ;; 18 - "..W....WW............WW....W.." ;; 19 - "..W........SS....SS........W.." ;; 20 - "..WWW......SS....SS......WWW.." ;; 21 - "......W....SS....SS...W......." ;; 22 - "..WW..W..............W....WW.." ;; 23 - "..WW..WWWW..........WWWW..WW.." ;; 24 - "..............WWW.............." ;; 25 - "..WWW...WWW...W.W...WWW...WWW.." ;; 26 - "..W.W...W.W...WWW...W.W...W.W.." ;; 27 - "..WWW...WWW.........WWW...WWW.." ;; 28 - "...............B..............." ;; 29 - "...............B..............." ;; 30 + "...............R..............." + "...............R..............." + "..WWW....WWW.......WWW....WWW.." + "..W.W....W.W..WWW..W.W....W.W.." + "..WWW....WWW..W.W..WWW....WWW.." + "..............WWW.............." + "..WW...WWWW.........WWWW...WW.." + "..WW...W.............W.....WW.." + "......W....SS...SS....W........" + "..WWW......SS...SS........WWW.." + "..W........SS...SS..........W.." + "..W....WW.............WW....W.." + "..W....WW.............WW....W.." + ".......WW....WWWWW....WW......." + "..SS.........W...W.........SS.." + "..SS.........W...W.........SS.." + "..SS.........WWWWW.........SS.." + ".......WW.............WW......." + "..W....WW.............WW....W.." + "..W....WW.............WW....W.." + "..W........SS...SS..........W.." + "..WWW......SS...SS........WWW.." + "......W....SS...SS....W........" + "..WW...W.............W.....WW.." + "..WW...WWWW.........WWWW...WW.." + "..............WWW.............." + "..WWW....WWW..W.W..WWW....WWW.." + "..W.W....W.W..WWW..W.W....W.W.." + "..WWW....WWW.......WWW....WWW.." + "...............B..............." + "...............B..............." ) end @@ -167,7 +167,6 @@ to setup-map let row-idx 0 foreach arena-map [ row-str -> let col-idx 0 - let chars-remaining row-str while [col-idx < 31 and col-idx < length row-str] [ let ch item col-idx row-str let px col-idx - 15 From f3a61aa096677b3bbd526696002f9200cc743add Mon Sep 17 00:00:00 2001 From: JNK234 Date: Tue, 17 Mar 2026 23:47:37 -0500 Subject: [PATCH 06/14] fix: use correct wrappingAllowedX/Y attribute names in view widget --- demos/battle-city-tank-arena/battle-city-tank-arena.nlogox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox index 12585e4..0cf3095 100644 --- a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox +++ b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox @@ -996,7 +996,7 @@ to-report game-status end ]]> - + From f0c0b5a8efe4ac290fba5806ef5cd0243f1f1311 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Tue, 17 Mar 2026 23:57:47 -0500 Subject: [PATCH 07/14] fix: remove nonexistent lower-case primitive, fix Y-axis inversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1: lower-case is not a NetLogo 7 primitive. Replace with multi-case position checks for action parsing. Bug 2: Map Y-axis was inverted — teams spawned at enemy bases. Change py = row-idx - 15 to py = 15 - row-idx so Red base (R) renders at top (y=+15) near red tank spawns (y=+13) and Blue base (B) at bottom (y=-15) near blue tank spawns (y=-13). --- .../battle-city-tank-arena.nlogox | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox index 0cf3095..5f87d08 100644 --- a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox +++ b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox @@ -113,7 +113,7 @@ end to build-arena-map ;; 31x31 symmetric arena inspired by Battle City ;; Legend: . = open, W = brick wall, S = steel, R = red base, B = blue base, ~ = water - ;; Row 0 is bottom, Row 30 is top + ;; Row 0 maps to y=+15 (top), Row 30 maps to y=-15 (bottom) ;; Each row is exactly 31 characters (columns 0-30, mapping to x: -15 to +15) set arena-map (list "...............R..............." @@ -170,7 +170,7 @@ to setup-map while [col-idx < 31 and col-idx < length row-str] [ let ch item col-idx row-str let px col-idx - 15 - let py row-idx - 15 + let py 15 - row-idx if px >= min-pxcor and px <= max-pxcor and py >= min-pycor and py <= max-pycor [ ask patch px py [ @@ -544,12 +544,18 @@ end ;; Parse action from free-text response (for show-thinking mode) to-report parse-action-response [response] - let r lower-case (word response) + ;; NetLogo has no built-in lower-case, so match common casings + let r (word response) if position "move-forward" r != false [ report "move-forward" ] + if position "Move-forward" r != false [ report "move-forward" ] if position "turn-left" r != false [ report "turn-left" ] + if position "Turn-left" r != false [ report "turn-left" ] if position "turn-right" r != false [ report "turn-right" ] + if position "Turn-right" r != false [ report "turn-right" ] if position "fire" r != false [ report "fire" ] + if position "Fire" r != false [ report "fire" ] if position "stay" r != false [ report "stay" ] + if position "Stay" r != false [ report "stay" ] report "stay" end From 4ce426533ff6884811afa4649b4ecc3d7a0c567a Mon Sep 17 00:00:00 2001 From: JNK234 Date: Tue, 17 Mar 2026 23:58:48 -0500 Subject: [PATCH 08/14] fix: use integer updateMode value in view widget --- demos/battle-city-tank-arena/battle-city-tank-arena.nlogox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox index 5f87d08..25f0114 100644 --- a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox +++ b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox @@ -1002,7 +1002,7 @@ to-report game-status end ]]> - + From ec72fb082a748fd17abadddd4f63df22f2be2167 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Tue, 17 Mar 2026 23:59:32 -0500 Subject: [PATCH 09/14] fix: add required showTickCounter and tickCounterLabel to view widget --- demos/battle-city-tank-arena/battle-city-tank-arena.nlogox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox index 25f0114..dd7131f 100644 --- a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox +++ b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox @@ -1002,7 +1002,7 @@ to-report game-status end ]]> - + From fd5e30f42f90c802c44881e6b472940f45c0d49d Mon Sep 17 00:00:00 2001 From: JNK234 Date: Wed, 18 Mar 2026 00:02:35 -0500 Subject: [PATCH 10/14] fix: rewrite widgets to match NetLogo 7.0.3 nlogox format - Plot pens use with / children instead of inline text content - Chooser uses instead of - @@ -1053,7 +1066,876 @@ end plot total-llm-calls - + ## WHAT IS IT? + +(a general understanding of what the model is trying to show or explain) + +## HOW IT WORKS + +(what rules the agents use to create the overall behavior of the model) + +## HOW TO USE IT + +(how to use the model, including a description of each of the items in the Interface tab) + +## THINGS TO NOTICE + +(suggested things for the user to notice while running the model) + +## THINGS TO TRY + +(suggested things for the user to try to do (move sliders, switches, etc.) with the model) + +## EXTENDING THE MODEL + +(suggested things to add or change in the Code tab to make the model more complicated, detailed, accurate, etc.) + +## NETLOGO FEATURES + +(interesting or unusual features of NetLogo that the model uses, particularly in the Code tab; or where workarounds were needed for missing features) + +## RELATED MODELS + +(models in the NetLogo Models Library and elsewhere which are of related interest) + +## CREDITS AND REFERENCES + +(a reference to the model's URL on the web if it has one, as well as any other necessary credits, citations, and links) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setup repeat 75 [ go ]