🌍
構造主観力学で作る「生きている」NPC:原始村落シミュレーション
はじめに
「ゲームのNPCをもっと生き生きとさせたい」
「単純なルールから複雑な社会が生まれる様子を見てみたい」
そんな想いから、**構造主観力学(Structural Subjectivity Dynamics: SSD)**という理論フレームワークを使って、原始的な村落のシミュレーションを作りました。
本記事では、このシミュレーションがどのように動作し、なぜ「生きている」と感じられるNPCが生まれるのかを解説します。
この記事で分かること
- 構造主観力学(SSD)の基本概念
- SSDをNPC AIに実装する方法
- シミュレーション結果と創発的な社会行動
- コードの実装詳細とカスタマイズ方法
対象読者
- ゲームAIに興味がある方
- シミュレーション開発に取り組んでいる方
- 理論を実装に落とし込む過程を知りたい方
原始村落シミュレーション
構造主観力学(SSD)とは?
核心概念:安定と変化の力学
SSDは、世界のあらゆる事象を「安定(整合)」と「変化(跳躍)」の動的バランスとして捉える理論です。
3つの基本要素
1. 意味圧(Meaning Pressure)
構造に作用するあらゆるエネルギーや影響。
- NPCにとっての「空腹」「疲労」「脅威」など
2. 整合(Alignment)
構造が意味圧に対して安定を保とうとするプロセス。
- 食べ物を探す、休息する、仲間と協力する
3. 跳躍(Leap)
整合の限界を超えた時に発生する非連続的な変化。
- このシミュレーションでは「死」として表現
SSDをNPCに実装する
コアシステムの数理モデル
NPCは以下の内部状態を持ちます:
class NPCWithTerritory:
def __init__(self, name, preset, env, roster_ref, start_pos):
# 生理的状態
self.hunger = 50.0 # 空腹度
self.fatigue = 30.0 # 疲労度
self.injury = 0.0 # 負傷度
# SSD核心パラメータ
self.kappa = defaultdict(lambda: 0.1) # 整合慣性
self.E = 0.0 # 未処理圧(熱)
self.T = 0.3 # 探索温度
# 社会的パラメータ
self.rel = defaultdict(float) # 他者との関係性
self.empathy = preset["empathy"] # 共感性
self.risk_tolerance = preset["risk_tolerance"]
整合慣性(κ: kappa)
「慣れ」や「学習」を表現する重要な概念
def update_kappa(self, action_type, success, reward):
"""行動の成功体験を蓄積する"""
kappa = self.kappa[action_type]
if success:
# 成功すればκが増加 → 次回その行動が取りやすくなる
work = self.eta * reward
else:
# 失敗すればκが減少
work = -self.rho * (kappa ** 2)
# 忘却も考慮
self.kappa[action_type] = max(
self.kappa_min,
kappa + work - (self.lambda_forget * (kappa - self.kappa_min))
)
κの効果:
- κが高い行動 → その行動を取りやすくなる(習慣化)
- κが低い行動 → その行動を避ける傾向
未処理圧(E: Heat)
処理しきれないストレスの蓄積
def update_heat(self, meaning_pressure, processed_amount):
"""意味圧が処理しきれないと熱として蓄積"""
unprocessed = max(0, meaning_pressure - processed_amount)
self.E = max(0,
self.E + self.alpha * unprocessed - self.beta_E * self.E
)
Eの効果:
- Eが高い → 探索温度Tが上昇 → ランダムな行動が増える
- Eが閾値超過 → 「跳躍」(このシミュレーションでは死の確率上昇)
シミュレーションの進化
レベル1:基本的な生存
# 基本的な行動選択
if self.hunger > 70:
self.forage() # 採餌
elif self.fatigue > 80:
self.rest() # 休息
else:
self.patrol() # パトロール
レベル2:昼夜サイクルの導入
class DayNightCycle:
def get_sleep_pressure(self):
"""夜になると睡眠圧が上昇"""
time = self.get_time_of_day()
if self.night_start <= time < self.night_end:
return 1.0
return 0.1
効果:
- NPCが夜に寝るようになる
- 睡眠中に記憶が定着(κが強化される)
レベル3:縄張り意識
class Territory:
def __init__(self, owner, center, radius):
self.owner = owner
self.center = center # 縄張りの中心
self.radius = radius # 縄張りの範囲
self.memory = {
"food_found": 0,
"helped_here": 0,
"rested": 0
}
縄張りの効果:
- ホームボーナス:縄張り内での睡眠は回復量1.5倍
- 侵入者検知:他のNPCが侵入すると警戒・追跡
- 愛着形成:成功体験が積まれた場所に戻りたくなる
レベル4:集団狩猟と社会行動
戦闘力の動的計算
def combat_power(self, group_bonus=0.0):
"""NPCの戦闘力を計算"""
k = self.kappa.get("hunt", 0.1)
xp_term = 1.0 + 0.15 * np.tanh(self.combat_xp / 30.0)
fatigue_pen = 1.0 - 0.35 * (self.fatigue / 120.0)
injury_pen = 1.0 - 0.50 * (self.injury / 120.0)
inertia_boost = 1.0 + 0.25 * self.align_inertia
return (
self.combat_power_base *
(1.0 + 0.25 * k) *
xp_term *
fatigue_pen *
injury_pen *
inertia_boost
) * (1.0 + group_bonus)
戦闘力に影響する要因:
- 狩りの整合慣性(κ)
- 戦闘経験値
- 疲労度・負傷度
- 整合慣性(align_inertia)
- グループボーナス
集団狩猟のメカニズム
def emit_rally_for_hunt(self, t, hunt_node=None, min_k=2, ttl=10):
"""狩りの呼びかけ"""
self.rally_state = {
"leader": self.name,
"ttl": ttl, # 有効時間
"node": hunt_node, # 目標地点
"min_k": min_k, # 最小人数
"kind": "hunt"
}
集団狩猟の流れ:
- リーダーが呼びかけ(
emit_rally_for_hunt) - 仲間が呼びかけに応答(
consider_join_rally) - 十分な人数が集まったら実行(
attempt_rally_group_hunt) - 成功すれば全員で報酬を分配
捕食者への対応
def emit_rally_predator(self, t, node, min_k=3, ttl=12):
"""捕食者討伐の呼びかけ"""
self.rally_state = {
"leader": self.name,
"ttl": ttl,
"node": node,
"min_k": min_k,
"kind": "predator" # 種類を区別
}
捕食者との戦闘:
- 捕食者が特定エリアに留まると(camping)検知
- NPCが自発的に討伐隊を結成
- グループサイズと戦闘力で勝率が変動
- 成功すれば大量の食料と経験値
優先度ベースの意思決定
階層的な行動選択
def step(self, t, predator=None):
# L1: 生命維持のガードレール
if self.hunger >= 95:
self.emergency_forage()
return
if self.injury >= 85 or self.should_sleep():
self.move_to_home_and_sleep()
return
# L2: 協調行動
if self.state == "rallying":
self.follow_rally_leader()
return
if predator and predator.is_camping():
self.emit_rally_predator()
if self.rally_state["leader"] == self.name:
self.attempt_group_action()
return
# L3: 個人の欲求
if self.hunger > self.TH_H:
self.forage()
return
# L4: 社会行動
self.social_gravity_move()
self.copresence_tick()
self.apply_triadic_closure()
優先度の意味:
- L1:生命維持 - 何よりも優先
- L2:協力行動 - 個人より集団の利益
- L3:個人欲求 - 通常の生活行動
- L4:社会行動 - 余裕があれば
創発的な社会行動
関係性の自然な発展
三者関係の形成
def apply_triadic_closure(self):
"""友達の友達は友達"""
for a in self.nearby_allies():
for b in self.nearby_allies():
if self.rel[a.name] > 0.2 and self.rel[b.name] > 0.2:
# AとBも仲良くなる
inc = 0.03 * min(self.rel[a.name], self.rel[b.name])
a.rel[b.name] += inc
b.rel[a.name] += inc
社会的引力
def social_gravity_move(self):
"""仲の良い相手に自然と近づく"""
cands = self.nearby_allies(radius=6)
if cands:
best = max(cands, key=lambda o: self.rel[o.name])
if random.random() < 0.25:
self.move_towards(best.pos())
NPCのパーソナリティプリセット
# 採餌者:安全志向、共感性高い
FORAGER = {
"risk_tolerance": 0.2,
"curiosity": 0.3,
"avoidance": 0.8,
"stamina": 0.6,
"empathy": 0.8
}
# 追跡者:リスクを取る、体力が高い
TRACKER = {
"risk_tolerance": 0.6,
"curiosity": 0.5,
"avoidance": 0.2,
"stamina": 0.8,
"empathy": 0.6
}
# 放浪者:超高リスク志向、共感性低い
NOMAD = {
"risk_tolerance": 0.7,
"curiosity": 0.8,
"avoidance": 0.1,
"stamina": 0.9,
"empathy": 0.3
}
パーソナリティの効果:
-
empathyが高い → 他者を助けやすい -
risk_toleranceが高い → 危険な狩りに挑戦 -
avoidanceが高い → 縄張り意識が強い
実装のポイント
1. 整合慣性(κ)の調整
# 学習が速すぎる場合
self.eta = 0.2 # 学習率を下げる
# 忘れやすすぎる場合
self.lambda_forget = 0.01 # 忘却率を下げる
# 最小値の調整
self.kappa_min = 0.05 # 完全に忘れないようにする
2. 熱の蓄積速度
# ストレスが溜まりすぎる場合
self.alpha = 0.4 # 蓄積係数を下げる
self.beta_E = 0.20 # 減衰係数を上げる
# ストレスが溜まらない場合
self.alpha = 0.8
self.beta_E = 0.10
3. 集団行動の調整
# 呼びかけに応じやすくする
def consider_join_rally(self, t):
# ...
u = base + 1.0 # 基準値を上げる
# ...
# 必要人数を調整
self.emit_rally_for_hunt(t, min_k=2) # 2人から開始
シミュレーション結果の分析
基本的な統計
if __name__ == "__main__":
final_npcs, df_logs = run_sim(TICKS=400)
# 生存者数
survivors = sum(1 for n in final_npcs if n.alive)
print(f"Survivors: {survivors} / {len(final_npcs)}")
# 捕食者討伐の成功率
pred_hunts = df_logs[df_logs['action'].str.contains("predator_slay")]
success_rate = len(pred_hunts[pred_hunts['action'] == 'predator_slay_success']) / len(pred_hunts)
print(f"Predator Hunt Success Rate: {success_rate:.2%}")
詳細な分析例
# 行動パターンの分析
action_counts = df_logs['action'].value_counts()
print("\n行動の分布:")
print(action_counts.head(10))
# 関係性の強さ
for npc in final_npcs:
if npc.alive:
strong_bonds = {name: rel for name, rel in npc.rel.items() if rel > 0.5}
print(f"{npc.name}: {len(strong_bonds)} strong bonds")
# 縄張りの分析
for npc in final_npcs:
if npc.alive:
attachment = npc.territory.attachment_strength
print(f"{npc.name} territory attachment: {attachment:.2f}")
カスタマイズのアイデア
1. 新しい行動の追加
def build_shelter(self, t):
"""避難所を建設する"""
if self.fatigue < 50 and self.hunger < 70:
# 建設コスト
self.fatigue += 20
self.hunger += 10
# 縄張りを強化
self.territory.radius += 1
self.territory.attachment_strength += 0.1
self.update_kappa("build", True, 30)
self.log.append({"t": t, "name": self.name, "action": "build_shelter"})
2. 資源の追加
class Environment:
def __init__(self):
# ...
self.water_sources = {} # 水源
self.tool_materials = {} # 道具の材料
self.medicinal_herbs = {} # 薬草
3. スキルシステム
class NPC:
def __init__(self):
# ...
self.skills = {
"hunting": 0.0,
"foraging": 0.0,
"medicine": 0.0,
"crafting": 0.0
}
def gain_skill_xp(self, skill_name, amount):
self.skills[skill_name] += amount
# スキルレベルに応じて成功率が上がる
4. 季節の変化
class SeasonalCycle:
def __init__(self):
self.seasons = ["spring", "summer", "autumn", "winter"]
self.current_season = 0
def get_season_modifier(self):
season = self.seasons[self.current_season]
if season == "winter":
return {"forage_success": 0.3, "energy_cost": 1.5}
elif season == "summer":
return {"forage_success": 1.2, "energy_cost": 0.9}
# ...
まとめ
SSDを使うメリット
-
シンプルな原理から複雑な行動が創発
- κ、E、Tという3つのパラメータで多様な行動
-
学習と適応が自然に組み込まれる
- 成功体験が自動的に蓄積される
-
個性の表現が容易
- パーソナリティプリセットで多様なキャラクター
-
社会行動が自然に発生
- 明示的にプログラムしなくても協力行動が生まれる
今後の展開
- より高度な認知モデルの実装
- リアルタイムビジュアライゼーション
- ゲームエンジンへの統合(Unity/Unreal)
- マルチエージェント強化学習との統合
参考リンク
最後に
このシミュレーションは、「生きている」と感じられるNPCを作るための一つのアプローチです。
SSDという理論フレームワークを使うことで:
- NPCが自律的に判断する
- 環境に適応して学習する
- 他者と協力する
- 個性的な行動を取る
これらが自然に実現できることを示しました。
皆さんもぜひ、このコードをベースに独自のシミュレーションを作ってみてください!
Discussion