From bf30a3df5fccfd00881de4605c8b98032998f47f Mon Sep 17 00:00:00 2001 From: JNK234 Date: Wed, 18 Mar 2026 03:03:33 -0500 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20scaffold=20social=20deduction=20gam?= =?UTF-8?q?e=20=E2=80=94=20config,=20template,=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the supporting files for the Undercover/Spy Words social deduction game demo: Ollama config, description-phase prompt template, and README with hypothesis, game mechanics, and setup instructions. --- .gitignore | 1 + demos/social-deduction-game/README.md | 57 +++++++++++++++++++ demos/social-deduction-game/config.txt | 5 ++ .../description-template.yaml | 15 +++++ 4 files changed, 78 insertions(+) create mode 100644 demos/social-deduction-game/README.md create mode 100644 demos/social-deduction-game/config.txt create mode 100644 demos/social-deduction-game/description-template.yaml diff --git a/.gitignore b/.gitignore index b1cf135..e5f71bd 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ commands/ config.txt config-*.txt !demos/provider-sensitivity/config-multi-provider.txt +!demos/social-deduction-game/config.txt *.env *.env.* *.secrets* diff --git a/demos/social-deduction-game/README.md b/demos/social-deduction-game/README.md new file mode 100644 index 0000000..89ea3b7 --- /dev/null +++ b/demos/social-deduction-game/README.md @@ -0,0 +1,57 @@ +# Social Deduction Game (Undercover / Spy Words) + +## Hypothesis + +LLM agents will correctly identify spies at below-chance accuracy because they struggle to integrate cross-round description patterns into coherent suspicion, while LLM spies will be caught faster than chance because they produce descriptions that are subtly misaligned with the majority word. + +## Source + +Inspired by LLMArena (ACL 2024) — social deduction games as an evaluation framework for LLM reasoning and theory of mind. + +## Game Mechanics + +1. **Setup**: N players arranged in a circle. One is randomly assigned the "spy word"; the rest share the "regular word". Words are similar but distinct (e.g., apple vs pear). +2. **Description Phase**: Each alive player describes their word in 1-2 sentences without saying it. +3. **Voting Phase**: Each alive player votes for who they think is the spy based on descriptions. +4. **Elimination**: The player with the most votes is eliminated. +5. **Win Condition**: Regulars win if the spy is eliminated. The spy wins if only 2 players remain or max rounds are reached. + +## Word Pairs (3 difficulty tiers) + +| Difficulty | Regular Word | Spy Word | +|-----------|-------------|----------| +| High (hard to detect) | apple/guitar/coffee/pillow/ocean | pear/ukulele/tea/cushion/lake | +| Medium | bicycle/painting/castle/penguin/candle | motorcycle/photograph/mansion/duck/flashlight | +| Low (easy to detect) | airplane/forest/piano/book/snowflake | submarine/desert/drums/television/raindrop | + +## Files + +| File | Purpose | +|------|---------| +| `social-deduction-game.nlogox` | Main NetLogo model | +| `config.txt` | LLM provider configuration (default: Ollama) | +| `description-template.yaml` | Prompt template for description phase | +| `README.md` | This file | + +## Setup + +1. Install the LLM extension (see main repo README) +2. Configure `config.txt` with your preferred provider +3. Open `social-deduction-game.nlogox` in NetLogo 7.0.3+ +4. Click **Setup** to initialize players +5. Click **Go** to run the full game, or **Step** for one round at a time + +## Interface Controls + +- **num-players**: Number of players (4-8, default 6) +- **max-rounds**: Maximum rounds before spy wins (3-10, default 6) +- **difficulty-level**: Word pair similarity ("low"/"medium"/"high") +- **show-descriptions?**: Display description speech bubbles +- **show-votes?**: Display vote targets +- **auto-reveal?**: Reveal spy identity when game ends + +## Expected Results + +- **Detection rate**: Expected below 1/N chance (where N = number of players) +- **Spy survival**: Spy descriptions tend to be subtly "off", leading to faster-than-chance detection in some rounds, but overall detection accuracy remains low because regulars struggle to synthesize cross-round patterns +- **Difficulty effect**: Higher difficulty (more similar words) should decrease detection rate diff --git a/demos/social-deduction-game/config.txt b/demos/social-deduction-game/config.txt new file mode 100644 index 0000000..ebb5e0f --- /dev/null +++ b/demos/social-deduction-game/config.txt @@ -0,0 +1,5 @@ +provider=ollama +model=llama3.2 +temperature=0.4 +max_tokens=80 +timeout_seconds=30 diff --git a/demos/social-deduction-game/description-template.yaml b/demos/social-deduction-game/description-template.yaml new file mode 100644 index 0000000..fb1434e --- /dev/null +++ b/demos/social-deduction-game/description-template.yaml @@ -0,0 +1,15 @@ +system: | + You are {player_name} in a word guessing social deduction game called Undercover. + You have a secret word. Most players share the same word, but one player (the spy) has a different but similar word. + Describe your word in 1-2 short sentences WITHOUT saying it directly. + Be specific enough to show knowledge but not so specific you reveal it to the spy. + Do NOT repeat descriptions from previous rounds. Be creative and varied. + +template: | + Your secret word is: {my_word} + This is round {round_number}. + + Descriptions from previous rounds: + {transcript} + + Give YOUR description for this round. 1-2 short sentences only. Do NOT say the word itself. From fa35ccd2d5544544276b14b0c6393f67e494d19d Mon Sep 17 00:00:00 2001 From: JNK234 Date: Wed, 18 Mar 2026 03:03:41 -0500 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20social=20deduction=20game=20?= =?UTF-8?q?=E2=80=94=20core=20game=20logic=20and=20.nlogox=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the full Undercover/Spy Words social deduction game with: - Circle layout for N players (4-8), one randomly assigned as spy - Description phase using llm:chat-with-template for word clues - Voting phase using llm:choose for constrained spy identification - Elimination with tie-breaking, win condition checks - 15 word pairs across 3 difficulty tiers - Cumulative stats tracking across games (detection rate, wins) - Vote plot and detection rate plot - Word leak protection and LLM error fallbacks --- .../social-deduction-game.nlogox | 587 ++++++++++++++++++ 1 file changed, 587 insertions(+) create mode 100644 demos/social-deduction-game/social-deduction-game.nlogox diff --git a/demos/social-deduction-game/social-deduction-game.nlogox b/demos/social-deduction-game/social-deduction-game.nlogox new file mode 100644 index 0000000..6b651e6 --- /dev/null +++ b/demos/social-deduction-game/social-deduction-game.nlogox @@ -0,0 +1,587 @@ + + + = 3 and (distancexy 0 0) <= 4] [ + set pcolor 3 + ] + + reset-ticks +end + +to init-word-pairs + set high-difficulty-pairs (list + (list "apple" "pear") + (list "guitar" "ukulele") + (list "coffee" "tea") + (list "pillow" "cushion") + (list "ocean" "lake") + ) + set medium-difficulty-pairs (list + (list "bicycle" "motorcycle") + (list "painting" "photograph") + (list "castle" "mansion") + (list "penguin" "duck") + (list "candle" "flashlight") + ) + set low-difficulty-pairs (list + (list "airplane" "submarine") + (list "forest" "desert") + (list "piano" "drums") + (list "book" "television") + (list "snowflake" "raindrop") + ) +end + +to-report get-pairs-for-difficulty + if difficulty-level = "high" [ report high-difficulty-pairs ] + if difficulty-level = "medium" [ report medium-difficulty-pairs ] + report low-difficulty-pairs +end + +;;; ========== MAIN GAME LOOP ========== + +to go + if game-over? [ stop ] + if not llm-config-loaded? [ + print "LLM config not loaded. Check config.txt." + stop + ] + step-round +end + +to step-round + if game-over? [ stop ] + if not llm-config-loaded? [ + print "LLM config not loaded. Check config.txt." + stop + ] + + set current-round current-round + 1 + print (word "===== Round " current-round " =====") + + ;; Description phase + run-description-phase + + ;; Voting phase + run-voting-phase + + ;; Elimination + run-elimination + + ;; Win check + check-win-condition + + tick +end + +;;; ========== DESCRIPTION PHASE ========== + +to run-description-phase + set current-phase "describing" + + let transcript-text full-transcript + if transcript-text = "" [ set transcript-text "No descriptions yet." ] + + ;; Each alive player generates a description + let alive-players sort-on [who] players with [alive?] + foreach alive-players [ p -> + ask p [ + llm:clear-history + let desc "" + carefully [ + set desc llm:chat-with-template "description-template.yaml" + (list "player_name" player-name "my_word" my-word + "round_number" (word current-round) "transcript" transcript-text) + set total-llm-calls total-llm-calls + 1 + ] [ + set desc "My word relates to something common and familiar." + print (word player-name " description error: " error-message) + ] + + ;; Word leak check: if description contains the actual word, replace + if member? my-word desc [ + set desc "My word relates to something common and familiar." + ] + + set my-description desc + + ;; Update transcript + set full-transcript (word full-transcript "Round " current-round " - " player-name ": " desc "\n") + + ;; Update label + if show-descriptions? [ + set label (word player-name ": " truncate-string desc 40) + ] + + if show-descriptions? [ + print (word player-name ": " desc) + ] + ] + ] +end + +;;; ========== VOTING PHASE ========== + +to run-voting-phase + set current-phase "voting" + + ;; Reset vote counts + ask players with [alive?] [ + set vote-count 0 + ] + + let alive-players sort-on [who] players with [alive?] + foreach alive-players [ p -> + ask p [ + ;; Build list of other alive player names + let others-names map [q -> [player-name] of q] filter [q -> q != self] alive-players + + ;; Build vote prompt + let vote-prompt (word + "You are " player-name " in a social deduction game called Undercover.\n" + "Your secret word is: " my-word "\n" + "Most players share the same word, but ONE player (the spy) has a different similar word.\n" + "The spy's descriptions will be subtly different because they are describing a different word.\n\n" + "Here are all descriptions so far:\n" + full-transcript "\n" + "Based on the descriptions, which player do you think is the SPY?\n" + "Look for descriptions that seem slightly off or describe a different concept.\n" + "Vote for the player you find most suspicious.") + + llm:clear-history + let vote-name "" + carefully [ + set vote-name llm:choose vote-prompt others-names + set total-llm-calls total-llm-calls + 1 + ] [ + set vote-name one-of others-names + print (word player-name " vote error: " error-message) + ] + + set my-vote vote-name + + ;; Increment the target's vote count + let target one-of players with [player-name = vote-name and alive?] + if target != nobody [ + ask target [ + set vote-count vote-count + 1 + set total-votes-received total-votes-received + 1 + ] + ] + + if show-votes? [ + set label (word player-name " -> " vote-name) + print (word player-name " votes for " vote-name) + ] + ] + ] +end + +;;; ========== ELIMINATION ========== + +to run-elimination + set current-phase "elimination" + + let alive-players players with [alive?] + let max-votes max [vote-count] of alive-players + + ;; Find all players with max votes + let candidates alive-players with [vote-count = max-votes] + + ;; Tie-breaking: highest total historical votes, then random + let eliminated-player max-one-of candidates [total-votes-received] + + ask eliminated-player [ + set alive? false + set elimination-round current-round + set color gray + set size 1.5 + set label (word player-name " [OUT R" current-round "]") + set label-color gray + + print (word ">>> " player-name " eliminated in round " current-round + " with " vote-count " votes" + (ifelse-value is-spy? [" (SPY!)"] [" (regular)"])) + ] +end + +;;; ========== WIN CONDITION ========== + +to check-win-condition + let alive-count count players with [alive?] + let spy-alive? any? players with [alive? and is-spy?] + + ;; Spy eliminated -> regulars win + if not spy-alive? [ + set game-over? true + set winner "regulars" + set spy-detected? true + set rounds-to-detection current-round + set total-games-played total-games-played + 1 + set total-regular-wins total-regular-wins + 1 + print "*** REGULARS WIN! The spy has been eliminated. ***" + if auto-reveal? [ reveal-spy ] + stop + ] + + ;; 2 or fewer alive -> spy wins + if alive-count <= 2 [ + set game-over? true + set winner "spy" + set spy-detected? false + set total-games-played total-games-played + 1 + set total-spy-wins total-spy-wins + 1 + print "*** SPY WINS! Too few players remain. ***" + if auto-reveal? [ reveal-spy ] + stop + ] + + ;; Max rounds reached -> spy wins + if current-round >= max-rounds [ + set game-over? true + set winner "spy" + set spy-detected? false + set total-games-played total-games-played + 1 + set total-spy-wins total-spy-wins + 1 + print "*** SPY WINS! Max rounds reached. ***" + if auto-reveal? [ reveal-spy ] + stop + ] +end + +;;; ========== REVEAL & UTILITIES ========== + +to reveal-spy + ask players [ + ifelse is-spy? [ + set color red + set size 3.0 + set label (word player-name " [SPY: " my-word "]") + set label-color red + ] [ + if alive? [ + set label (word player-name " [" my-word "]") + ] + ] + ] + print (word "Regular word: " regular-word " | Spy word: " spy-word) +end + +to new-game + setup +end + +to-report truncate-string [str max-len] + ifelse length str > max-len + [ report (word substring str 0 max-len "...") ] + [ report str ] +end + +to-report detection-rate + ifelse total-games-played > 0 + [ report precision (total-regular-wins / total-games-played * 100) 1 ] + [ report 0 ] +end + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + current-round + current-phase + winner + total-llm-calls + + + total-games-played + detection-rate + total-regular-wins + total-spy-wins + + + regular-word + ifelse-value game-over? [spy-word] ["(hidden)"] + + + + + + let alive-players players with [alive? or elimination-round = current-round] + ask alive-players [ + create-temporary-plot-pen player-name + set-current-plot-pen player-name + ifelse is-spy? + [ set-plot-pen-color red ] + [ set-plot-pen-color sky ] + plotxy current-round vote-count + ] + + + + + + + + + if total-games-played > 0 and game-over? [ plotxy total-games-played detection-rate ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3a571cd457a0628443efcd25dba6b14e92a21b1f Mon Sep 17 00:00:00 2001 From: JNK234 Date: Wed, 18 Mar 2026 12:34:00 -0500 Subject: [PATCH 3/7] fix: nlogox widget attributes and template variable format - Add disableUntilTicks to buttons (required attribute) - Add fontSize to monitors (required attribute) - Fix plot attributes to camelCase (xMin/xMax/yMin/yMax/autoPlotX/autoPlotY) - Fix llm:chat-with-template to use nested [key value] pairs - Use llama3.2:3b model name in config --- demos/social-deduction-game/config.txt | 2 +- .../social-deduction-game.nlogox | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/demos/social-deduction-game/config.txt b/demos/social-deduction-game/config.txt index ebb5e0f..19611c7 100644 --- a/demos/social-deduction-game/config.txt +++ b/demos/social-deduction-game/config.txt @@ -1,5 +1,5 @@ provider=ollama -model=llama3.2 +model=llama3.2:3b temperature=0.4 max_tokens=80 timeout_seconds=30 diff --git a/demos/social-deduction-game/social-deduction-game.nlogox b/demos/social-deduction-game/social-deduction-game.nlogox index 6b651e6..d85ef9b 100644 --- a/demos/social-deduction-game/social-deduction-game.nlogox +++ b/demos/social-deduction-game/social-deduction-game.nlogox @@ -206,9 +206,11 @@ to run-description-phase llm:clear-history let desc "" carefully [ - set desc llm:chat-with-template "description-template.yaml" - (list "player_name" player-name "my_word" my-word - "round_number" (word current-round) "transcript" transcript-text) + set desc llm:chat-with-template "description-template.yaml" (list + (list "player_name" player-name) + (list "my_word" my-word) + (list "round_number" (word current-round)) + (list "transcript" transcript-text)) set total-llm-calls total-llm-calls + 1 ] [ set desc "My word relates to something common and familiar." @@ -410,13 +412,13 @@ end + disableUntilTicks="false" kind="Observer" display="Setup" forever="false">setup + disableUntilTicks="false" kind="Observer" display="Go" forever="true">go + disableUntilTicks="false" kind="Observer" display="Step" forever="false">step-round + disableUntilTicks="false" kind="Observer" display="New Game" forever="false">new-game current-round + fontSize="11" display="Round" precision="17">current-round current-phase + fontSize="11" display="Phase" precision="17">current-phase winner + fontSize="11" display="Winner" precision="17">winner total-llm-calls + fontSize="11" display="LLM Calls" precision="17">total-llm-calls total-games-played + fontSize="11" display="Games Played" precision="17">total-games-played detection-rate + fontSize="11" display="Detection %" precision="17">detection-rate total-regular-wins + fontSize="11" display="Regular Wins" precision="17">total-regular-wins total-spy-wins + fontSize="11" display="Spy Wins" precision="17">total-spy-wins regular-word + fontSize="11" display="Regular Word" precision="17">regular-word ifelse-value game-over? [spy-word] ["(hidden)"] + fontSize="11" display="Spy Word" precision="17">ifelse-value game-over? [spy-word] ["(hidden)"] + autoPlotX="true" autoPlotY="true" legend="false" + xMin="0.0" xMax="10.0" yMin="0.0" yMax="5.0"> let alive-players players with [alive? or elimination-round = current-round] @@ -490,7 +493,8 @@ end + autoPlotX="true" autoPlotY="true" legend="false" + xMin="0.0" xMax="10.0" yMin="0.0" yMax="100.0"> From db3755a6eff54f63a9c2beed6c8930534bef5bca Mon Sep 17 00:00:00 2001 From: JNK234 Date: Wed, 18 Mar 2026 13:33:52 -0500 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20visual=20polish=20=E2=80=94=20noir?= =?UTF-8?q?=20theme,=20distinct=20colors,=20vote=20arrows,=20phase=20indic?= =?UTF-8?q?ator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix player visibility: radius 10, -90° offset (12 o'clock start), size 4.0 - Distinct player colors: 8-color palette with base-color variable - Dark noir background: charcoal gradient + dark red circular table - Vote arrows: directed links from voter to target in voter's color - Center phase indicator: shows round, phase, elimination, and win state - Spy reveal: size 5.0, bright red, all words shown on reveal - Eliminated players: muted color (base-color - 4) instead of gray - Correct link shape XML for NetLogo 7.0.3 format --- .../social-deduction-game.nlogox | 152 +++++++++++++++--- 1 file changed, 129 insertions(+), 23 deletions(-) diff --git a/demos/social-deduction-game/social-deduction-game.nlogox b/demos/social-deduction-game/social-deduction-game.nlogox index d85ef9b..bd8278f 100644 --- a/demos/social-deduction-game/social-deduction-game.nlogox +++ b/demos/social-deduction-game/social-deduction-game.nlogox @@ -7,6 +7,8 @@ extensions [llm] breed [players player] +breed [indicators indicator] +directed-link-breed [vote-arrows vote-arrow] players-own [ player-name @@ -18,6 +20,9 @@ players-own [ vote-count total-votes-received elimination-round + base-color + home-x + home-y ] globals [ @@ -84,12 +89,20 @@ to setup set spy-detected? false set rounds-to-detection -1 + ;; Draw noir background and table + draw-background + + ;; Color palette for up to 8 players + let palette (list 15 105 55 25 125 85 45 65) + ;; Create players in a circle set-default-shape players "person" create-players num-players [ - set size 3.0 - set color sky - set player-name (word "Player " (who + 1)) + set size 4.0 + let idx who + set base-color item idx palette + set color base-color + set player-name (word "Player " (idx + 1)) set is-spy? false set alive? true set my-description "" @@ -98,11 +111,13 @@ to setup set total-votes-received 0 set elimination-round -1 - ;; Arrange in circle - let angle (360 / num-players) * who - setxy (12 * sin angle) (12 * cos angle) + ;; Arrange in circle — radius 10, starting at 12 o'clock (-90 offset) + let angle -90 + (360 / num-players) * idx + set home-x (10 * cos angle) + set home-y (10 * sin angle) + setxy home-x home-y set label player-name - set label-color white + set label-color base-color + 2 ] ;; Assign one random spy @@ -117,9 +132,14 @@ to setup [ set my-word regular-word ] ] - ;; Draw the discussion table (center ring) - ask patches with [abs pxcor <= 4 and abs pycor <= 4 and (distancexy 0 0) >= 3 and (distancexy 0 0) <= 4] [ - set pcolor 3 + ;; Create center phase indicator + create-indicators 1 [ + setxy 0 0 + set size 0.1 + set shape "default" + set color black + set label (word "ROUND " current-round) + set label-color white ] reset-ticks @@ -196,6 +216,15 @@ end to run-description-phase set current-phase "describing" + ;; Clear vote arrows from previous round + ask vote-arrows [ die ] + + ;; Update center indicator + ask indicators [ + set label (word "ROUND " current-round " — DESCRIBING...") + set label-color white + ] + let transcript-text full-transcript if transcript-text = "" [ set transcript-text "No descriptions yet." ] @@ -244,6 +273,12 @@ end to run-voting-phase set current-phase "voting" + ;; Update center indicator + ask indicators [ + set label (word "ROUND " current-round " — VOTING...") + set label-color white + ] + ;; Reset vote counts ask players with [alive?] [ set vote-count 0 @@ -288,6 +323,14 @@ to run-voting-phase ] ] + ;; Draw vote arrow from voter to target + if target != nobody [ + create-vote-arrow-to target [ + set color [base-color] of myself + set thickness 0.3 + ] + ] + if show-votes? [ set label (word player-name " -> " vote-name) print (word player-name " votes for " vote-name) @@ -313,15 +356,21 @@ to run-elimination ask eliminated-player [ set alive? false set elimination-round current-round - set color gray - set size 1.5 + set color base-color - 4 + set size 2.0 set label (word player-name " [OUT R" current-round "]") - set label-color gray + set label-color base-color - 4 print (word ">>> " player-name " eliminated in round " current-round " with " vote-count " votes" (ifelse-value is-spy? [" (SPY!)"] [" (regular)"])) ] + + ;; Update center indicator + ask indicators [ + set label (word "ELIMINATED: " [player-name] of eliminated-player) + set label-color 15 + ] end ;;; ========== WIN CONDITION ========== @@ -339,6 +388,7 @@ to check-win-condition set total-games-played total-games-played + 1 set total-regular-wins total-regular-wins + 1 print "*** REGULARS WIN! The spy has been eliminated. ***" + ask indicators [ set label "REGULARS WIN!" set label-color 55 ] if auto-reveal? [ reveal-spy ] stop ] @@ -351,6 +401,7 @@ to check-win-condition set total-games-played total-games-played + 1 set total-spy-wins total-spy-wins + 1 print "*** SPY WINS! Too few players remain. ***" + ask indicators [ set label "SPY WINS!" set label-color 15 ] if auto-reveal? [ reveal-spy ] stop ] @@ -363,6 +414,7 @@ to check-win-condition set total-games-played total-games-played + 1 set total-spy-wins total-spy-wins + 1 print "*** SPY WINS! Max rounds reached. ***" + ask indicators [ set label "SPY WINS!" set label-color 15 ] if auto-reveal? [ reveal-spy ] stop ] @@ -371,21 +423,52 @@ end ;;; ========== REVEAL & UTILITIES ========== to reveal-spy - ask players [ - ifelse is-spy? [ - set color red - set size 3.0 - set label (word player-name " [SPY: " my-word "]") - set label-color red + let the-spy one-of players with [is-spy?] + let spy-name [player-name] of the-spy + + ask the-spy [ + set color 15 + set size 5.0 + set label (word "SPY: " my-word) + set label-color 15 + ] + + ask players with [not is-spy?] [ + ifelse alive? [ + set label (word player-name " [" my-word "]") + set label-color base-color + 2 ] [ - if alive? [ - set label (word player-name " [" my-word "]") - ] + set label (word "[OUT R" elimination-round "] " my-word) + set label-color base-color - 4 ] ] + + ;; Update center indicator + ask indicators [ + set label (word "SPY WAS " spy-name "!") + set label-color 15 + ] + print (word "Regular word: " regular-word " | Spy word: " spy-word) end +to draw-background + ;; Dark navy/charcoal gradient — lighter near center + ask patches [ + let dist distancexy 0 0 + set pcolor scale-color 3 dist 25 0 + ] + + ;; Circular table: ring at distance 5-6 from center + ask patches with [distancexy 0 0 >= 5 and distancexy 0 0 <= 6] [ + set pcolor 13 + ] + ;; Inner table fill + ask patches with [distancexy 0 0 < 5] [ + set pcolor 12 + ] +end + to new-game setup end @@ -587,5 +670,28 @@ Built using the NetLogo LLM Extension. - + + + + + + + + + + + + + + + + + + + + + + + + From 151d8268de684216f467c5669ee41d1c7c170249 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Wed, 18 Mar 2026 13:47:12 -0500 Subject: [PATCH 5/7] refactor: extract vote prompt to YAML template, use base-color in plot - Add vote-template.yaml with system/template structure matching description template - Replace inline vote prompt with llm:chat-with-template call - Validate LLM response against alive player names, fallback to llm:choose - Update Votes Per Round plot to use per-player base-color instead of red/sky - Update info tab to reflect template-based voting --- .../social-deduction-game.nlogox | 38 ++++++++++--------- .../social-deduction-game/vote-template.yaml | 18 +++++++++ 2 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 demos/social-deduction-game/vote-template.yaml diff --git a/demos/social-deduction-game/social-deduction-game.nlogox b/demos/social-deduction-game/social-deduction-game.nlogox index bd8278f..1fcc10c 100644 --- a/demos/social-deduction-game/social-deduction-game.nlogox +++ b/demos/social-deduction-game/social-deduction-game.nlogox @@ -289,23 +289,29 @@ to run-voting-phase ask p [ ;; Build list of other alive player names let others-names map [q -> [player-name] of q] filter [q -> q != self] alive-players + let others-str reduce [[ a b ] -> (word a ", " b)] others-names - ;; Build vote prompt - let vote-prompt (word - "You are " player-name " in a social deduction game called Undercover.\n" - "Your secret word is: " my-word "\n" - "Most players share the same word, but ONE player (the spy) has a different similar word.\n" - "The spy's descriptions will be subtly different because they are describing a different word.\n\n" - "Here are all descriptions so far:\n" - full-transcript "\n" - "Based on the descriptions, which player do you think is the SPY?\n" - "Look for descriptions that seem slightly off or describe a different concept.\n" - "Vote for the player you find most suspicious.") - + ;; Vote via template — LLM picks from constrained list llm:clear-history let vote-name "" carefully [ - set vote-name llm:choose vote-prompt others-names + let raw-vote llm:chat-with-template "vote-template.yaml" (list + (list "player_name" player-name) + (list "my_word" my-word) + (list "transcript" full-transcript) + (list "other_players" others-str)) + ;; Match response against valid player names + set vote-name "" + foreach others-names [ n -> + if member? n raw-vote and vote-name = "" [ + set vote-name n + ] + ] + ;; Fallback: if no valid name found, use llm:choose + if vote-name = "" [ + set vote-name llm:choose (word "Pick the most suspicious player: " others-str) others-names + set total-llm-calls total-llm-calls + 1 + ] set total-llm-calls total-llm-calls + 1 ] [ set vote-name one-of others-names @@ -566,9 +572,7 @@ end ask alive-players [ create-temporary-plot-pen player-name set-current-plot-pen player-name - ifelse is-spy? - [ set-plot-pen-color red ] - [ set-plot-pen-color sky ] + set-plot-pen-color base-color plotxy current-round vote-count ] @@ -594,7 +598,7 @@ A social deduction game (Undercover / Spy Words) where LLM agents try to identif 1. **Setup**: Players are arranged in a circle. One random player is assigned the spy word; the rest get the regular word. 2. **Description Phase**: Each alive player uses the LLM to generate a 1-2 sentence description of their word without saying it. -3. **Voting Phase**: Each alive player uses `llm:choose` to vote for the most suspicious player based on all descriptions so far. +3. **Voting Phase**: Each alive player uses `llm:chat-with-template` to vote for the most suspicious player based on all descriptions so far. 4. **Elimination**: The player with the most votes is eliminated. Ties broken by historical vote totals. 5. **Win Check**: Regulars win if the spy is eliminated. The spy wins if 2 or fewer players remain or max rounds are reached. diff --git a/demos/social-deduction-game/vote-template.yaml b/demos/social-deduction-game/vote-template.yaml new file mode 100644 index 0000000..8868d39 --- /dev/null +++ b/demos/social-deduction-game/vote-template.yaml @@ -0,0 +1,18 @@ +system: | + You are {player_name} in a social deduction game called Undercover. + Most players share the same word, but ONE player (the spy) has a different similar word. + The spy's descriptions will be subtly different because they are describing a different word. + You must vote to eliminate the player you find most suspicious. + Respond with ONLY the name of the player you vote for (e.g. "Player 3"). Nothing else. + +template: | + Your secret word is: {my_word} + + Here are all descriptions so far: + {transcript} + + The alive players you can vote for: {other_players} + + Based on the descriptions, which player do you think is the SPY? + Look for descriptions that seem slightly off or describe a different concept. + Respond with ONLY the player name. From 36d53a5cfa60eea364cdde8627ca82d3ad3f5be3 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Wed, 18 Mar 2026 14:07:34 -0500 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20rewrite=20prompts=20for=20better=20g?= =?UTF-8?q?ameplay=20=E2=80=94=20concrete=20clues,=20no=20self-votes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description template: - Demand distinguishing clues: "something TRUE about your word that would NOT be true for a similar word" - Ban vague phrases and "I notice" narration style - Add good vs bad clue examples for calibration - Enforce one sentence under 20 words Vote template: - Add explicit self-vote prevention: "You CANNOT vote for {player_name}" - Reinforce constraint in both system prompt and template - Guide analytical comparison of clues Voting code: - Separate template vote and llm:choose fallback into independent carefully blocks - Prevent cascading errors when template returns self-vote --- .../description-template.yaml | 26 +++- .../social-deduction-game.nlogox | 140 +++++++----------- .../social-deduction-game/vote-template.yaml | 19 +-- 3 files changed, 78 insertions(+), 107 deletions(-) diff --git a/demos/social-deduction-game/description-template.yaml b/demos/social-deduction-game/description-template.yaml index fb1434e..1955891 100644 --- a/demos/social-deduction-game/description-template.yaml +++ b/demos/social-deduction-game/description-template.yaml @@ -1,15 +1,25 @@ system: | - You are {player_name} in a word guessing social deduction game called Undercover. - You have a secret word. Most players share the same word, but one player (the spy) has a different but similar word. - Describe your word in 1-2 short sentences WITHOUT saying it directly. - Be specific enough to show knowledge but not so specific you reveal it to the spy. - Do NOT repeat descriptions from previous rounds. Be creative and varied. + You are {player_name} in a social deduction game called Undercover. + Several players each have a secret word. Most share the SAME word, but one player (the spy) has a DIFFERENT but similar word. + Each round you give a clue about your word. After clues, players vote to eliminate the suspected spy. + + HOW TO GIVE A GOOD CLUE: + - Describe something TRUE about your word that would NOT be true for a similar word + - Think: "What makes MY word different from things like it?" + - Examples of GOOD clues (specific, distinguishing): + "Mine is typically found in kitchens" / "You need electricity to use mine" / "Mine comes in many flavors" + - Examples of BAD clues (too vague, applies to anything similar): + "It brings warmth" / "It's associated with creativity" / "People enjoy using it" + - Write exactly ONE sentence, under 20 words + - Do NOT say the word itself + - Do NOT start with "I notice" or "My secret word" + - Each round, give a DIFFERENT clue than previous rounds template: | Your secret word is: {my_word} - This is round {round_number}. + Round {round_number}. - Descriptions from previous rounds: + Previous clues: {transcript} - Give YOUR description for this round. 1-2 short sentences only. Do NOT say the word itself. + Give ONE short sentence (under 20 words) describing something specific and true about "{my_word}" that helps distinguish it from similar things. Do NOT say the word. diff --git a/demos/social-deduction-game/social-deduction-game.nlogox b/demos/social-deduction-game/social-deduction-game.nlogox index 1fcc10c..5588942 100644 --- a/demos/social-deduction-game/social-deduction-game.nlogox +++ b/demos/social-deduction-game/social-deduction-game.nlogox @@ -294,28 +294,37 @@ to run-voting-phase ;; Vote via template — LLM picks from constrained list llm:clear-history let vote-name "" + + ;; Step 1: Try template-based vote carefully [ let raw-vote llm:chat-with-template "vote-template.yaml" (list (list "player_name" player-name) (list "my_word" my-word) (list "transcript" full-transcript) (list "other_players" others-str)) - ;; Match response against valid player names - set vote-name "" + set total-llm-calls total-llm-calls + 1 + ;; Match response against valid player names (others only) foreach others-names [ n -> if member? n raw-vote and vote-name = "" [ set vote-name n ] ] - ;; Fallback: if no valid name found, use llm:choose - if vote-name = "" [ - set vote-name llm:choose (word "Pick the most suspicious player: " others-str) others-names + ] [ + print (word player-name " template vote error: " error-message) + ] + + ;; Step 2: Fallback to llm:choose if template didn't yield a valid name + if vote-name = "" [ + carefully [ + llm:clear-history + set vote-name llm:choose + (word "You are " player-name ". Pick the most suspicious player from: " others-str ". You CANNOT pick yourself.") + others-names set total-llm-calls total-llm-calls + 1 + ] [ + set vote-name one-of others-names + print (word player-name " vote fallback error: " error-message) ] - set total-llm-calls total-llm-calls + 1 - ] [ - set vote-name one-of others-names - print (word player-name " vote error: " error-message) ] set my-vote vote-name @@ -492,80 +501,32 @@ to-report detection-rate end ]]> - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - current-round - current-phase - winner - total-llm-calls - - - total-games-played - detection-rate - total-regular-wins - total-spy-wins - - - regular-word - ifelse-value game-over? [spy-word] ["(hidden)"] - - - + + + + regular-word + total-regular-wins + total-spy-wins + total-games-played + winner + total-llm-calls + detection-rate + current-phase + current-round + ifelse-value game-over? [spy-word] ["(hidden)"] + let alive-players players with [alive? or elimination-round = current-round] @@ -577,20 +538,16 @@ end ] - - + - + - if total-games-played > 0 and game-over? [ plotxy total-games-played detection-rate ] + 0 and game-over? [ plotxy total-games-played detection-rate ]]]> - ## WHAT IS IT? A social deduction game (Undercover / Spy Words) where LLM agents try to identify a hidden spy among them. Each player receives a secret word — most players share the same word (regulars), but one player has a different but similar word (the spy). Players describe their words and vote to eliminate the suspected spy. @@ -636,12 +593,14 @@ A social deduction game (Undercover / Spy Words) where LLM agents try to identif Inspired by the Undercover (Who Is Spy?) party game and LLMArena (ACL 2024) — social deduction games as an evaluation framework for LLM reasoning. Built using the NetLogo LLM Extension. - ]]> + - - + + + + @@ -698,4 +657,5 @@ Built using the NetLogo LLM Extension. + setup repeat 75 [ go ] diff --git a/demos/social-deduction-game/vote-template.yaml b/demos/social-deduction-game/vote-template.yaml index 8868d39..58c84d5 100644 --- a/demos/social-deduction-game/vote-template.yaml +++ b/demos/social-deduction-game/vote-template.yaml @@ -1,18 +1,19 @@ system: | You are {player_name} in a social deduction game called Undercover. - Most players share the same word, but ONE player (the spy) has a different similar word. - The spy's descriptions will be subtly different because they are describing a different word. - You must vote to eliminate the player you find most suspicious. - Respond with ONLY the name of the player you vote for (e.g. "Player 3"). Nothing else. + Most players share the same secret word, but ONE player (the spy) has a different but similar word. + You must vote for the player whose descriptions suggest they have a DIFFERENT word than yours. + IMPORTANT: You CANNOT vote for yourself. You must pick someone else. + Respond with ONLY a player name (e.g. "Player 3"). Nothing else — no explanation. template: | Your secret word is: {my_word} + You are: {player_name} - Here are all descriptions so far: + All descriptions so far: {transcript} - The alive players you can vote for: {other_players} + Players you can vote for (NOT yourself): {other_players} - Based on the descriptions, which player do you think is the SPY? - Look for descriptions that seem slightly off or describe a different concept. - Respond with ONLY the player name. + Think about which player's descriptions don't quite match your word "{my_word}". + Look for concrete details that fit a SIMILAR but DIFFERENT object. + Respond with ONLY the player name you vote for. You CANNOT vote for {player_name}. From 841ab630d5c634ccea88ad8742deb01e99a6a786 Mon Sep 17 00:00:00 2001 From: JNK234 Date: Thu, 19 Mar 2026 11:44:29 -0500 Subject: [PATCH 7/7] fix: enforce unique indirect clues, raise temperature for variety - Rewrite description template: demand indirect associations, ban obvious properties - Add good/bad clue examples for calibration - Explicitly label transcript as "DO NOT repeat any of these" - Ban common openings ("Best enjoyed", "Often enjoyed") - Bump temperature to 0.8 in config for more diverse outputs --- .../description-template.yaml | 28 +++++++++++-------- .../social-deduction-game.nlogox | 4 +-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/demos/social-deduction-game/description-template.yaml b/demos/social-deduction-game/description-template.yaml index 1955891..263271a 100644 --- a/demos/social-deduction-game/description-template.yaml +++ b/demos/social-deduction-game/description-template.yaml @@ -3,23 +3,27 @@ system: | Several players each have a secret word. Most share the SAME word, but one player (the spy) has a DIFFERENT but similar word. Each round you give a clue about your word. After clues, players vote to eliminate the suspected spy. - HOW TO GIVE A GOOD CLUE: - - Describe something TRUE about your word that would NOT be true for a similar word - - Think: "What makes MY word different from things like it?" - - Examples of GOOD clues (specific, distinguishing): - "Mine is typically found in kitchens" / "You need electricity to use mine" / "Mine comes in many flavors" - - Examples of BAD clues (too vague, applies to anything similar): - "It brings warmth" / "It's associated with creativity" / "People enjoy using it" - - Write exactly ONE sentence, under 20 words + CRITICAL RULES: + - Your clue must be DIFFERENT from every clue already given. Read the previous clues carefully and say something NEW. + - Be INDIRECT — describe a memory, association, or situation involving your word, NOT its obvious properties + - Do NOT describe what it looks/tastes/smells like directly - Do NOT say the word itself - - Do NOT start with "I notice" or "My secret word" - - Each round, give a DIFFERENT clue than previous rounds + - Write exactly ONE sentence, under 20 words + - Do NOT start with "Best enjoyed" or "Often enjoyed" or "My word" + + GOOD CLUE EXAMPLES (indirect, subtle): + - "Some people collect fancy versions of these from around the world." + - "You might see one of these at a birthday party or during a power outage." + + BAD CLUE EXAMPLES (too obvious, reveals the word): + - "It is brewed in a pot every morning" — too obvious + - "It has a warm glow and a wick" — basically says the word template: | Your secret word is: {my_word} Round {round_number}. - Previous clues: + Previous clues from all players (DO NOT repeat any of these): {transcript} - Give ONE short sentence (under 20 words) describing something specific and true about "{my_word}" that helps distinguish it from similar things. Do NOT say the word. + Think of a unique angle or personal association with your word that nobody has mentioned yet. Write ONE indirect, subtle clue under 20 words. diff --git a/demos/social-deduction-game/social-deduction-game.nlogox b/demos/social-deduction-game/social-deduction-game.nlogox index 5588942..a6be13a 100644 --- a/demos/social-deduction-game/social-deduction-game.nlogox +++ b/demos/social-deduction-game/social-deduction-game.nlogox @@ -506,9 +506,9 @@ end - + - +