這陣子利用 AI 所作的探索主要有兩條途徑。一是知識蒸餾 + MyTime 數位分身,一是共存真實世界。
看似兩條不同路徑,其實指向同一個核心,只不過出發點相反。
知識蒸餾 + MyTime 數位分身:
事件 -> 世界模型
共存真實世界:
世界模型 -> 事件
兩者互為鏡像。
這兩條研究路徑最後都指向同一個問題:如何利用 LLM 的世界模型,重新建構一個可運作、可推理、可互動的世界。
LLM 已經學到大量世界知識,因此非常適合作為世界模型(World Model)的核心推理引擎,也讓這類系統第一次具有較高的可行性。
底下是對得到的部份結果和一些嘗試所作的整理和記錄。
- 建構虛擬世界 (MCOS): 一個宏大的願景,目標是建構一個像 Matrix 那樣的世界。其中部份內容較科幻,但是對於整個世界的格局和架構有個大概了解。
- 共存真實模型 (CRM): 一種用於大型互動世界的事件導向一致性模型。這是從一個問題開始發展起來的架構:可否為不同玩家維持不同但不衝突的真實?
- Mini MCOS + CRM Prototype: 結合 MCOS 與 CRM 的一個小型實驗及模擬程式。非常雛形的一個模擬,每個世界只跑 50 個 ticks,主要目的是驗證 CRM 的核心想法。
§建構虛擬世界 (MCOS)
MCOS(Micro Cosmos Operating System)目前仍屬於概念性架構,目的是探索大型 AI 原生虛擬世界可能需要的整體分層設計,而不是提出已完成的實作方案。
1. 夢境的藍圖:虛擬世界架構的宏觀設計
打造與現實無異的虛擬世界,核心是將其視為一個**「微型宇宙操作系統」 (MCOS)**。為了高效管理與無限擴充,我們將其分為三層:
- 核心仿真層: 虛擬世界的心臟。負責維持時間、空間的絕對同步,並讓地心引力、能量守恆等基礎物理規則永不停歇地運作。
- 互動與感知層: 大腦與大腦外的橋樑。透過腦機接口捕捉人的意圖,並將世界的幾何、光影數據,直接轉換成大腦看得懂的五感神經信號,完美欺騙大腦。
- 內容與社會層: 宇宙的日常。運行非玩家角色(NPC)的 AI、維持社會經濟,並動態生成新聞與隨機事件,讓世界保持運轉。
系統防護機制: 為了不當機,世界被切成數百萬個小區域無縫切換。若某處出現物理 Bug,系統會在幾毫秒內將該區時間倒帶、修正,甚至微微覆蓋使用者的記憶(如產生一陣暈眩或既視感),抹除出錯痕跡。
2. 代碼與感知:構建底層物理引擎與環境模擬
要讓使用者無論用放大鏡還是望遠鏡都找不到破綻,底層的物理引擎(FPE)必須做到大小兼顧:
- 看哪裡,算哪裡: 為了節省運算資源,引擎採用「薛丁格的貓」邏輯。沒人看的地方只用文字記錄狀態;一旦有人走近,系統才會在千分之一秒內將該區升級為全精度計算(包括分子與量子實驗變化)。
- 絕對的真實感: 這裡沒有預先畫好的死背景。風雨、海浪都是根據流體力學當場計算的;東西被打碎時(如玻璃、牆壁),碎裂的痕跡與質量也是依據材料特性即時產生。
- 視覺與光線的極致: 系統會追蹤每一道光線的數百次折射。但為了效率,會聰明地忽略人類潛意識分不出來的微小差異,確保運作流暢不頭暈。
3. 數位生命:複雜 NPC 的行為邏輯與情感模型
一個真實的世界,取決於裡面的居民活不活靈。Matrix 裡的幾十億個 NPC 必須擁有個性和自由意志的假象:
- 獨立思考(感知-認知-行動, Perception-Cognition-Action, P-C-A): NPC 有自己的觀察視角、信念(Beliefs, 會記錯或誤會)、慾望 (Desires)與短中長期計畫 (Intentions),並依照這些擬定行為。
- 會聊天、會進化的 AI 大腦: 每個 NPC 都內建專屬的語言模型與記憶庫。農夫只聊收成,不會聊高科技,且他們會跟路人學習新的流行語和社會規範,不斷進化。
- 喜怒哀樂與記憶: NPC 的情緒由事件觸發,並會影響做事的衝勁。他們擁有自傳式的回憶,也會像人類一樣隨著時間選擇性遺忘不重要的事。
- 智商調配(社會 LOD): 玩家走近時,NPC 才會瞬間開機、充滿靈魂;遠離玩家時則降為低耗能狀態,只跑背景數據。系統也會模擬 NPC 的生老病死,維持社會的歷史厚重感。
結論:編程宇宙的最終考驗
建構一個與物理現實無異的虛擬世界,這不僅是技術上的終極挑戰,更是哲學和倫理上的終極考驗。
§共存真實模型 (CRM)
相較於 MCOS 偏向長期願景,CRM (Coexistent Reality Model) 則是本文目前真正開始進行實作驗證的一部分。Mini Prototype 的主要目的,就是驗證 CRM 在事件提交(Event Commit)模型下是否具有可行性。
摘要
大型互動世界通常假設所有玩家共享一個全域一致的世界狀態。這種設計雖然易於理解,但在高延遲與高併發環境下,會導致大量同步成本與回滾行為。其實對玩家體驗而言,全域狀態一致性既非必要,也非最佳解。本文提出「共存真實模型(CRM)」,允許多個玩家各自維持暫時不同、但不衝突的真實世界視角,並僅在事件層級上進行承諾與收斂。此模型以事件提交取代狀態同步,避免回滾,並能在網路分割下平滑退化。
1. 問題背景
傳統線上遊戲與虛擬世界架構依賴權威伺服器維持單一世界狀態。當玩家行為產生衝突時,系統必須回溯或修正已顯示的結果,造成體驗中斷。 然而實務觀察顯示,只要因果關係合理、敘事連貫、最終結果一致,玩家通常不會察覺暫時的不一致。
2. 核心觀點
CRM 的核心假設是:
玩家不需要共享完全相同的狀態,只需要共享「最終會產生影響的事件結果」。
3. 共存真實模型
個人真實(Personal Reality, PR)
每位玩家都有自己的即時模擬世界,可自由推進,不需等待同步。
共享真實(Shared Reality, SR)
只有會影響他人的事件,才進入共享層級。
世界帳本(World Ledger, WL)
所有被承諾的事件都寫入帳本,確保最終一致的結果。
4. 為什麼不需要回滾
CRM 不回溯時間,而是重新詮釋事件意義:
- 攻擊失敗 → 未命中
- 撿取衝突 → 延遲取得
- 動作被拒 → 視覺淡出
玩家看到的是「合理結果」,而不是「被修正的錯誤」。
5. 網路分割不是錯誤,而是模式
在 CRM 中,網路分割代表世界暫時分岔,而非系統故障。只要事件最終能被分類、承諾或降級,世界就不會崩潰。
6. 限制與適用範圍
CRM 不適合需要即時全域公平或嚴格決定性的系統,例如金融交易或競技裁判。但對多數互動世界而言,這是一個更符合人類感知的模型。
7. 結論
共存真實模型提供了一種重新思考一致性的方式:
不是讓世界永遠一致,而是讓體驗永遠合理。
我們希望此模型能成為大型互動系統設計的新討論起點。
§Mini MCOS + CRM Prototype
Mini MCOS + CRM Prototype 並不是 MCOS 的完整實現,而是一個極小型的概念驗證(Proof of Concept)。它驗證了 CRM 的事件提交、Personal Reality、World Ledger 與 Autonomous Agent 等核心想法,可以在一個小型模擬世界中運作。
概念
MCOS:分層式虛擬世界架構(核心仿真 / 互動感知 / 內容社會)
CRM:事件導向一致性模型,允許多個真實共存,衝突時重新詮釋而非回滾
系統架構
┌─────────────────────────────────────────────────┐
│ Agent A (PR) │ Agent B (PR) │ NPC Agents │
├─────────────────────────────────────────────────┤
│ Commit Resolver (SR) │
│ 因果檢查 → 衝突偵測 → COMMIT / DOWNGRADE / REJECT │
├─────────────────────────────────────────────────┤
│ World Ledger (WL) │
│ Append-only 事件日誌 │
├─────────────────────────────────────────────────┤
│ World Engine (Core) │
│ 10x10 網格 │ 時間步進 │ 資源再生 │
└─────────────────────────────────────────────────┘核心機制
CRM 事件承諾流程
- Agent 自主決策產生 Event
- 提交到 Commit Resolver
- Resolver 檢查因果性 → 違規則 REJECT
- 偵測同 tick 衝突 → DOWNGRADE(重新詮釋)
- 無衝突 → COMMIT
- 結果寫入 World Ledger,廣播給所有 PR
重新詮義範例
| 衝突 | DOWNGRADE 結果 |
|---|---|
| 兩人同時採集 | 一人取得,另一人「already taken」 |
| 交易目標已被佔用 | 「shop just closed」 |
| 攻擊已倒下的目標 | 「attack missed」 |
BDI 自主決策
每個 Agent 根據 Belief-Desire-Intention 模型運作:
- 信念:感知範圍內的環境狀態
- 慾望:生存(飢餓)> 繁榮(工作/採集)> 交易 > 互動 > 探索
- 意圖:根據信念+慾望+人格特質選擇行動
- 人格:boldness(攻擊傾向)、sociability(對話頻率)、greed(採集積極度)、patience(等待忍耐度)
角色行為
| 角色 | 工作行為 | 交易物品 |
|---|---|---|
| Farmer | 採集食物 | 食物 |
| Baker | 採集食物 | 食物 |
| Trader | 採集木材 | 木材 |
| Craftsman | 採集礦石 | 礦石 |
| Mayor | 閒逛 | 無 |
世界事件(隨機觸發,12% 機率/每 tick)
| 事件 | 效果 |
|---|---|
| sunny | 晴天,無特殊效果 |
| rainy | 雨天,移動受阻 |
| windy | 大風,資源位置打亂 |
| drought | 乾旱,食物再生率降至 30% |
| market_boom | 市場繁榮,所有價格上漲 30-80% |
| market_crash | 市場崩盤,所有價格下跌 30-60% |
| festival | 慶典,所有 Agent 快樂度 +0.3 |
| fog | 霧,感知範圍縮小 |
檔案結構
mcos_crm_prototype/
├── main.py # 模擬主迴圈
├── world_engine.py # 空間網格、時間、資源
├── world_ledger.py # Append-only 事件帳本
├── personal_reality.py # 每 Agent 的本地快照
├── commit_resolver.py # CRM 核心:事件評估
├── npc_agent.py # BDI 自主決策 Agent
├── economy.py # 經濟系統
└── models.py # 資料結構執行
cd mcos_crm_prototype
python main.py每次執行結果不同,隨機元素包括:
- Agent 起始位置與資金
- 人格特質(boldness, sociability, greed, patience)
- 每 tick 的行動順序
- 世界事件(drought, market_boom, fog, festival 等)
- 市場價格波動
- Agent 決策與行為
結論
MCOS、CRM 與 Mini Prototype 並不是一個完成的系統,而是一次對「AI 原生虛擬世界」的探索。
MCOS 嘗試回答「世界應該如何被建構」;CRM 嘗試回答「多個真實如何共存」;Prototype 則驗證其中部分概念是否具有工程上的可行性。
這些工作距離真正的大型 AI 世界仍然很遙遠,但希望能作為後續研究的一個起點。
對我而言,這三個部分並不是彼此獨立的專案,而是同一條研究路徑上的不同層次:MCOS 描述世界的整體架構,CRM 描述世界的一致性機制,而 MyTime 的數位分身與知識蒸餾,則探索如何從真實世界建立世界模型。三者最終都希望朝向 AI 原生世界(AI-native World)的建構。
A. 模擬結果
OK= COMMIT,事件成功執行DOWN= DOWNGRADE,衝突後重新詮釋REJ= REJECT,因果性違規
============================================================
Mini MCOS + CRM Prototype
Coexistent Reality Model - Autonomous Simulation
Agents: 3 players + 2 NPCs
World: 10x10 grid | Ticks: 50
Seed: 1863589732
============================================================
══════════ Tick 1 ══════════
Bob (Farmer ) OK MOVE (direction=east)
Smith (Craftsman) OK MOVE (direction=north)
Alice (Baker ) OK MOVE (direction=east)
Mayor (Mayor ) OK MOVE (direction=east)
Carol (Trader ) OK TALK (target=bob,topic=life)
------------------------------------------------------------
Economy: supply=[food:53 wood:20 ore:10] prices=[food:4.8 wood:5.7 ore:7.9] M-Credits=570.7
------------------------------------------------------------
Alice pos=(4, 1) credits=128.2 hunger=[..........] 0.08 inv=[empty] mood=happy(0.5)
Bob pos=(8, 0) credits=124.5 hunger=[..........] 0.07 inv=[empty] mood=happy(0.5)
Carol pos=(9, 2) credits= 96.8 hunger=[..........] 0.07 inv=[empty] mood=happy(0.5)
Smith pos=(8, 6) credits=123.2 hunger=[..........] 0.07 inv=[empty] mood=happy(0.5)
Mayor pos=(2, 6) credits= 98.0 hunger=[..........] 0.07 inv=[empty] mood=happy(0.5)
══════════ Tick 2 ══════════
Smith (Craftsman) OK MOVE (direction=west)
Bob (Farmer ) OK MOVE (direction=east)
Mayor (Mayor ) OK IDLE ()
Alice (Baker ) OK MOVE (direction=east)
Carol (Trader ) OK MOVE (direction=north)
------------------------------------------------------------
Economy: supply=[food:50 wood:20 ore:10] prices=[food:4.3 wood:5.5 ore:7.7] M-Credits=570.7
------------------------------------------------------------
Alice pos=(5, 1) credits=128.2 hunger=[#.........] 0.16 inv=[empty] mood=happy(0.4)
Bob pos=(9, 0) credits=124.5 hunger=[#.........] 0.16 inv=[empty] mood=happy(0.4)
Carol pos=(9, 1) credits= 96.8 hunger=[#.........] 0.16 inv=[empty] mood=happy(0.4)
Smith pos=(7, 6) credits=123.2 hunger=[#.........] 0.14 inv=[empty] mood=happy(0.4)
Mayor pos=(2, 6) credits= 98.0 hunger=[#.........] 0.15 inv=[empty] mood=happy(0.4)
══════════ Tick 3 ══════════
** WORLD EVENT: rainy - rain slows movement **
Carol (Trader ) OK ATTACK (target=bob,reason=robbery)
Bob (Farmer ) OK MOVE (direction=west)
Alice (Baker ) OK MOVE (direction=south)
Mayor (Mayor ) OK MOVE (direction=south)
Smith (Craftsman) OK MOVE (direction=south)
------------------------------------------------------------
Economy: supply=[food:53 wood:20 ore:10] prices=[food:4.0 wood:5.3 ore:7.5] M-Credits=570.7
------------------------------------------------------------
Alice pos=(5, 2) credits=128.2 hunger=[##........] 0.23 inv=[empty] mood=happy(0.4)
Bob pos=(8, 0) credits=124.5 hunger=[##........] 0.24 inv=[empty] mood=happy(0.4)
Carol pos=(9, 1) credits= 96.8 hunger=[##........] 0.23 inv=[empty] mood=happy(0.4)
Smith pos=(7, 7) credits=123.2 hunger=[##........] 0.22 inv=[empty] mood=happy(0.4)
Mayor pos=(2, 7) credits= 98.0 hunger=[##........] 0.23 inv=[empty] mood=happy(0.4)
══════════ Tick 4 ══════════
Carol (Trader ) OK MOVE (direction=north)
Smith (Craftsman) OK MOVE (direction=east)
Alice (Baker ) OK MOVE (direction=south)
Mayor (Mayor ) OK MOVE (direction=east)
Bob (Farmer ) OK MOVE (direction=south)
------------------------------------------------------------
Economy: supply=[food:53 wood:20 ore:10] prices=[food:4.0 wood:5.2 ore:7.3] M-Credits=570.7
------------------------------------------------------------
Alice pos=(5, 3) credits=128.2 hunger=[###.......] 0.31 inv=[empty] mood=happy(0.4)
Bob pos=(8, 1) credits=124.5 hunger=[###.......] 0.32 inv=[empty] mood=happy(0.4)
Carol pos=(9, 0) credits= 96.8 hunger=[##........] 0.30 inv=[empty] mood=happy(0.4)
Smith pos=(8, 7) credits=123.2 hunger=[##........] 0.29 inv=[empty] mood=happy(0.4)
Mayor pos=(3, 7) credits= 98.0 hunger=[###.......] 0.32 inv=[empty] mood=happy(0.4)
══════════ Tick 5 ══════════
Bob (Farmer ) OK TALK (target=carol,topic=life)
Smith (Craftsman) OK MOVE (direction=north)
Mayor (Mayor ) OK MOVE (direction=east)
Alice (Baker ) OK MOVE (direction=east)
Carol (Trader ) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:53 wood:23 ore:10] prices=[food:3.9 wood:5.1 ore:7.3] M-Credits=570.7
------------------------------------------------------------
Alice pos=(6, 3) credits=128.2 hunger=[###.......] 0.38 inv=[empty] mood=happy(0.3)
Bob pos=(8, 1) credits=124.5 hunger=[####......] 0.40 inv=[empty] mood=happy(0.4)
Carol pos=(9, 0) credits= 96.8 hunger=[###.......] 0.39 inv=[empty] mood=happy(0.4)
Smith pos=(8, 6) credits=123.2 hunger=[###.......] 0.37 inv=[empty] mood=happy(0.3)
Mayor pos=(4, 7) credits= 98.0 hunger=[###.......] 0.40 inv=[empty] mood=happy(0.3)
══════════ Tick 6 ══════════
Carol (Trader ) OK ATTACK (target=bob,reason=robbery)
Alice (Baker ) OK MOVE (direction=north)
Mayor (Mayor ) OK IDLE ()
Bob (Farmer ) OK MOVE (direction=east)
Smith (Craftsman) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:52 wood:20 ore:10] prices=[food:3.7 wood:4.6 ore:7.2] M-Credits=570.7
------------------------------------------------------------
Alice pos=(5, 2) credits=128.2 hunger=[####......] 0.46 inv=[empty] mood=happy(0.3)
Bob pos=(9, 1) credits=124.5 hunger=[####......] 0.49 inv=[empty] mood=angry(0.3)
Carol pos=(9, 0) credits= 96.8 hunger=[####......] 0.47 inv=[empty] mood=happy(0.3)
Smith pos=(9, 6) credits=123.2 hunger=[####......] 0.46 inv=[empty] mood=happy(0.3)
Mayor pos=(4, 7) credits= 98.0 hunger=[####......] 0.49 inv=[empty] mood=happy(0.3)
══════════ Tick 7 ══════════
Mayor (Mayor ) OK MOVE (direction=south)
Smith (Craftsman) OK MOVE (direction=west)
Bob (Farmer ) OK MOVE (direction=east)
Carol (Trader ) OK MOVE (direction=east)
Alice (Baker ) OK MOVE (direction=north)
------------------------------------------------------------
Economy: supply=[food:52 wood:22 ore:10] prices=[food:3.7 wood:4.5 ore:7.2] M-Credits=570.7
------------------------------------------------------------
Alice pos=(5, 1) credits=128.2 hunger=[#####.....] 0.53 inv=[empty] mood=happy(0.3)
Bob pos=(9, 1) credits=124.5 hunger=[#####.....] 0.56 inv=[empty] mood=happy(0.3)
Carol pos=(9, 0) credits= 96.8 hunger=[#####.....] 0.54 inv=[empty] mood=happy(0.3)
Smith pos=(8, 6) credits=123.2 hunger=[#####.....] 0.56 inv=[empty] mood=happy(0.3)
Mayor pos=(4, 8) credits= 98.0 hunger=[#####.....] 0.55 inv=[empty] mood=happy(0.3)
══════════ Tick 8 ══════════
Alice (Baker ) OK MOVE (direction=north)
Bob (Farmer ) OK MOVE (direction=south)
Carol (Trader ) OK MOVE (direction=north)
Mayor (Mayor ) OK MOVE (direction=east)
Smith (Craftsman) OK MOVE (direction=south)
------------------------------------------------------------
Economy: supply=[food:50 wood:20 ore:10] prices=[food:4.0 wood:4.6 ore:7.4] M-Credits=570.7
------------------------------------------------------------
Alice pos=(5, 0) credits=128.2 hunger=[######....] 0.62 inv=[empty] mood=happy(0.3)
Bob pos=(9, 2) credits=124.5 hunger=[######....] 0.63 inv=[empty] mood=happy(0.3)
Carol pos=(9, 0) credits= 96.8 hunger=[######....] 0.64 inv=[empty] mood=happy(0.3)
Smith pos=(7, 7) credits=123.2 hunger=[######....] 0.65 inv=[empty] mood=happy(0.3)
Mayor pos=(4, 8) credits= 98.0 hunger=[######....] 0.65 inv=[empty] mood=happy(0.3)
══════════ Tick 9 ══════════
Bob (Farmer ) OK MOVE (direction=south)
Mayor (Mayor ) OK MOVE (direction=west)
Smith (Craftsman) OK MOVE (direction=west)
Alice (Baker ) OK MOVE (direction=south)
Carol (Trader ) OK MOVE (direction=west)
------------------------------------------------------------
Economy: supply=[food:52 wood:20 ore:10] prices=[food:3.9 wood:4.6 ore:7.2] M-Credits=570.7
------------------------------------------------------------
Alice pos=(5, 1) credits=128.2 hunger=[######....] 0.68 inv=[empty] mood=happy(0.2)
Bob pos=(9, 3) credits=124.5 hunger=[#######...] 0.73 inv=[empty] mood=happy(0.3)
Carol pos=(8, 0) credits= 96.8 hunger=[#######...] 0.71 inv=[empty] mood=happy(0.3)
Smith pos=(5, 7) credits=123.2 hunger=[#######...] 0.74 inv=[empty] mood=happy(0.2)
Mayor pos=(2, 8) credits= 98.0 hunger=[#######...] 0.73 inv=[empty] mood=happy(0.2)
══════════ Tick 10 ══════════
Mayor (Mayor ) OK MOVE (direction=west)
Smith (Craftsman) OK MOVE (direction=west)
Bob (Farmer ) OK MOVE (direction=west)
Carol (Trader ) OK MOVE (direction=south)
Alice (Baker ) OK MOVE (direction=south)
------------------------------------------------------------
Economy: supply=[food:50 wood:20 ore:13] prices=[food:4.0 wood:4.4 ore:7.0] M-Credits=570.7
------------------------------------------------------------
Alice pos=(4, 2) credits=128.2 hunger=[#######...] 0.75 inv=[empty] mood=happy(0.2)
Bob pos=(8, 3) credits=124.5 hunger=[########..] 0.80 inv=[empty] mood=happy(0.2)
Carol pos=(8, 1) credits= 96.8 hunger=[#######...] 0.77 inv=[empty] mood=happy(0.2)
Smith pos=(3, 7) credits=123.2 hunger=[########..] 0.82 inv=[empty] mood=happy(0.2)
Mayor pos=(0, 8) credits= 98.0 hunger=[########..] 0.81 inv=[empty] mood=happy(0.2)
══════════ Tick 11 ══════════
Alice (Baker ) OK MOVE (direction=north)
Carol (Trader ) OK MOVE (direction=east)
Mayor (Mayor ) OK MOVE (direction=west)
Smith (Craftsman) OK MOVE (direction=east)
Bob (Farmer ) OK ATTACK (target=carol,reason=robbery)
------------------------------------------------------------
Economy: supply=[food:50 wood:21 ore:13] prices=[food:3.9 wood:4.4 ore:7.0] M-Credits=570.7
------------------------------------------------------------
Alice pos=(4, 1) credits=128.2 hunger=[########..] 0.83 inv=[empty] mood=happy(0.2)
Bob pos=(8, 3) credits=124.5 hunger=[########..] 0.89 inv=[empty] mood=angry(0.2)
Carol pos=(8, 1) credits= 96.8 hunger=[########..] 0.83 inv=[empty] mood=angry(0.2)
Smith pos=(4, 7) credits=123.2 hunger=[########..] 0.89 inv=[empty] mood=happy(0.2)
Mayor pos=(0, 8) credits= 98.0 hunger=[########..] 0.89 inv=[empty] mood=happy(0.2)
══════════ Tick 12 ══════════
Carol (Trader ) OK MOVE (direction=north)
Mayor (Mayor ) OK MOVE (direction=west)
Alice (Baker ) OK MOVE (direction=west)
Bob (Farmer ) OK ATTACK (target=carol,reason=dispute)
Smith (Craftsman) OK MOVE (direction=north)
------------------------------------------------------------
Economy: supply=[food:50 wood:20 ore:10] prices=[food:4.2 wood:4.3 ore:6.9] M-Credits=570.7
------------------------------------------------------------
Alice pos=(3, 1) credits=128.2 hunger=[########..] 0.89 inv=[empty] mood=happy(0.2)
Bob pos=(7, 3) credits=124.5 hunger=[#########.] 0.98 inv=[empty] mood=angry(0.3)
Carol pos=(8, 0) credits= 96.8 hunger=[########..] 0.90 inv=[empty] mood=angry(0.4)
Smith pos=(3, 6) credits=123.2 hunger=[#########.] 0.99 inv=[empty] mood=happy(0.2)
Mayor pos=(0, 8) credits= 98.0 hunger=[#########.] 0.98 inv=[empty] mood=happy(0.2)
══════════ Tick 13 ══════════
Carol (Trader ) OK MOVE (direction=south)
Alice (Baker ) OK MOVE (direction=south)
Smith (Craftsman) OK MOVE (direction=west)
Bob (Farmer ) OK MOVE (direction=south)
Mayor (Mayor ) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:53 wood:21 ore:10] prices=[food:4.0 wood:4.1 ore:7.0] M-Credits=570.7
------------------------------------------------------------
Alice pos=(3, 3) credits=128.2 hunger=[#########.] 0.96 inv=[empty] mood=happy(0.2)
Bob pos=(6, 4) credits=124.5 hunger=[##########] 1.00 inv=[empty] mood=angry(0.2)
Carol pos=(7, 1) credits= 96.8 hunger=[#########.] 0.99 inv=[empty] mood=angry(0.4)
Smith pos=(2, 5) credits=123.2 hunger=[##########] 1.00 inv=[empty] mood=happy(0.2)
Mayor pos=(2, 8) credits= 98.0 hunger=[##########] 1.00 inv=[empty] mood=happy(0.2)
══════════ Tick 14 ══════════
Carol (Trader ) OK MOVE (direction=west)
Alice (Baker ) OK GATHER (resource_type=food,position=[3, 3])
Smith (Craftsman) OK ATTACK (target=alice,reason=provoked)
Bob (Farmer ) OK MOVE (direction=north)
Mayor (Mayor ) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:49 wood:21 ore:10] prices=[food:4.0 wood:3.8 ore:6.7] M-Credits=570.7
------------------------------------------------------------
Alice pos=(3, 3) credits=128.2 hunger=[##########] 1.00 inv=[food:1] mood=angry(0.2)
Bob pos=(5, 3) credits=124.5 hunger=[##########] 1.00 inv=[empty] mood=angry(0.2)
Carol pos=(5, 1) credits= 96.8 hunger=[##########] 1.00 inv=[empty] mood=angry(0.3)
Smith pos=(3, 5) credits=123.2 hunger=[##########] 1.00 inv=[empty] mood=happy(0.2)
Mayor pos=(2, 8) credits= 98.0 hunger=[##########] 1.00 inv=[empty] mood=happy(0.2)
══════════ Tick 15 ══════════
Smith (Craftsman) OK MOVE (direction=west)
Bob (Farmer ) OK MOVE (direction=south)
Alice (Baker ) OK EAT (reason=hungry)
Carol (Trader ) OK MOVE (direction=east)
Mayor (Mayor ) OK MOVE (direction=west)
------------------------------------------------------------
Economy: supply=[food:54 wood:21 ore:10] prices=[food:3.9 wood:3.6 ore:7.0] M-Credits=570.7
------------------------------------------------------------
Alice pos=(3, 3) credits=128.2 hunger=[######....] 0.60 inv=[empty] mood=happy(0.2)
Bob pos=(4, 4) credits=124.5 hunger=[##########] 1.00 inv=[empty] mood=angry(0.2)
Carol pos=(5, 1) credits= 96.8 hunger=[##########] 1.00 inv=[empty] mood=angry(0.3)
Smith pos=(2, 4) credits=123.2 hunger=[##########] 1.00 inv=[empty] mood=happy(0.1)
Mayor pos=(0, 8) credits= 98.0 hunger=[##########] 1.00 inv=[empty] mood=happy(0.1)
══════════ Tick 16 ══════════
Carol (Trader ) OK ATTACK (target=alice,reason=provoked)
Bob (Farmer ) OK MOVE (direction=south)
Alice (Baker ) OK GATHER (resource_type=food,position=[3, 3])
Mayor (Mayor ) OK MOVE (direction=west)
Smith (Craftsman) OK MOVE (direction=south)
------------------------------------------------------------
Economy: supply=[food:49 wood:20 ore:10] prices=[food:3.9 wood:3.4 ore:6.9] M-Credits=570.7
------------------------------------------------------------
Alice pos=(3, 3) credits=128.2 hunger=[######....] 0.68 inv=[food:1] mood=angry(0.4)
Bob pos=(3, 5) credits=124.5 hunger=[##########] 1.00 inv=[empty] mood=angry(0.2)
Carol pos=(4, 1) credits= 96.8 hunger=[##########] 1.00 inv=[empty] mood=angry(0.3)
Smith pos=(3, 5) credits=123.2 hunger=[##########] 1.00 inv=[empty] mood=happy(0.1)
Mayor pos=(0, 8) credits= 98.0 hunger=[##########] 1.00 inv=[empty] mood=happy(0.1)
══════════ Tick 17 ══════════
Alice (Baker ) OK EAT (reason=hungry)
Bob (Farmer ) OK ATTACK (target=npc_smith,reason=robbery)
Smith (Craftsman) OK MOVE (direction=north)
Mayor (Mayor ) OK MOVE (direction=north)
Carol (Trader ) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:49 wood:21 ore:10] prices=[food:3.9 wood:3.2 ore:6.8] M-Credits=570.7
------------------------------------------------------------
Alice pos=(3, 3) credits=128.2 hunger=[###.......] 0.37 inv=[empty] mood=angry(0.3)
Bob pos=(3, 4) credits=124.5 hunger=[##########] 1.00 inv=[empty] mood=angry(0.2)
Carol pos=(4, 1) credits= 96.8 hunger=[##########] 1.00 inv=[empty] mood=angry(0.3)
Smith pos=(3, 3) credits=123.2 hunger=[##########] 1.00 inv=[empty] mood=angry(0.2)
Mayor pos=(1, 7) credits= 98.0 hunger=[##########] 1.00 inv=[empty] mood=happy(0.1)
══════════ Tick 18 ══════════
Mayor (Mayor ) OK MOVE (direction=west)
Alice (Baker ) OK WORK (payment=7,resource_type=food,position=[3, 3])
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Smith (Craftsman) DOWN GATHER (resource_type=food,position=[3, 3]) [resource depleted just before arrival]
Carol (Trader ) OK MOVE (direction=south)
------------------------------------------------------------
Economy: supply=[food:48 wood:20 ore:10] prices=[food:3.8 wood:3.1 ore:6.7] M-Credits=577.7
------------------------------------------------------------
Alice pos=(3, 3) credits=135.2 hunger=[#####.....] 0.51 inv=[food:1] mood=angry(0.3)
Bob pos=(3, 3) credits=124.5 hunger=[##########] 1.00 inv=[food:1] mood=angry(0.2)
Carol pos=(3, 2) credits= 96.8 hunger=[##########] 1.00 inv=[empty] mood=angry(0.2)
Smith pos=(3, 3) credits=123.2 hunger=[##########] 1.00 inv=[empty] mood=angry(0.2)
Mayor pos=(0, 8) credits= 98.0 hunger=[##########] 1.00 inv=[empty] mood=sad(0.1)
══════════ Tick 19 ══════════
** WORLD EVENT: fog - perception radius reduced **
Smith (Craftsman) OK GATHER (resource_type=food,position=[3, 3])
Alice (Baker ) DOWN GATHER (resource_type=food,position=[3, 3]) [found only scraps remains]
Bob (Farmer ) OK EAT (reason=hungry)
Mayor (Mayor ) OK MOVE (direction=north)
Carol (Trader ) DOWN GATHER (resource_type=food,position=[3, 3]) [found only scraps remains]
------------------------------------------------------------
Economy: supply=[food:47 wood:20 ore:10] prices=[food:3.8 wood:3.1 ore:6.7] M-Credits=577.7
------------------------------------------------------------
Alice pos=(3, 3) credits=135.2 hunger=[######....] 0.60 inv=[food:1] mood=happy(0.3)
Bob pos=(3, 3) credits=124.5 hunger=[######....] 0.60 inv=[empty] mood=happy(0.2)
Carol pos=(3, 3) credits= 96.8 hunger=[##########] 1.00 inv=[empty] mood=sad(0.2)
Smith pos=(3, 3) credits=123.2 hunger=[##########] 1.00 inv=[food:1] mood=sad(0.2)
Mayor pos=(1, 7) credits= 98.0 hunger=[##########] 1.00 inv=[empty] mood=sad(0.1)
══════════ Tick 20 ══════════
Mayor (Mayor ) OK MOVE (direction=east)
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Smith (Craftsman) OK EAT (reason=hungry)
Carol (Trader ) DOWN GATHER (resource_type=food,position=[3, 3]) [found only scraps remains]
Alice (Baker ) OK TRADE (target=carol,item=food,cost=7)
------------------------------------------------------------
Economy: supply=[food:47 wood:20 ore:10] prices=[food:3.9 wood:2.9 ore:6.6] M-Credits=577.7
------------------------------------------------------------
Alice pos=(3, 3) credits=128.2 hunger=[######....] 0.65 inv=[food:2] mood=happy(0.3)
Bob pos=(3, 3) credits=124.5 hunger=[######....] 0.67 inv=[food:1] mood=happy(0.2)
Carol pos=(3, 3) credits=103.8 hunger=[##########] 1.00 inv=[empty] mood=sad(0.3)
Smith pos=(3, 3) credits=123.2 hunger=[######....] 0.60 inv=[empty] mood=sad(0.2)
Mayor pos=(2, 8) credits= 98.0 hunger=[##########] 1.00 inv=[empty] mood=sad(0.1)
══════════ Tick 21 ══════════
Bob (Farmer ) OK EAT (reason=hungry)
Carol (Trader ) OK GATHER (resource_type=food,position=[3, 3])
Smith (Craftsman) OK MOVE (direction=west)
Alice (Baker ) OK EAT (reason=hungry)
Mayor (Mayor ) OK MOVE (direction=south)
------------------------------------------------------------
Economy: supply=[food:46 wood:20 ore:10] prices=[food:4.0 wood:2.8 ore:6.6] M-Credits=577.7
------------------------------------------------------------
Alice pos=(3, 3) credits=128.2 hunger=[###.......] 0.34 inv=[food:1] mood=happy(0.3)
Bob pos=(3, 3) credits=124.5 hunger=[###.......] 0.35 inv=[empty] mood=happy(0.2)
Carol pos=(3, 3) credits=103.8 hunger=[##########] 1.00 inv=[empty] mood=sad(0.3)
Smith pos=(2, 3) credits=123.2 hunger=[######....] 0.69 inv=[empty] mood=sad(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[##########] 1.00 inv=[empty] mood=sad(0.1)
══════════ Tick 22 ══════════
Alice (Baker ) OK TRADE (target=bob,item=food,cost=7)
Smith (Craftsman) OK MOVE (direction=west)
Mayor (Mayor ) OK GATHER (resource_type=food,position=[1, 9])
Carol (Trader ) OK GATHER (resource_type=food,position=[3, 3])
Bob (Farmer ) OK WORK (payment=3,resource_type=food,position=[3, 3])
------------------------------------------------------------
Economy: supply=[food:44 wood:20 ore:10] prices=[food:3.7 wood:2.7 ore:6.6] M-Credits=580.7
------------------------------------------------------------
Alice pos=(3, 3) credits=121.2 hunger=[####......] 0.43 inv=[food:2] mood=happy(0.3)
Bob pos=(3, 3) credits=134.5 hunger=[####......] 0.48 inv=[empty] mood=happy(0.3)
Carol pos=(3, 3) credits=103.8 hunger=[##########] 1.00 inv=[food:1] mood=sad(0.3)
Smith pos=(1, 3) credits=123.2 hunger=[#######...] 0.76 inv=[empty] mood=sad(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[##########] 1.00 inv=[food:1] mood=sad(0.1)
══════════ Tick 23 ══════════
Smith (Craftsman) OK GATHER (resource_type=food,position=[1, 2])
Alice (Baker ) OK TRADE (target=carol,item=food,cost=5)
Mayor (Mayor ) OK EAT (reason=hungry)
Bob (Farmer ) OK ATTACK (target=npc_smith,reason=provoked)
Carol (Trader ) REJ EAT (reason=hungry) [violates causality]
------------------------------------------------------------
Economy: supply=[food:47 wood:20 ore:10] prices=[food:3.6 wood:2.3 ore:6.2] M-Credits=580.7
------------------------------------------------------------
Alice pos=(3, 3) credits=116.2 hunger=[#####.....] 0.50 inv=[food:3] mood=happy(0.3)
Bob pos=(3, 3) credits=134.5 hunger=[#####.....] 0.54 inv=[empty] mood=happy(0.3)
Carol pos=(3, 3) credits=108.8 hunger=[##########] 1.00 inv=[empty] mood=sad(0.3)
Smith pos=(1, 2) credits=123.2 hunger=[########..] 0.82 inv=[food:1] mood=angry(0.3)
Mayor pos=(1, 9) credits= 98.0 hunger=[######....] 0.60 inv=[empty] mood=happy(0.2)
══════════ Tick 24 ══════════
Mayor (Mayor ) OK GATHER (resource_type=food,position=[1, 9])
Alice (Baker ) OK TRADE (target=bob,item=food,cost=5)
Carol (Trader ) OK GATHER (resource_type=food,position=[3, 3])
Smith (Craftsman) OK EAT (reason=hungry)
Bob (Farmer ) DOWN GATHER (resource_type=food,position=[3, 3]) [found only scraps remains]
------------------------------------------------------------
Economy: supply=[food:49 wood:20 ore:10] prices=[food:3.5 wood:2.4 ore:6.3] M-Credits=580.7
------------------------------------------------------------
Alice pos=(3, 3) credits=111.2 hunger=[#####.....] 0.56 inv=[food:4] mood=happy(0.3)
Bob pos=(3, 3) credits=139.5 hunger=[######....] 0.61 inv=[empty] mood=happy(0.3)
Carol pos=(3, 3) credits=108.8 hunger=[##########] 1.00 inv=[food:1] mood=sad(0.3)
Smith pos=(1, 2) credits=123.2 hunger=[####......] 0.49 inv=[empty] mood=angry(0.3)
Mayor pos=(1, 9) credits= 98.0 hunger=[######....] 0.70 inv=[food:1] mood=happy(0.2)
══════════ Tick 25 ══════════
Carol (Trader ) OK EAT (reason=hungry)
Alice (Baker ) OK TRADE (target=carol,item=food,cost=4)
Bob (Farmer ) OK WORK (payment=5,resource_type=food,position=[3, 3])
Smith (Craftsman) OK MOVE (direction=north)
Mayor (Mayor ) OK EAT (reason=hungry)
------------------------------------------------------------
Economy: supply=[food:50 wood:21 ore:10] prices=[food:3.5 wood:2.6 ore:6.1] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits=107.2 hunger=[######....] 0.65 inv=[food:5] mood=happy(0.3)
Bob pos=(3, 3) credits=144.5 hunger=[#######...] 0.73 inv=[empty] mood=happy(0.3)
Carol pos=(3, 3) credits=112.8 hunger=[######....] 0.60 inv=[empty] mood=happy(0.3)
Smith pos=(1, 1) credits=123.2 hunger=[#####.....] 0.56 inv=[empty] mood=angry(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[###.......] 0.39 inv=[empty] mood=happy(0.2)
══════════ Tick 26 ══════════
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Carol (Trader ) OK MOVE (direction=north)
Alice (Baker ) OK EAT (reason=hungry)
Smith (Craftsman) OK MOVE (direction=west)
Mayor (Mayor ) OK MOVE (direction=north)
------------------------------------------------------------
Economy: supply=[food:49 wood:20 ore:11] prices=[food:3.4 wood:2.3 ore:6.1] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits=107.2 hunger=[###.......] 0.33 inv=[food:4] mood=happy(0.3)
Bob pos=(3, 3) credits=144.5 hunger=[########..] 0.81 inv=[food:1] mood=happy(0.3)
Carol pos=(3, 2) credits=112.8 hunger=[######....] 0.68 inv=[empty] mood=happy(0.3)
Smith pos=(0, 1) credits=123.2 hunger=[######....] 0.63 inv=[empty] mood=angry(0.2)
Mayor pos=(1, 8) credits= 98.0 hunger=[####......] 0.48 inv=[empty] mood=happy(0.2)
══════════ Tick 27 ══════════
Bob (Farmer ) OK EAT (reason=hungry)
Alice (Baker ) OK TRADE (target=bob,item=food,cost=5)
Mayor (Mayor ) OK IDLE ()
Smith (Craftsman) OK MOVE (direction=south)
Carol (Trader ) OK TALK (target=bob,topic=work)
------------------------------------------------------------
Economy: supply=[food:49 wood:20 ore:11] prices=[food:3.3 wood:2.3 ore:5.9] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits=102.2 hunger=[####......] 0.41 inv=[food:5] mood=happy(0.3)
Bob pos=(3, 3) credits=149.5 hunger=[####......] 0.49 inv=[empty] mood=happy(0.4)
Carol pos=(3, 2) credits=112.8 hunger=[#######...] 0.75 inv=[empty] mood=happy(0.3)
Smith pos=(0, 2) credits=123.2 hunger=[#######...] 0.71 inv=[empty] mood=angry(0.2)
Mayor pos=(1, 8) credits= 98.0 hunger=[#####.....] 0.57 inv=[empty] mood=happy(0.2)
══════════ Tick 28 ══════════
Mayor (Mayor ) OK MOVE (direction=north)
Smith (Craftsman) OK GATHER (resource_type=food,position=[0, 2])
Bob (Farmer ) OK TALK (target=carol,topic=life)
Alice (Baker ) OK TRADE (target=bob,item=food,cost=7)
Carol (Trader ) OK MOVE (direction=west)
------------------------------------------------------------
Economy: supply=[food:49 wood:26 ore:12] prices=[food:3.2 wood:2.0 ore:5.9] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 95.2 hunger=[####......] 0.48 inv=[food:6] mood=happy(0.3)
Bob pos=(3, 3) credits=156.5 hunger=[#####.....] 0.58 inv=[empty] mood=happy(0.4)
Carol pos=(3, 2) credits=112.8 hunger=[########..] 0.84 inv=[empty] mood=happy(0.3)
Smith pos=(0, 2) credits=123.2 hunger=[#######...] 0.78 inv=[food:1] mood=happy(0.2)
Mayor pos=(1, 7) credits= 98.0 hunger=[######....] 0.65 inv=[empty] mood=happy(0.2)
══════════ Tick 29 ══════════
Alice (Baker ) OK TRADE (target=bob,item=food,cost=5)
Bob (Farmer ) OK ATTACK (target=alice,reason=dispute)
Carol (Trader ) OK GATHER (resource_type=food,position=[3, 3])
Smith (Craftsman) OK EAT (reason=hungry)
Mayor (Mayor ) OK IDLE ()
------------------------------------------------------------
Economy: supply=[food:48 wood:26 ore:12] prices=[food:2.9 wood:1.9 ore:5.7] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 90.2 hunger=[#####.....] 0.58 inv=[food:7] mood=happy(0.3)
Bob pos=(3, 3) credits=161.5 hunger=[######....] 0.64 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=112.8 hunger=[#########.] 0.91 inv=[empty] mood=happy(0.3)
Smith pos=(0, 2) credits=123.2 hunger=[####......] 0.46 inv=[empty] mood=happy(0.2)
Mayor pos=(1, 7) credits= 98.0 hunger=[#######...] 0.74 inv=[empty] mood=happy(0.1)
══════════ Tick 30 ══════════
Alice (Baker ) OK GATHER (resource_type=food,position=[3, 3])
Smith (Craftsman) OK MOVE (direction=west)
Bob (Farmer ) OK MOVE (direction=west)
Mayor (Mayor ) OK MOVE (direction=north)
Carol (Trader ) DOWN GATHER (resource_type=food,position=[3, 3]) [resource depleted just before arrival]
------------------------------------------------------------
Economy: supply=[food:51 wood:20 ore:10] prices=[food:2.9 wood:1.6 ore:5.9] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 90.2 hunger=[######....] 0.65 inv=[food:8] mood=happy(0.3)
Bob pos=(2, 3) credits=161.5 hunger=[#######...] 0.71 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=112.8 hunger=[#########.] 0.99 inv=[empty] mood=sad(0.3)
Smith pos=(0, 2) credits=123.2 hunger=[#####.....] 0.54 inv=[empty] mood=happy(0.2)
Mayor pos=(1, 6) credits= 98.0 hunger=[########..] 0.82 inv=[empty] mood=happy(0.1)
══════════ Tick 31 ══════════
Carol (Trader ) OK GATHER (resource_type=food,position=[3, 3])
Alice (Baker ) OK EAT (reason=hungry)
Smith (Craftsman) OK GATHER (resource_type=food,position=[0, 2])
Bob (Farmer ) DOWN GATHER (resource_type=food,position=[3, 3]) [found only scraps remains]
Mayor (Mayor ) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:49 wood:20 ore:10] prices=[food:2.7 wood:1.5 ore:5.9] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 90.2 hunger=[###.......] 0.33 inv=[food:7] mood=happy(0.4)
Bob pos=(3, 3) credits=161.5 hunger=[########..] 0.80 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=112.8 hunger=[##########] 1.00 inv=[food:1] mood=sad(0.3)
Smith pos=(0, 2) credits=123.2 hunger=[######....] 0.62 inv=[food:1] mood=happy(0.2)
Mayor pos=(2, 7) credits= 98.0 hunger=[#########.] 0.90 inv=[empty] mood=happy(0.1)
══════════ Tick 32 ══════════
Smith (Craftsman) OK GATHER (resource_type=food,position=[0, 2])
Alice (Baker ) OK TRADE (target=carol,item=food,cost=6)
Carol (Trader ) REJ EAT (reason=hungry) [violates causality]
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Mayor (Mayor ) OK MOVE (direction=south)
------------------------------------------------------------
Economy: supply=[food:49 wood:20 ore:10] prices=[food:2.2 wood:1.2 ore:5.9] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 84.2 hunger=[####......] 0.42 inv=[food:8] mood=happy(0.4)
Bob pos=(3, 3) credits=161.5 hunger=[########..] 0.87 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=118.8 hunger=[##########] 1.00 inv=[empty] mood=sad(0.3)
Smith pos=(0, 2) credits=123.2 hunger=[######....] 0.69 inv=[food:2] mood=happy(0.2)
Mayor pos=(1, 8) credits= 98.0 hunger=[#########.] 0.98 inv=[empty] mood=sad(0.1)
══════════ Tick 33 ══════════
Alice (Baker ) OK TRADE (target=bob,item=food,cost=5)
Smith (Craftsman) OK EAT (reason=hungry)
Carol (Trader ) OK GATHER (resource_type=food,position=[3, 3])
Mayor (Mayor ) OK GATHER (resource_type=food,position=[1, 9])
Bob (Farmer ) DOWN GATHER (resource_type=food,position=[3, 3]) [resource depleted just before arrival]
------------------------------------------------------------
Economy: supply=[food:47 wood:20 ore:10] prices=[food:2.1 wood:1.3 ore:5.8] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 79.2 hunger=[####......] 0.50 inv=[food:9] mood=happy(0.4)
Bob pos=(3, 3) credits=166.5 hunger=[#########.] 0.94 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=118.8 hunger=[##########] 1.00 inv=[food:1] mood=happy(0.3)
Smith pos=(0, 2) credits=123.2 hunger=[###.......] 0.36 inv=[food:1] mood=happy(0.3)
Mayor pos=(1, 9) credits= 98.0 hunger=[##########] 1.00 inv=[food:1] mood=happy(0.1)
══════════ Tick 34 ══════════
Alice (Baker ) OK TRADE (target=carol,item=food,cost=6)
Carol (Trader ) REJ EAT (reason=hungry) [violates causality]
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Mayor (Mayor ) OK EAT (reason=hungry)
Smith (Craftsman) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:49 wood:20 ore:10] prices=[food:1.9 wood:1.1 ore:5.6] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 73.2 hunger=[#####.....] 0.57 inv=[food:10] mood=happy(0.4)
Bob pos=(3, 3) credits=166.5 hunger=[##########] 1.00 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=124.8 hunger=[##########] 1.00 inv=[empty] mood=happy(0.3)
Smith pos=(1, 2) credits=123.2 hunger=[####......] 0.44 inv=[food:1] mood=happy(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[######....] 0.60 inv=[empty] mood=happy(0.2)
══════════ Tick 35 ══════════
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Mayor (Mayor ) OK IDLE ()
Smith (Craftsman) OK TALK (target=carol,topic=life)
Alice (Baker ) REJ TRADE (target=npc_smith,item=food,cost=4) [violates causality]
Carol (Trader ) DOWN GATHER (resource_type=food,position=[3, 3]) [already taken]
------------------------------------------------------------
Economy: supply=[food:48 wood:20 ore:10] prices=[food:1.8 wood:1.0 ore:5.4] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 73.2 hunger=[######....] 0.65 inv=[food:10] mood=happy(0.3)
Bob pos=(3, 3) credits=166.5 hunger=[##########] 1.00 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=124.8 hunger=[##########] 1.00 inv=[empty] mood=sad(0.4)
Smith pos=(1, 2) credits=123.2 hunger=[####......] 0.50 inv=[food:1] mood=happy(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[######....] 0.68 inv=[empty] mood=happy(0.2)
══════════ Tick 36 ══════════
Smith (Craftsman) OK MOVE (direction=south)
Alice (Baker ) OK EAT (reason=hungry)
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Carol (Trader ) DOWN GATHER (resource_type=food,position=[3, 3]) [already taken]
Mayor (Mayor ) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:48 wood:20 ore:10] prices=[food:1.9 wood:1.0 ore:5.4] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 73.2 hunger=[###.......] 0.35 inv=[food:9] mood=happy(0.4)
Bob pos=(3, 3) credits=166.5 hunger=[##########] 1.00 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=124.8 hunger=[##########] 1.00 inv=[empty] mood=sad(0.4)
Smith pos=(1, 3) credits=123.2 hunger=[#####.....] 0.57 inv=[food:1] mood=happy(0.2)
Mayor pos=(2, 9) credits= 98.0 hunger=[#######...] 0.77 inv=[empty] mood=happy(0.1)
══════════ Tick 37 ══════════
** WORLD EVENT: sunny - clear skies, productivity up **
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Smith (Craftsman) OK MOVE (direction=south)
Mayor (Mayor ) OK GATHER (resource_type=food,position=[1, 9])
Carol (Trader ) DOWN GATHER (resource_type=food,position=[3, 3]) [already taken]
Alice (Baker ) OK TRADE (target=bob,item=food,cost=4)
------------------------------------------------------------
Economy: supply=[food:47 wood:20 ore:10] prices=[food:1.8 wood:1.0 ore:5.1] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 69.2 hunger=[####......] 0.41 inv=[food:10] mood=happy(0.4)
Bob pos=(3, 3) credits=170.5 hunger=[##########] 1.00 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=124.8 hunger=[##########] 1.00 inv=[empty] mood=sad(0.5)
Smith pos=(1, 4) credits=123.2 hunger=[######....] 0.66 inv=[food:1] mood=happy(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[########..] 0.87 inv=[food:1] mood=happy(0.2)
══════════ Tick 38 ══════════
Mayor (Mayor ) OK EAT (reason=hungry)
Carol (Trader ) OK GATHER (resource_type=food,position=[3, 3])
Alice (Baker ) REJ TRADE (target=npc_smith,item=food,cost=7) [violates causality]
Bob (Farmer ) DOWN GATHER (resource_type=food,position=[3, 3]) [resource depleted just before arrival]
Smith (Craftsman) OK EAT (reason=hungry)
------------------------------------------------------------
Economy: supply=[food:47 wood:20 ore:10] prices=[food:1.7 wood:1.0 ore:5.3] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 69.2 hunger=[####......] 0.50 inv=[food:10] mood=happy(0.3)
Bob pos=(3, 3) credits=170.5 hunger=[##########] 1.00 inv=[empty] mood=happy(0.4)
Carol pos=(3, 3) credits=124.8 hunger=[##########] 1.00 inv=[food:1] mood=sad(0.4)
Smith pos=(1, 4) credits=123.2 hunger=[###.......] 0.34 inv=[empty] mood=happy(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[#####.....] 0.56 inv=[empty] mood=happy(0.2)
══════════ Tick 39 ══════════
Mayor (Mayor ) OK IDLE ()
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Smith (Craftsman) OK MOVE (direction=south)
Alice (Baker ) REJ TRADE (target=npc_smith,item=food,cost=4) [violates causality]
Carol (Trader ) OK EAT (reason=hungry)
------------------------------------------------------------
Economy: supply=[food:46 wood:21 ore:10] prices=[food:1.7 wood:1.0 ore:5.0] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 69.2 hunger=[#####.....] 0.57 inv=[food:10] mood=happy(0.3)
Bob pos=(3, 3) credits=170.5 hunger=[##########] 1.00 inv=[food:1] mood=happy(0.4)
Carol pos=(3, 3) credits=124.8 hunger=[######....] 0.60 inv=[empty] mood=sad(0.4)
Smith pos=(1, 5) credits=123.2 hunger=[####......] 0.42 inv=[empty] mood=happy(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[######....] 0.64 inv=[empty] mood=happy(0.2)
══════════ Tick 40 ══════════
Mayor (Mayor ) OK MOVE (direction=east)
Smith (Craftsman) OK MOVE (direction=west)
Alice (Baker ) REJ TRADE (target=npc_smith,item=food,cost=6) [violates causality]
Bob (Farmer ) OK EAT (reason=hungry)
Carol (Trader ) OK MOVE (direction=north)
------------------------------------------------------------
Economy: supply=[food:47 wood:20 ore:10] prices=[food:1.5 wood:1.0 ore:5.1] M-Credits=585.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 69.2 hunger=[######....] 0.64 inv=[food:10] mood=happy(0.3)
Bob pos=(3, 3) credits=170.5 hunger=[######....] 0.60 inv=[empty] mood=happy(0.4)
Carol pos=(4, 2) credits=124.8 hunger=[######....] 0.69 inv=[empty] mood=sad(0.3)
Smith pos=(0, 5) credits=123.2 hunger=[####......] 0.49 inv=[empty] mood=happy(0.2)
Mayor pos=(2, 9) credits= 98.0 hunger=[#######...] 0.73 inv=[empty] mood=happy(0.2)
══════════ Tick 41 ══════════
Smith (Craftsman) OK MOVE (direction=north)
Mayor (Mayor ) OK GATHER (resource_type=food,position=[1, 9])
Bob (Farmer ) OK WORK (payment=6,resource_type=food,position=[3, 3])
Alice (Baker ) OK EAT (reason=hungry)
Carol (Trader ) OK MOVE (direction=north)
------------------------------------------------------------
Economy: supply=[food:45 wood:20 ore:10] prices=[food:1.6 wood:1.0 ore:5.2] M-Credits=591.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 69.2 hunger=[###.......] 0.32 inv=[food:9] mood=happy(0.3)
Bob pos=(3, 3) credits=176.5 hunger=[#######...] 0.72 inv=[food:1] mood=happy(0.4)
Carol pos=(4, 1) credits=124.8 hunger=[#######...] 0.77 inv=[empty] mood=sad(0.3)
Smith pos=(0, 4) credits=123.2 hunger=[#####.....] 0.58 inv=[empty] mood=happy(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[########..] 0.82 inv=[food:1] mood=happy(0.2)
══════════ Tick 42 ══════════
Mayor (Mayor ) OK EAT (reason=hungry)
Bob (Farmer ) OK EAT (reason=hungry)
Smith (Craftsman) OK MOVE (direction=west)
Carol (Trader ) OK MOVE (direction=west)
Alice (Baker ) OK TRADE (target=bob,item=food,cost=7)
------------------------------------------------------------
Economy: supply=[food:47 wood:20 ore:10] prices=[food:1.3 wood:1.1 ore:5.1] M-Credits=591.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 62.2 hunger=[####......] 0.41 inv=[food:10] mood=happy(0.3)
Bob pos=(3, 3) credits=183.5 hunger=[###.......] 0.40 inv=[empty] mood=happy(0.5)
Carol pos=(2, 1) credits=124.8 hunger=[########..] 0.85 inv=[empty] mood=sad(0.3)
Smith pos=(0, 4) credits=123.2 hunger=[######....] 0.66 inv=[empty] mood=happy(0.2)
Mayor pos=(1, 9) credits= 98.0 hunger=[#####.....] 0.52 inv=[empty] mood=happy(0.2)
══════════ Tick 43 ══════════
** WORLD EVENT: festival - NPC happiness boosted **
Carol (Trader ) OK GATHER (resource_type=food,position=[1, 1])
Smith (Craftsman) OK MOVE (direction=west)
Bob (Farmer ) OK MOVE (direction=north)
Mayor (Mayor ) OK MOVE (direction=north)
Alice (Baker ) OK TRADE (target=bob,item=food,cost=5)
------------------------------------------------------------
Economy: supply=[food:46 wood:20 ore:13] prices=[food:1.0 wood:1.1 ore:4.8] M-Credits=591.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 57.2 hunger=[####......] 0.48 inv=[food:11] mood=happy(0.6)
Bob pos=(3, 2) credits=188.5 hunger=[####......] 0.47 inv=[empty] mood=happy(0.7)
Carol pos=(1, 1) credits=124.8 hunger=[#########.] 0.93 inv=[food:1] mood=happy(0.5)
Smith pos=(0, 3) credits=123.2 hunger=[#######...] 0.75 inv=[empty] mood=happy(0.4)
Mayor pos=(1, 8) credits= 98.0 hunger=[#####.....] 0.59 inv=[empty] mood=happy(0.5)
══════════ Tick 44 ══════════
Bob (Farmer ) OK WORK (payment=3,resource_type=food,position=[3, 3])
Alice (Baker ) REJ TRADE (target=npc_smith,item=food,cost=4) [violates causality]
Carol (Trader ) OK EAT (reason=hungry)
Smith (Craftsman) OK MOVE (direction=south)
Mayor (Mayor ) OK GATHER (resource_type=food,position=[1, 9])
------------------------------------------------------------
Economy: supply=[food:49 wood:20 ore:10] prices=[food:1.0 wood:1.0 ore:4.6] M-Credits=594.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 57.2 hunger=[#####.....] 0.56 inv=[food:11] mood=happy(0.5)
Bob pos=(3, 3) credits=191.5 hunger=[#####.....] 0.58 inv=[empty] mood=happy(0.7)
Carol pos=(1, 1) credits=124.8 hunger=[#####.....] 0.59 inv=[empty] mood=happy(0.5)
Smith pos=(0, 4) credits=123.2 hunger=[########..] 0.84 inv=[empty] mood=happy(0.4)
Mayor pos=(1, 9) credits= 98.0 hunger=[######....] 0.68 inv=[food:1] mood=happy(0.5)
══════════ Tick 45 ══════════
Smith (Craftsman) OK MOVE (direction=south)
Alice (Baker ) REJ TRADE (target=carol,item=food,cost=4) [violates causality]
Carol (Trader ) OK TALK (target=alice,topic=life)
Bob (Farmer ) OK MOVE (direction=west)
Mayor (Mayor ) OK EAT (reason=hungry)
------------------------------------------------------------
Economy: supply=[food:49 wood:21 ore:10] prices=[food:1.1 wood:1.0 ore:4.5] M-Credits=594.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 57.2 hunger=[######....] 0.63 inv=[food:11] mood=happy(0.5)
Bob pos=(2, 3) credits=191.5 hunger=[######....] 0.65 inv=[empty] mood=happy(0.6)
Carol pos=(1, 1) credits=124.8 hunger=[######....] 0.67 inv=[empty] mood=happy(0.5)
Smith pos=(0, 4) credits=123.2 hunger=[#########.] 0.90 inv=[empty] mood=happy(0.4)
Mayor pos=(1, 9) credits= 98.0 hunger=[###.......] 0.34 inv=[empty] mood=happy(0.5)
══════════ Tick 46 ══════════
Mayor (Mayor ) OK MOVE (direction=east)
Smith (Craftsman) OK MOVE (direction=south)
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Carol (Trader ) OK GATHER (resource_type=food,position=[1, 1])
Alice (Baker ) DOWN GATHER (resource_type=food,position=[3, 3]) [already taken]
------------------------------------------------------------
Economy: supply=[food:48 wood:20 ore:10] prices=[food:1.0 wood:1.0 ore:4.4] M-Credits=594.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 57.2 hunger=[######....] 0.69 inv=[food:11] mood=happy(0.5)
Bob pos=(3, 3) credits=191.5 hunger=[#######...] 0.74 inv=[empty] mood=happy(0.6)
Carol pos=(1, 1) credits=124.8 hunger=[#######...] 0.76 inv=[food:1] mood=happy(0.5)
Smith pos=(0, 4) credits=123.2 hunger=[#########.] 0.98 inv=[empty] mood=happy(0.3)
Mayor pos=(2, 9) credits= 98.0 hunger=[####......] 0.41 inv=[empty] mood=happy(0.4)
══════════ Tick 47 ══════════
Alice (Baker ) OK EAT (reason=hungry)
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Mayor (Mayor ) OK MOVE (direction=north)
Smith (Craftsman) OK MOVE (direction=north)
Carol (Trader ) OK EAT (reason=hungry)
------------------------------------------------------------
Economy: supply=[food:50 wood:20 ore:10] prices=[food:1.0 wood:1.0 ore:4.5] M-Credits=594.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 57.2 hunger=[###.......] 0.36 inv=[food:10] mood=happy(0.5)
Bob pos=(3, 3) credits=191.5 hunger=[########..] 0.82 inv=[food:1] mood=happy(0.6)
Carol pos=(1, 1) credits=124.8 hunger=[####......] 0.45 inv=[empty] mood=happy(0.5)
Smith pos=(0, 2) credits=123.2 hunger=[##########] 1.00 inv=[empty] mood=happy(0.3)
Mayor pos=(2, 8) credits= 98.0 hunger=[####......] 0.50 inv=[empty] mood=happy(0.4)
══════════ Tick 48 ══════════
** WORLD EVENT: rainy - rain slows movement **
Alice (Baker ) OK TRADE (target=bob,item=food,cost=4)
Carol (Trader ) OK MOVE (direction=south)
Smith (Craftsman) OK GATHER (resource_type=food,position=[0, 2])
Bob (Farmer ) REJ EAT (reason=hungry) [violates causality]
Mayor (Mayor ) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:50 wood:20 ore:10] prices=[food:1.0 wood:1.0 ore:4.8] M-Credits=594.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 53.2 hunger=[####......] 0.43 inv=[food:11] mood=happy(0.5)
Bob pos=(3, 3) credits=195.5 hunger=[#########.] 0.90 inv=[empty] mood=happy(0.6)
Carol pos=(1, 2) credits=124.8 hunger=[#####.....] 0.54 inv=[empty] mood=happy(0.5)
Smith pos=(0, 2) credits=123.2 hunger=[##########] 1.00 inv=[food:1] mood=happy(0.3)
Mayor pos=(3, 8) credits= 98.0 hunger=[#####.....] 0.60 inv=[empty] mood=happy(0.4)
══════════ Tick 49 ══════════
Smith (Craftsman) OK EAT (reason=hungry)
Carol (Trader ) OK TALK (target=bob,topic=work)
Alice (Baker ) OK TRADE (target=bob,item=food,cost=4)
Mayor (Mayor ) OK MOVE (direction=south)
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
------------------------------------------------------------
Economy: supply=[food:50 wood:20 ore:10] prices=[food:1.2 wood:1.0 ore:4.6] M-Credits=594.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 49.2 hunger=[#####.....] 0.52 inv=[food:12] mood=happy(0.5)
Bob pos=(3, 3) credits=199.5 hunger=[#########.] 0.99 inv=[empty] mood=happy(0.6)
Carol pos=(1, 2) credits=124.8 hunger=[######....] 0.62 inv=[empty] mood=happy(0.4)
Smith pos=(0, 2) credits=123.2 hunger=[######....] 0.60 inv=[empty] mood=happy(0.3)
Mayor pos=(2, 9) credits= 98.0 hunger=[######....] 0.68 inv=[empty] mood=happy(0.3)
══════════ Tick 50 ══════════
Smith (Craftsman) OK MOVE (direction=north)
Bob (Farmer ) OK GATHER (resource_type=food,position=[3, 3])
Carol (Trader ) OK GATHER (resource_type=food,position=[1, 2])
Alice (Baker ) OK TRADE (target=bob,item=food,cost=5)
Mayor (Mayor ) OK MOVE (direction=east)
------------------------------------------------------------
Economy: supply=[food:47 wood:20 ore:10] prices=[food:1.3 wood:1.0 ore:4.6] M-Credits=594.7
------------------------------------------------------------
Alice pos=(3, 3) credits= 44.2 hunger=[#####.....] 0.60 inv=[food:13] mood=happy(0.5)
Bob pos=(3, 3) credits=204.5 hunger=[##########] 1.00 inv=[empty] mood=happy(0.7)
Carol pos=(1, 2) credits=124.8 hunger=[#######...] 0.71 inv=[food:1] mood=happy(0.4)
Smith pos=(0, 1) credits=123.2 hunger=[######....] 0.69 inv=[empty] mood=happy(0.3)
Mayor pos=(3, 9) credits= 98.0 hunger=[#######...] 0.76 inv=[empty] mood=happy(0.3)
============================================================
SIMULATION COMPLETE
============================================================
Total events : 250
COMMIT : 227
DOWNGRADE : 13
REJECT : 10
------------------------------------------------------------
Final Agent States:
Alice credits= 44.2 inv=[food:13] mood=happy(0.5) events=50
Bob credits=204.5 inv=[empty] mood=happy(0.7) events=50
Carol credits=124.8 inv=[food:1] mood=happy(0.4) events=50
Smith credits=123.2 inv=[empty] mood=happy(0.3) events=50
Mayor credits= 98.0 inv=[empty] mood=happy(0.3) events=50
============================================================B. Source Code Greatest Notes
>>> commit_resolver.py
from __future__ import annotations
import random
from models import Event, CommitResult, CommitStatus, Intent
from world_engine import WorldEngine
from world_ledger import WorldLedger
DOWNGRADE_MESSAGES = {
Intent.GATHER: [
"already taken",
"resource depleted just before arrival",
"found only scraps remains",
],
Intent.TRADE: [
"shop just closed",
"trader already sold out",
"deal fell through at last moment",
],
Intent.ATTACK: [
"attack missed",
"target dodged at the last second",
"blow glanced off harmlessly",
],
Intent.TALK: [
"target walked away mid-sentence",
"too noisy to hear",
"conversation drifted to nothing",
],
Intent.MOVE: [
"path blocked, retracing steps",
"lost footing briefly",
],
Intent.EAT: [
"food spoiled unexpectedly",
"ate but still slightly hungry",
],
Intent.WORK: [
"work partially completed",
"tool broke mid-task",
],
Intent.IDLE: [],
}
class CommitResolver:
def __init__(self, world: WorldEngine, ledger: WorldLedger) -> None:
self.world = world
self.ledger = ledger
self._pending: list[Event] = []
self._committed_this_tick: dict[str, list[str]] = {}
def submit(self, event: Event) -> None:
self._pending.append(event)
def resolve_all(self) -> list[CommitResult]:
results: list[CommitResult] = []
self._committed_this_tick.clear()
for event in self._pending:
result = self._evaluate(event)
self.ledger.append(result)
results.append(result)
if result.status == CommitStatus.COMMIT:
key = f"{event.intent.value}:{self._event_key(event)}"
self._committed_this_tick.setdefault(key, []).append(event.actor)
elif result.status == CommitStatus.DOWNGRADE:
agent = self.world.get_agent(event.actor)
if agent:
agent.emotion.boost("sadness", 0.08)
elif result.status == CommitStatus.REJECT:
agent = self.world.get_agent(event.actor)
if agent:
agent.emotion.boost("anger", 0.05)
agent.emotion.boost("sadness", 0.03)
self._pending.clear()
return results
def _event_key(self, event: Event) -> str:
if event.intent == Intent.GATHER:
return str(event.context.get("position", ""))
if event.intent == Intent.TRADE:
return str(event.context.get("target", ""))
if event.intent in (Intent.ATTACK, Intent.TALK):
return str(event.context.get("target", ""))
if event.intent == Intent.MOVE:
return event.context.get("direction", "")
return ""
def _evaluate(self, event: Event) -> CommitResult:
agent = self.world.get_agent(event.actor)
if agent is None:
return CommitResult(CommitStatus.REJECT, event, "actor not found")
if not self._check_causality(event, agent):
return CommitResult(CommitStatus.REJECT, event, "violates causality")
if self._detect_conflict(event):
return self._downgrade(event)
self._execute(event, agent)
return CommitResult(CommitStatus.COMMIT, event, "no conflict")
def _check_causality(self, event: Event, agent) -> bool:
if event.intent == Intent.GATHER:
pos = event.context.get("position")
if pos and tuple(pos) != agent.position:
return False
rtype = event.context.get("resource_type")
res = self.world.find_resource_at(agent.position, rtype)
if res is None or res.depleted:
return False
if event.intent == Intent.MOVE:
direction = event.context.get("direction", "")
if direction not in ("north", "south", "east", "west"):
return False
if event.intent == Intent.TRADE:
target_id = event.context.get("target", "")
target = self.world.get_agent(target_id)
if target is None:
return False
dist = abs(target.position[0] - agent.position[0]) + abs(
target.position[1] - agent.position[1]
)
if dist > 1:
return False
if event.intent == Intent.EAT:
if agent.inventory.get("food", 0) <= 0:
return False
return True
def _detect_conflict(self, event: Event) -> bool:
key = f"{event.intent.value}:{self._event_key(event)}"
existing = self._committed_this_tick.get(key, [])
if not existing:
return False
if event.intent == Intent.GATHER:
return True
if event.intent == Intent.TRADE:
return True
if event.intent in (Intent.ATTACK, Intent.TALK):
return True
return False
def _downgrade(self, event: Event) -> CommitResult:
messages = DOWNGRADE_MESSAGES.get(event.intent, ["unforeseen complication"])
msg = random.choice(messages) if messages else "reinterpretation applied"
reinterpreted = Event(
actor=event.actor,
intent=event.intent,
context={**event.context, "downgrade_reason": msg},
timestamp=event.timestamp,
id=event.id,
)
return CommitResult(CommitStatus.DOWNGRADE, reinterpreted, msg)
def _execute(self, event: Event, agent) -> None:
if event.intent == Intent.MOVE:
direction = event.context.get("direction", "")
self.world.move_agent(agent, direction)
elif event.intent == Intent.GATHER:
rtype = event.context.get("resource_type", "food")
res = self.world.find_resource_at(agent.position, rtype)
if res:
amount = res.harvest(1)
if amount > 0:
agent.inventory[rtype] = agent.inventory.get(rtype, 0) + amount
agent.emotion.boost("happiness", 0.03)
elif event.intent == Intent.EAT:
if agent.inventory.get("food", 0) > 0:
agent.inventory["food"] -= 1
agent.hunger = max(0.0, agent.hunger - 0.4)
agent.emotion.boost("happiness", 0.05)
elif event.intent == Intent.WORK:
agent.credits += event.context.get("payment", 5)
rtype = event.context.get("resource_type")
if rtype:
pos = tuple(event.context.get("position", agent.position))
res = self.world.find_resource_at(pos, rtype)
if res:
amount = res.harvest(1)
if amount > 0:
agent.inventory[rtype] = agent.inventory.get(rtype, 0) + amount
agent.emotion.boost("happiness", 0.03)
agent.hunger = min(1.0, agent.hunger + 0.05)
agent.emotion.boost("sadness", 0.02)
elif event.intent == Intent.TRADE:
cost = event.context.get("cost", 5)
item = event.context.get("item", "food")
target_id = event.context.get("target", "")
if agent.credits >= cost:
agent.credits -= cost
agent.inventory[item] = agent.inventory.get(item, 0) + 1
target = self.world.get_agent(target_id)
if target:
target.credits += cost
target.inventory[item] = target.inventory.get(item, 0) - 1
target.emotion.boost("happiness", 0.04)
agent.emotion.boost("happiness", 0.03)
elif event.intent == Intent.TALK:
target_id = event.context.get("target", "")
target = self.world.get_agent(target_id)
if target:
agent.emotion.boost("happiness", 0.02)
target.emotion.boost("happiness", 0.02)
elif event.intent == Intent.ATTACK:
target_id = event.context.get("target", "")
target = self.world.get_agent(target_id)
if target:
target.emotion.boost("anger", 0.2)
target.emotion.boost("fear", 0.1)
agent.emotion.boost("anger", 0.05)
>>> economy.py
from __future__ import annotations
import random
from world_engine import WorldEngine
class Economy:
def __init__(self, world: WorldEngine) -> None:
self.world = world
self.prices: dict[str, float] = {
"food": 5.0,
"wood": 6.0,
"ore": 8.0,
}
self.tax_rate: float = 0.1
self._base_demand: dict[str, float] = {
"food": 15.0,
"wood": 10.0,
"ore": 8.0,
}
def supply(self) -> dict[str, int]:
return self.world.resource_summary()
def demand(self) -> dict[str, float]:
demand = dict(self._base_demand)
for agent in self.world.agents.values():
hunger_factor = agent.hunger * 3
demand["food"] += hunger_factor
if agent.inventory.get("food", 0) == 0:
demand["food"] += 2
return demand
def update_prices(self) -> dict[str, float]:
supply = self.supply()
demand = self.demand()
for rtype in self.prices:
s = max(1, supply.get(rtype, 1))
d = max(0.1, demand.get(rtype, 1))
ratio = d / s
adjustment = (ratio - 1.0) * 0.3
noise = random.gauss(0, 0.15)
self.prices[rtype] = max(1.0, min(20.0, self.prices[rtype] + adjustment + noise))
return dict(self.prices)
def tax_revenue(self, amount: float) -> float:
return amount * self.tax_rate
def summary(self) -> dict:
supply = self.supply()
demand = self.demand()
total_credits = sum(a.credits for a in self.world.agents.values())
return {
"prices": dict(self.prices),
"supply": supply,
"demand": {k: round(v, 1) for k, v in demand.items()},
"total_credits": round(total_credits, 1),
}
>>> main.py
from __future__ import annotations
import random
from models import Agent, CommitStatus
from world_engine import WorldEngine
from world_ledger import WorldLedger
from commit_resolver import CommitResolver
from npc_agent import BDIAgent
from economy import Economy
TOTAL_TICKS = 50
AGENTS_SPEC = [
{"id": "alice", "name": "Alice", "role": "Baker"},
{"id": "bob", "name": "Bob", "role": "Farmer"},
{"id": "carol", "name": "Carol", "role": "Trader"},
]
NPCS_SPEC = [
{"id": "npc_smith", "name": "Smith", "role": "Craftsman"},
{"id": "npc_mayor", "name": "Mayor", "role": "Mayor"},
]
DIVIDER = "=" * 60
THIN_DIVIDER = "-" * 60
WORLD_EVENTS = [
("sunny", "clear skies, productivity up"),
("rainy", "rain slows movement"),
("windy", "gusts scatter resources"),
("drought", "food regen halved"),
("market_boom", "all prices surge"),
("market_crash", "all prices drop"),
("festival", "NPC happiness boosted"),
("fog", "perception radius reduced"),
]
def build_world() -> tuple[WorldEngine, WorldLedger, CommitResolver, Economy, list[BDIAgent]]:
world = WorldEngine(width=10, height=10)
ledger = WorldLedger()
resolver = CommitResolver(world, ledger)
economy = Economy(world)
agents: list[BDIAgent] = []
start_positions = [
(random.randint(0, world.width - 1), random.randint(0, world.height - 1))
for _ in AGENTS_SPEC + NPCS_SPEC
]
for i, spec in enumerate(AGENTS_SPEC + NPCS_SPEC):
agent = Agent(
id=spec["id"],
name=spec["name"],
role=spec["role"],
position=start_positions[i],
credits=round(random.uniform(60, 140), 1),
)
world.add_agent(agent)
agents.append(BDIAgent(agent, world))
return world, ledger, resolver, economy, agents
def maybe_world_event(economy: Economy) -> str | None:
if random.random() > 0.12:
return None
event_name, desc = random.choice(WORLD_EVENTS)
if event_name == "drought":
for r in economy.world.resources:
if r.resource_type == "food":
r.regeneration_rate *= 0.3
elif event_name == "market_boom":
for k in economy.prices:
economy.prices[k] *= random.uniform(1.3, 1.8)
elif event_name == "market_crash":
for k in economy.prices:
economy.prices[k] *= random.uniform(0.4, 0.7)
elif event_name == "festival":
for a in economy.world.agents.values():
a.emotion.boost("happiness", 0.3)
elif event_name == "windy":
random.shuffle(economy.world.resources)
return f"** WORLD EVENT: {event_name} - {desc} **"
def print_agent_line(name: str, role: str, result, event) -> str:
status_sym = {
CommitStatus.COMMIT: " OK ",
CommitStatus.DOWNGRADE: "DOWN",
CommitStatus.REJECT: "REJ ",
}
sym = status_sym.get(result.status, " ???")
ctx_parts = []
for k, v in event.context.items():
if k == "downgrade_reason":
continue
ctx_parts.append(f"{k}={v}")
ctx_str = ",".join(ctx_parts)
reason = ""
if result.status == CommitStatus.DOWNGRADE:
reason = f" [{result.reason}]"
elif result.status == CommitStatus.REJECT:
reason = f" [{result.reason}]"
return f" {name:8s}({role:9s}) {sym} {event.intent.value:7s}({ctx_str}){reason}"
def print_tick_header(tick: int) -> None:
print(f"\n{'═' * 10} Tick {tick:3d} {'═' * 10}")
def print_economy(economy: Economy) -> None:
s = economy.summary()
supply_str = " ".join(f"{k}:{v}" for k, v in s["supply"].items())
price_str = " ".join(f"{k}:{v:.1f}" for k, v in s["prices"].items())
print(f" {THIN_DIVIDER}")
print(f" Economy: supply=[{supply_str}] prices=[{price_str}] M-Credits={s['total_credits']}")
def print_agent_states(agents: list[BDIAgent]) -> None:
print(f" {THIN_DIVIDER}")
for ba in agents:
a = ba.agent
inv_str = ",".join(f"{k}:{v}" for k, v in a.inventory.items() if v > 0) or "empty"
hunger_bar = "#" * int(a.hunger * 10) + "." * (10 - int(a.hunger * 10))
emotion_str = str(a.emotion)
print(
f" {a.name:8s} pos={a.position} credits={a.credits:5.1f} "
f"hunger=[{hunger_bar}] {a.hunger:.2f} inv=[{inv_str}] mood={emotion_str}"
)
def print_summary(ledger: WorldLedger, agents: list[BDIAgent]) -> None:
print(f"\n{DIVIDER}")
print(" SIMULATION COMPLETE")
print(f"{DIVIDER}")
print(f" Total events : {len(ledger)}")
print(f" COMMIT : {ledger.committed_count()}")
print(f" DOWNGRADE : {ledger.downgraded_count()}")
print(f" REJECT : {ledger.rejected_count()}")
print(f"{THIN_DIVIDER}")
print(" Final Agent States:")
for ba in agents:
a = ba.agent
inv_str = ",".join(f"{k}:{v}" for k, v in a.inventory.items() if v > 0) or "empty"
print(
f" {a.name:8s} credits={a.credits:5.1f} inv=[{inv_str}] "
f"mood={a.emotion} events={len(a.last_events)}"
)
print(f"{DIVIDER}")
def main() -> None:
import time as _time
seed = int(_time.time() * 1000) % (2**31)
random.seed(seed)
world, ledger, resolver, economy, agents = build_world()
print(f"{DIVIDER}")
print(" Mini MCOS + CRM Prototype")
print(" Coexistent Reality Model - Autonomous Simulation")
print(f" Agents: {len(AGENTS_SPEC)} players + {len(NPCS_SPEC)} NPCs")
print(f" World: {world.width}x{world.height} grid | Ticks: {TOTAL_TICKS}")
print(f" Seed: {seed}")
print(f"{DIVIDER}")
for tick in range(1, TOTAL_TICKS + 1):
world.advance()
print_tick_header(tick)
world_event = maybe_world_event(economy)
if world_event:
print(f" {world_event}")
order = list(agents)
random.shuffle(order)
for bdi in order:
ev = bdi.tick()
if ev:
resolver.submit(ev)
results = resolver.resolve_all()
for result in results:
for bdi in agents:
bdi.pr.integrate_commit(result)
result_order = sorted(
results, key=lambda r: next(
(i for i, b in enumerate(order) if b.agent.id == r.resolved_event.actor),
len(order),
)
)
for result in result_order:
ev = result.resolved_event
agent = world.get_agent(ev.actor)
if agent:
name = agent.name
role = agent.role
else:
name = ev.actor
role = "?"
print(print_agent_line(name, role, result, ev))
if not results:
print(" (no events this tick)")
economy.update_prices()
print_economy(economy)
print_agent_states(agents)
print_summary(ledger, agents)
if __name__ == "__main__":
main()
>>> models.py
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any
import uuid
class CommitStatus(Enum):
COMMIT = "COMMIT"
DOWNGRADE = "DOWNGRADE"
REJECT = "REJECT"
class Intent(Enum):
MOVE = "MOVE"
GATHER = "GATHER"
WORK = "WORK"
TRADE = "TRADE"
TALK = "TALK"
EAT = "EAT"
IDLE = "IDLE"
ATTACK = "ATTACK"
DIRECTION_VECTORS = {
"north": (0, -1),
"south": (0, 1),
"east": (1, 0),
"west": (-1, 0),
}
@dataclass
class Event:
actor: str
intent: Intent
context: dict[str, Any] = field(default_factory=dict)
timestamp: int = 0
id: str = field(default_factory=lambda: uuid.uuid4().hex[:8])
def __str__(self) -> str:
ctx = ",".join(f"{k}={v}" for k, v in self.context.items())
return f"[{self.id}] {self.actor} {self.intent.value}({ctx})"
@dataclass
class CommitResult:
status: CommitStatus
resolved_event: Event
reason: str = ""
@dataclass
class EmotionState:
happiness: float = 0.5
anger: float = 0.0
fear: float = 0.0
sadness: float = 0.0
def decay(self, rate: float = 0.05) -> None:
self.happiness = max(0.0, self.happiness * 0.92)
self.anger = max(0.0, self.anger * 0.88)
self.fear = max(0.0, self.fear * 0.88)
self.sadness = max(0.0, self.sadness * 0.88)
def boost(self, emotion: str, amount: float) -> None:
current = getattr(self, emotion, 0.0)
setattr(self, emotion, max(0.0, min(1.0, current + amount)))
def dominant(self) -> tuple[str, float]:
m = {
"happy": self.happiness,
"angry": self.anger,
"afraid": self.fear,
"sad": self.sadness,
}
best = max(m, key=m.get)
return best, m[best]
def __str__(self) -> str:
name, val = self.dominant()
return f"{name}({val:.1f})"
@dataclass
class Agent:
id: str
name: str
role: str
position: tuple[int, int] = (0, 0)
credits: float = 100.0
inventory: dict[str, int] = field(default_factory=dict)
hunger: float = 0.0
beliefs: dict[str, Any] = field(default_factory=dict)
desires: list[str] = field(default_factory=lambda: ["survive", "prosper"])
emotion: EmotionState = field(default_factory=EmotionState)
lod_level: int = 2
last_events: list[str] = field(default_factory=list)
def perceive_radius(self) -> int:
return 3 if self.lod_level >= 2 else 1
def hunger_rate(self) -> float:
return 0.08
def is_npc(self) -> bool:
return self.id.startswith("npc_")
def __str__(self) -> str:
return f"{self.name}({self.role})@{self.position}"
@dataclass
class ResourceNode:
resource_type: str
position: tuple[int, int]
quantity: int = 10
regeneration_rate: float = 0.5
depleted: bool = False
_regen_buf: float = 0.0
def regen(self) -> None:
if self.depleted:
self.quantity = min(10, self.quantity + 1)
if self.quantity > 2:
self.depleted = False
else:
self._regen_buf += self.regeneration_rate
if self._regen_buf >= 1.0:
self.quantity = min(10, self.quantity + int(self._regen_buf))
self._regen_buf -= int(self._regen_buf)
def harvest(self, amount: int = 1) -> int:
actual = min(amount, self.quantity)
self.quantity -= actual
if self.quantity <= 0:
self.depleted = True
return actual
>>> npc_agent.py
from __future__ import annotations
import random
from models import Agent, Event, Intent, DIRECTION_VECTORS
from world_engine import WorldEngine
from personal_reality import PersonalReality
ROLE_BEHAVIOR = {
"Farmer": {
"work_intent": Intent.GATHER,
"work_context": {"resource_type": "food"},
"trade_item": "food",
"trade_price_range": (3, 6),
},
"Baker": {
"work_intent": Intent.GATHER,
"work_context": {"resource_type": "food"},
"trade_item": "food",
"trade_price_range": (4, 7),
},
"Trader": {
"work_intent": Intent.GATHER,
"work_context": {"resource_type": "wood"},
"trade_item": "wood",
"trade_price_range": (5, 8),
},
"Craftsman": {
"work_intent": Intent.GATHER,
"work_context": {"resource_type": "ore"},
"trade_item": "ore",
"trade_price_range": (6, 10),
},
"Mayor": {
"work_intent": Intent.IDLE,
"work_context": {},
"trade_item": None,
"trade_price_range": (0, 0),
},
}
class BDIAgent:
def __init__(self, agent: Agent, world: WorldEngine) -> None:
self.agent = agent
self.world = world
self.pr = PersonalReality(agent)
self._work_counter = 0
self._personality = {
"boldness": random.uniform(0.2, 0.8),
"sociability": random.uniform(0.2, 0.8),
"greed": random.uniform(0.2, 0.8),
"patience": random.uniform(0.2, 0.8),
}
def tick(self) -> Event | None:
self._sync_pr()
event = self._decide()
self.agent.emotion.decay(0.03)
if self.agent.hunger > 0.7:
self.agent.emotion.boost("sadness", 0.02)
if self.agent.hunger > 0.9:
self.agent.emotion.boost("fear", 0.01)
return event
def _sync_pr(self) -> None:
nearby_a = self.world.nearby_agents(self.agent)
nearby_r = self.world.nearby_resources(self.agent)
self.pr.sync_from_world(nearby_a, nearby_r)
def _decide(self) -> Event:
ts = self.world.tick
if self.agent.hunger > 0.7 and self.agent.inventory.get("food", 0) > 0:
return Event(
actor=self.agent.id,
intent=Intent.EAT,
context={"reason": "hungry"},
timestamp=ts,
)
if self.agent.hunger > 0.9:
return self._find_food(ts)
if self.agent.hunger > 0.6 and random.random() < 0.4:
return self._find_food(ts)
role_cfg = ROLE_BEHAVIOR.get(self.agent.role, ROLE_BEHAVIOR["Mayor"])
if self._should_trade():
return self._do_trade(ts, role_cfg)
if self._should_work():
return self._do_work(ts, role_cfg)
if random.random() < self._personality["sociability"] * 0.5:
return self._do_talk(ts)
if random.random() < 0.15:
return self._do_attack(ts)
return self._do_explore(ts)
def _should_work(self) -> bool:
self._work_counter += 1
work_frequency = random.randint(2, 5)
return self._work_counter % work_frequency == 0
def _should_trade(self) -> bool:
return self.agent.inventory.get(self._role_trade_item(), 0) > 0
def _role_trade_item(self) -> str:
cfg = ROLE_BEHAVIOR.get(self.agent.role, {})
return cfg.get("trade_item", "") or ""
def _find_food(self, ts: int) -> Event:
resources = self.world.nearby_resources(self.agent, radius=5)
food = [r for r in resources if r.resource_type == "food"]
if food:
nearest = min(
food,
key=lambda r: abs(r.position[0] - self.agent.position[0])
+ abs(r.position[1] - self.agent.position[1]),
)
self._move_toward(nearest.position)
if nearest.position == self.agent.position:
return Event(
actor=self.agent.id,
intent=Intent.GATHER,
context={"resource_type": "food", "position": list(nearest.position)},
timestamp=ts,
)
return self._do_explore(ts)
def _do_work(self, ts: int, cfg: dict) -> Event:
intent = cfg["work_intent"]
ctx = dict(cfg["work_context"])
if intent == Intent.GATHER:
resources = self.world.nearby_resources(self.agent)
typed = [r for r in resources if r.resource_type == ctx.get("resource_type")]
if typed:
target = min(
typed,
key=lambda r: abs(r.position[0] - self.agent.position[0])
+ abs(r.position[1] - self.agent.position[1]),
)
self._move_toward(target.position)
if target.position == self.agent.position:
return Event(
actor=self.agent.id,
intent=Intent.WORK,
context={
"payment": random.randint(3, 7),
"resource_type": ctx.get("resource_type"),
"position": list(target.position),
},
timestamp=ts,
)
return self._do_explore(ts)
if intent == Intent.TRADE:
targets = self.world.nearby_agents(self.agent)
trade_targets = [t for t in targets if t.id != self.agent.id]
if trade_targets:
target = random.choice(trade_targets)
ctx["target"] = target.id
return Event(
actor=self.agent.id, intent=intent, context=ctx, timestamp=ts
)
return self._do_explore(ts)
return Event(actor=self.agent.id, intent=intent, context=ctx, timestamp=ts)
def _do_trade(self, ts: int, cfg: dict) -> Event:
targets = self.world.nearby_agents(self.agent)
trade_targets = [t for t in targets if t.id != self.agent.id]
if not trade_targets:
return self._do_explore(ts)
target = random.choice(trade_targets)
item = cfg.get("trade_item", "food")
lo, hi = cfg.get("trade_price_range", (3, 6))
cost = random.randint(lo, hi)
return Event(
actor=self.agent.id,
intent=Intent.TRADE,
context={"target": target.id, "item": item, "cost": cost},
timestamp=ts,
)
def _do_talk(self, ts: int) -> Event:
targets = self.world.nearby_agents(self.agent)
if not targets:
return self._do_explore(ts)
target = random.choice(targets)
return Event(
actor=self.agent.id,
intent=Intent.TALK,
context={"target": target.id, "topic": random.choice(["weather", "work", "life"])},
timestamp=ts,
)
def _do_explore(self, ts: int) -> Event:
if random.random() < self._personality["boldness"] * 0.3:
targets = self.world.nearby_agents(self.agent, radius=2)
others = [t for t in targets if t.id != self.agent.id]
if others:
return self._do_attack(ts)
direction = random.choice(list(DIRECTION_VECTORS.keys()))
return Event(
actor=self.agent.id,
intent=Intent.MOVE,
context={"direction": direction},
timestamp=ts,
)
def _do_attack(self, ts: int) -> Event:
targets = self.world.nearby_agents(self.agent, radius=2)
others = [t for t in targets if t.id != self.agent.id]
if not others:
return self._do_explore(ts)
target = random.choice(others)
return Event(
actor=self.agent.id,
intent=Intent.ATTACK,
context={"target": target.id, "reason": random.choice(["provoked", "robbery", "dispute"])},
timestamp=ts,
)
def _move_toward(self, target: tuple[int, int]) -> None:
ax, ay = self.agent.position
tx, ty = target
if ax < tx:
self.world.move_agent(self.agent, "east")
elif ax > tx:
self.world.move_agent(self.agent, "west")
elif ay < ty:
self.world.move_agent(self.agent, "south")
elif ay > ty:
self.world.move_agent(self.agent, "north")
>>> personal_reality.py
from __future__ import annotations
from models import Agent, Event, ResourceNode
class PersonalReality:
def __init__(self, agent: Agent) -> None:
self.agent = agent
self.known_agents: dict[str, Agent] = {}
self.known_resources: dict[tuple[int, int], ResourceNode] = {}
self.pending_events: list[Event] = []
self.local_time: int = 0
def sync_from_world(self, nearby_agents: list[Agent], nearby_resources: list[ResourceNode]) -> None:
self.known_agents.clear()
for a in nearby_agents:
self.known_agents[a.id] = a
self.known_resources.clear()
for r in nearby_resources:
self.known_resources[r.position] = r
def integrate_commit(self, result) -> None:
ev = result.resolved_event
if ev.actor == self.agent.id:
self.agent.last_events.append(
f"T{ev.timestamp}:{result.status.value}:{ev.intent.value}"
)
def perceive(self) -> dict:
return {
"position": self.agent.position,
"hunger": self.agent.hunger,
"credits": self.agent.credits,
"inventory": dict(self.agent.inventory),
"nearby_agents": list(self.known_agents.keys()),
"nearby_resources": [
(r.position, r.resource_type, r.quantity)
for r in self.known_resources.values()
],
}
>>> world_engine.py
from __future__ import annotations
import random
from models import Agent, ResourceNode, DIRECTION_VECTORS
class WorldEngine:
def __init__(self, width: int = 10, height: int = 10):
self.width = width
self.height = height
self.tick = 0
self.agents: dict[str, Agent] = {}
self.resources: list[ResourceNode] = []
self._build_resource_nodes()
def _build_resource_nodes(self) -> None:
spots = set()
for _ in range(8):
while True:
pos = (random.randint(0, self.width - 1), random.randint(0, self.height - 1))
if pos not in spots:
spots.add(pos)
break
rtype = random.choice(["food", "wood", "ore"])
self.resources.append(ResourceNode(resource_type=rtype, position=pos))
def add_agent(self, agent: Agent) -> None:
self.agents[agent.id] = agent
def get_agent(self, agent_id: str) -> Agent | None:
return self.agents.get(agent_id)
def move_agent(self, agent: Agent, direction: str) -> bool:
dx, dy = DIRECTION_VECTORS.get(direction, (0, 0))
nx, ny = agent.position[0] + dx, agent.position[1] + dy
if 0 <= nx < self.width and 0 <= ny < self.height:
agent.position = (nx, ny)
return True
return False
def find_resource_at(self, pos: tuple[int, int], rtype: str | None = None) -> ResourceNode | None:
for r in self.resources:
if r.position == pos and not r.depleted:
if rtype is None or r.resource_type == rtype:
return r
return None
def agents_at(self, pos: tuple[int, int], exclude: str = "") -> list[Agent]:
return [a for a in self.agents.values() if a.position == pos and a.id != exclude]
def nearby_agents(self, agent: Agent, radius: int | None = None) -> list[Agent]:
if radius is None:
radius = agent.perceive_radius()
ax, ay = agent.position
result = []
for a in self.agents.values():
if a.id == agent.id:
continue
dx = abs(a.position[0] - ax)
dy = abs(a.position[1] - ay)
if dx <= radius and dy <= radius:
result.append(a)
return result
def nearby_resources(self, agent: Agent, radius: int | None = None) -> list[ResourceNode]:
if radius is None:
radius = agent.perceive_radius()
ax, ay = agent.position
return [
r for r in self.resources
if not r.depleted
and abs(r.position[0] - ax) <= radius
and abs(r.position[1] - ay) <= radius
]
def update_lod(self) -> None:
for agent in self.agents.values():
nearby = self.nearby_agents(agent, radius=2)
if len(nearby) > 0:
agent.lod_level = 2
else:
agent.lod_level = 1
def tick_resources(self) -> None:
for r in self.resources:
r.regen()
if random.random() < 0.08:
r.quantity = min(15, r.quantity + random.randint(1, 3))
def tick_hunger(self) -> None:
for agent in self.agents.values():
hunger_var = random.gauss(0, 0.01)
agent.hunger = min(1.0, max(0.0, agent.hunger + agent.hunger_rate() + hunger_var))
def advance(self) -> int:
self.tick += 1
self.tick_resources()
self.tick_hunger()
self.update_lod()
return self.tick
def resource_summary(self) -> dict[str, int]:
summary: dict[str, int] = {}
for r in self.resources:
if not r.depleted:
summary[r.resource_type] = summary.get(r.resource_type, 0) + r.quantity
return summary
>>> world_ledger.py
from __future__ import annotations
from models import Event, CommitResult, CommitStatus
class WorldLedger:
def __init__(self) -> None:
self.entries: list[CommitResult] = []
self._event_index: dict[str, CommitResult] = {}
def append(self, result: CommitResult) -> None:
self.entries.append(result)
self._event_index[result.resolved_event.id] = result
def get(self, event_id: str) -> CommitResult | None:
return self._event_index.get(event_id)
def recent(self, n: int = 5) -> list[CommitResult]:
return self.entries[-n:]
def events_by(self, actor: str) -> list[CommitResult]:
return [e for e in self.entries if e.resolved_event.actor == actor]
def committed_count(self) -> int:
return sum(1 for e in self.entries if e.status == CommitStatus.COMMIT)
def downgraded_count(self) -> int:
return sum(1 for e in self.entries if e.status == CommitStatus.DOWNGRADE)
def rejected_count(self) -> int:
return sum(1 for e in self.entries if e.status == CommitStatus.REJECT)
def __len__(self) -> int:
return len(self.entries)
留言
張貼留言