South town MVP: world sim, seasons, granary, contrast test + findings
Godot 4.7 C# world simulation per SPEC.md (with 2026-07-21 amendments). 15 NPCs, 5-state machine, demand-driven work, soul-imprint triad data model, seasons with winter frost, granary, need-based gift economy, selfish-soul contrast wiring, daily/per-villager/gift CSV logging. FINDINGS.md documents the four soak experiments: the dead paradise, the buffers winning, the emergent hungry gap, and the contrast test proving the four-town thesis (and that selfishness wins until the soul-consequence layer exists). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Godot
|
||||
.godot/
|
||||
*.translation
|
||||
export_presets.cfg
|
||||
|
||||
# .NET
|
||||
bin/
|
||||
obj/
|
||||
*.csproj.user
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# WorldSim — Soak Experiment Findings (July 2026)
|
||||
|
||||
Four long-run experiments against the South (Nature) town MVP. Each run:
|
||||
15 NPCs, 256×256 world, seed 12345, ~240 simulated days (2 years) at
|
||||
accelerated speed. Data: `soak_stats.csv` (daily), `soak_npc_days.csv`
|
||||
(per-villager dominant states), `soak_gifts.csv` (every gift, with names).
|
||||
|
||||
---
|
||||
|
||||
## Run 1 — Baseline (no seasons): the dead paradise
|
||||
|
||||
**Setup:** constant regeneration, upkeep + food demand only.
|
||||
|
||||
**Result:** perfectly stable, perfectly inert. 200 days: wood held a ±1%
|
||||
band, shelter condition pinned at 100%, **zero trades**, no roles — every
|
||||
villager Socializing-dominant ~114 of 119 days, 78% of waking life idle.
|
||||
The village completed all construction and expansion by day 3–4 and then
|
||||
had nothing left to want.
|
||||
|
||||
**Lesson:** a village is a metabolism, not a project. Equilibrium without
|
||||
pressure produces no society: no differentiation, no exchange, no
|
||||
structure. Abundance makes generosity unobservable.
|
||||
|
||||
---
|
||||
|
||||
## Run 2 — Seasons (regen throttle only): the buffers win
|
||||
|
||||
**Setup:** 30-day seasons; winter cuts food regen to 5%, wood to 40%.
|
||||
Granary added (clay walls — first clay use), need-based gift trigger.
|
||||
|
||||
**Result:** winter became a *worked* season — gathering 4,889 ticks/day vs
|
||||
1,857 in summer, wild stocks visibly drained and refilled. But nourishment
|
||||
ran the identical sawtooth year-round, the granary sat untouched at
|
||||
150/150 through two winters, and trades stayed at **zero** across 240 days.
|
||||
|
||||
**Lesson:** three compounding buffers (the sustainability floor acting as a
|
||||
~360-unit hidden savings account, personal pack reserves, wild food budget
|
||||
exceeding winter need) meant scarcity never reached an individual. Pressure
|
||||
that only raises labor doesn't create interdependence.
|
||||
|
||||
---
|
||||
|
||||
## Run 3 — Frost: the hungry gap
|
||||
|
||||
**Setup:** winter now *decays* standing food ~7%/day toward 15% (frost
|
||||
kills the larder); granary draws deepened; anti-redeposit rule.
|
||||
|
||||
**Result:** the simulation came alive.
|
||||
- **First trades in sim history: 21** (17 deep winter, 4 early spring) —
|
||||
exactly when granary-empty met neighbor-with-food.
|
||||
- **Granary sawtooth textbook**: full through autumn, empty by winter day
|
||||
~25, refilled within 4 days of spring. Both years.
|
||||
- **The sim reproduced the "hungry gap"** — a real agricultural phenomenon
|
||||
neither designed nor named in the code: worst hunger (village nourishment
|
||||
0.21) fell in *early spring*, after stores ran out but before the land
|
||||
regrew past harvestable thresholds.
|
||||
- Crisis cascaded: the food emergency pulled labor from heating (firewood
|
||||
reserves dipped to 10, condition below 100% for the first time).
|
||||
- Balanced: severe hunger, zero lasting health damage, both years.
|
||||
|
||||
**Lesson:** storage limits + decay + regrowth lag ⇒ emergent seasonality of
|
||||
need. Sharing is a *response*, not a behavior: 480 days of abundance
|
||||
produced zero exchanges; ten days of genuine need produced twenty-one.
|
||||
Still missing: individual roles — identical souls in a global work queue
|
||||
respond identically (winter gather ticks all within 490–553).
|
||||
|
||||
---
|
||||
|
||||
## Run 4 — The contrast test: 3 selfish souls among 12
|
||||
|
||||
**Setup:** villagers M/N/O run the East (Selfish) soul profile. Same
|
||||
engine, same possibility space; only behavior weights differ: low
|
||||
sustainability (strip nodes to ~15% vs the 50% commons floor), low
|
||||
generosity (refuse gifts; excluded from asking), low community bias (no
|
||||
communal work at all), high accumulation (double carry, standing hoard).
|
||||
|
||||
**Result — the four-town thesis validated:**
|
||||
- Selfish villager-days: **0 build ticks** (vs 104/day for nature souls),
|
||||
gather 69 vs 269, **rest 0** (never tired), socializing 1,369 vs 921.
|
||||
A visible leisure class living off a working village.
|
||||
- Gift network: 11 gifts, all nature→nature. The selfish never gave and
|
||||
never needed to receive. First individual differentiation appeared:
|
||||
**Villager_A gave 4 of 11 gifts** — the village's generous one.
|
||||
- The 12 communal souls absorbed 15 souls' worth of burden without a
|
||||
single ruin.
|
||||
|
||||
**The central finding: selfishness strictly won.** Most leisure, least
|
||||
fatigue, fullest bellies, zero costs, no pushback. This is not a balance
|
||||
bug — it is the empirical case for the game's soul layer. Economics alone
|
||||
cannot make selfishness cost anything; that is the job of imprints,
|
||||
debris, echoes, and social memory (the Unity POC's warmth-echo system).
|
||||
The soul-consequence layer is not flavor on top of the economy — it is
|
||||
the only thing standing between the South town and being farmed by the
|
||||
East.
|
||||
|
||||
---
|
||||
|
||||
## Next
|
||||
|
||||
1. **Port the soul layer into the sim**: imprint triad accumulation,
|
||||
social memory of gifts/refusals, consequences routed through behavior
|
||||
(the generous get helped first; the ungenerous get asked last, served
|
||||
last, remembered coldly). Rerun the contrast test; see if the village
|
||||
organically stops carrying its parasites.
|
||||
2. Instrumentation gap: per-villager nourishment/health in
|
||||
`soak_npc_days.csv` (currently the village average hides who starves).
|
||||
3. Save/load round-trip (SPEC §9 amendment) — still outstanding.
|
||||
@@ -0,0 +1,11 @@
|
||||
[gd_scene format=3 uid="uid://d4nynm8e2xnyj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cvugyvd1fuj7l" path="res://scripts/GameManager.cs" id="1_gm"]
|
||||
[ext_resource type="Script" uid="uid://b822hloehyggd" path="res://scripts/Visualization.cs" id="2_viz"]
|
||||
|
||||
[node name="Main" type="Node2D" unique_id=1549676286]
|
||||
script = ExtResource("1_gm")
|
||||
TicksPerRealSecond = 1500.0
|
||||
|
||||
[node name="Visualization" type="Node2D" parent="." unique_id=1946930493]
|
||||
script = ExtResource("2_viz")
|
||||
@@ -0,0 +1,35 @@
|
||||
# Soul World Sim — MVP
|
||||
|
||||
Godot 4.6 (.NET / C#) world simulation for the soul reincarnation game.
|
||||
|
||||
- **Design rationale**: `H:\Projects\Game\Game\DESIGN.md` (the *why*)
|
||||
- **Production spec**: `H:\Projects\Game\Game\SPEC.md` (the *what*, with 2026-07-21 amendments)
|
||||
- **Mechanics reference**: the Unity POC at `H:\Projects\Game\Game` (soul/debris/imprint systems, proven Feb 2026)
|
||||
|
||||
## MVP goal
|
||||
|
||||
Prove constrained emergence + soul philosophy with one town: 15 nature-souled
|
||||
NPCs dropped into a 256×256 world with no scripted layout. Success is
|
||||
behavioral (SPEC §11): no over-harvesting, voluntary cooperation, an organic
|
||||
community cluster, stable resource bands — then the contrast test (3 selfish
|
||||
NPCs dropped in) to prove weights alone change the society.
|
||||
|
||||
## Structure
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `scripts/GameManager.cs` | main loop, tick system (1 tick = 1 in-game minute), day/night |
|
||||
| `scripts/World.cs` | 256×256 grid, resource nodes, sustainable harvest, daily regen |
|
||||
| `scripts/NPC.cs` | NPC data + 5-state machine (gather/build/trade/rest/socialize) |
|
||||
| `scripts/SoulProfile.cs` | soul types, behavior weights, **imprint triad** (SPEC §2 amendment) |
|
||||
| `scripts/Building.cs` | modular, physically-sequential, multi-NPC construction |
|
||||
| `scripts/TradeSystem.cs` | generous no-currency exchange, imprint consequences |
|
||||
| `scripts/Visualization.cs` | top-down debug view (designer sees all meters; the player never will) |
|
||||
| `scripts/SaveSystem.cs` | world-state JSON round-trip (persistence is first-class) |
|
||||
|
||||
## Build weeks (SPEC §12)
|
||||
|
||||
1. World gen + NPC base + state machine ← *scaffold is here*
|
||||
2. Building system
|
||||
3. Trade + soul-imprint-weighted decisions
|
||||
4. Testing protocol + save round-trip + debug UI
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.7.1">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<RootNamespace>WorldSim</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorldSim", "WorldSim.csproj", "{8A1F42C7-3B6E-4D29-9C51-D0E7B4A6F213}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8A1F42C7-3B6E-4D29-9C51-D0E7B4A6F213}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8A1F42C7-3B6E-4D29-9C51-D0E7B4A6F213}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8A1F42C7-3B6E-4D29-9C51-D0E7B4A6F213}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{8A1F42C7-3B6E-4D29-9C51-D0E7B4A6F213}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{8A1F42C7-3B6E-4D29-9C51-D0E7B4A6F213}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||
{8A1F42C7-3B6E-4D29-9C51-D0E7B4A6F213}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,29 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Soul World Sim"
|
||||
run/main_scene="res://Main.tscn"
|
||||
config/features=PackedStringArray("4.7", "C#", "Forward Plus")
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1600
|
||||
window/size/viewport_height=900
|
||||
window/stretch/mode="canvas_items"
|
||||
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="WorldSim"
|
||||
|
||||
[rendering]
|
||||
|
||||
textures/canvas_textures/default_texture_filter=0
|
||||
@@ -0,0 +1,206 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace WorldSim;
|
||||
|
||||
public enum BuildingKind { Shelter, Granary }
|
||||
|
||||
/// <summary>
|
||||
/// Modular buildings (SPEC §6): physically-sequential construction
|
||||
/// (foundation → walls → roof) with multi-NPC material delivery.
|
||||
///
|
||||
/// Buildings are the world's metabolism:
|
||||
/// Shelters — burn firewood daily, decay into ruins if neglected, and
|
||||
/// accept expansion modules that permanently raise their burn.
|
||||
/// Granary — the village's winter insurance: stores food surplus in the
|
||||
/// abundant seasons, feeds the hungry when the land gives nothing.
|
||||
/// Its walls are clay — the first real use for it.
|
||||
/// </summary>
|
||||
public class Building
|
||||
{
|
||||
public BuildingKind Kind = BuildingKind.Shelter;
|
||||
public Vector2 Site;
|
||||
|
||||
public static Building NewShelter(Vector2 site) => new()
|
||||
{
|
||||
Kind = BuildingKind.Shelter,
|
||||
Site = site,
|
||||
Stages = new()
|
||||
{
|
||||
new("foundation", MaterialKind.Stone, 10f),
|
||||
new("walls", MaterialKind.Wood, 20f),
|
||||
new("roof", MaterialKind.Wood, 10f),
|
||||
},
|
||||
};
|
||||
|
||||
public static Building NewGranary(Vector2 site) => new()
|
||||
{
|
||||
Kind = BuildingKind.Granary,
|
||||
Site = site,
|
||||
Stages = new()
|
||||
{
|
||||
new("foundation", MaterialKind.Stone, 10f),
|
||||
new("walls", MaterialKind.Clay, 25f),
|
||||
new("roof", MaterialKind.Wood, 10f),
|
||||
},
|
||||
};
|
||||
|
||||
// --- Construction ----------------------------------------------------
|
||||
|
||||
public record Stage(string Name, MaterialKind Material, float Cost);
|
||||
|
||||
public List<Stage> Stages = new();
|
||||
|
||||
public int CurrentStage;
|
||||
public float Delivered; // toward the current stage's cost
|
||||
public bool IsComplete => CurrentStage >= Stages.Count;
|
||||
|
||||
public MaterialKind? NeededMaterial => IsComplete ? null : Stages[CurrentStage].Material;
|
||||
|
||||
public float StageProgress =>
|
||||
IsComplete ? 1f : Delivered / Stages[CurrentStage].Cost;
|
||||
|
||||
// --- Shelter: upkeep -------------------------------------------------
|
||||
|
||||
public const float UpkeepStockCap = 10f;
|
||||
|
||||
public float UpkeepStock; // delivered wood reserve
|
||||
public float Condition = 1f; // 1 = sound; 0 = ruin
|
||||
public bool IsRuined => Condition <= 0f;
|
||||
|
||||
/// <summary>Base burn plus half a unit per expansion module, forever.</summary>
|
||||
public float DailyBurn => Kind == BuildingKind.Shelter ? 2f + Modules * 0.5f : 0f;
|
||||
|
||||
/// <summary>True when this shelter wants a firewood delivery.</summary>
|
||||
public bool WantsUpkeepWood =>
|
||||
Kind == BuildingKind.Shelter && IsComplete && !IsRuined &&
|
||||
UpkeepStock < UpkeepStockCap * 0.7f;
|
||||
|
||||
// --- Shelter: expansion modules --------------------------------------
|
||||
|
||||
public const int ModuleCap = 4;
|
||||
public const float ModuleCost = 15f; // wood per module
|
||||
|
||||
public int Modules;
|
||||
public float ModuleDelivered; // toward the next module
|
||||
|
||||
/// <summary>Sound, stocked shelters accept expansion work.</summary>
|
||||
public bool WantsExpansion =>
|
||||
Kind == BuildingKind.Shelter && IsComplete && !IsRuined &&
|
||||
Modules < ModuleCap && !WantsUpkeepWood;
|
||||
|
||||
// --- Granary: food store ---------------------------------------------
|
||||
|
||||
public const float FoodStockCap = 150f;
|
||||
|
||||
public float FoodStock;
|
||||
|
||||
/// <summary>True when the granary has room for more food.</summary>
|
||||
public bool WantsFood =>
|
||||
Kind == BuildingKind.Granary && IsComplete && FoodStock < FoodStockCap;
|
||||
|
||||
/// <summary>Take food out for a hungry villager. Returns amount granted.</summary>
|
||||
public float WithdrawFood(float requested)
|
||||
{
|
||||
float granted = Mathf.Min(requested, FoodStock);
|
||||
FoodStock -= granted;
|
||||
return granted;
|
||||
}
|
||||
|
||||
// --- Daily tick -------------------------------------------------------
|
||||
|
||||
/// <summary>Called once per in-game day by GameManager.</summary>
|
||||
public void DailyUpkeep()
|
||||
{
|
||||
if (Kind != BuildingKind.Shelter || !IsComplete || IsRuined) return;
|
||||
|
||||
if (UpkeepStock >= DailyBurn)
|
||||
{
|
||||
UpkeepStock -= DailyBurn;
|
||||
Condition = Mathf.Min(1f, Condition + 0.05f); // tended buildings mend
|
||||
}
|
||||
else
|
||||
{
|
||||
UpkeepStock = 0f;
|
||||
Condition -= 0.1f; // neglect shows in ~10 days
|
||||
}
|
||||
}
|
||||
|
||||
// --- Delivery --------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// The visitor delivers what the building currently needs — construction
|
||||
/// material while building; then firewood/expansion (shelter) or food
|
||||
/// (granary). Returns false if they had nothing useful to give.
|
||||
/// </summary>
|
||||
public bool Deliver(Npc npc)
|
||||
{
|
||||
if (IsRuined) return false;
|
||||
|
||||
if (!IsComplete)
|
||||
{
|
||||
var stage = Stages[CurrentStage];
|
||||
if (!npc.Inventory.TryGetValue(stage.Material, out float have) || have <= 0f)
|
||||
return false;
|
||||
|
||||
float take = Mathf.Min(have, stage.Cost - Delivered);
|
||||
npc.Inventory[stage.Material] = have - take;
|
||||
Delivered += take;
|
||||
|
||||
if (Delivered >= stage.Cost)
|
||||
{
|
||||
CurrentStage++;
|
||||
Delivered = 0f;
|
||||
}
|
||||
return take > 0f;
|
||||
}
|
||||
|
||||
if (Kind == BuildingKind.Granary)
|
||||
{
|
||||
// Deposit real surplus only (keep 5) — and never redeposit what
|
||||
// was just withdrawn: depositors must arrive carrying > 8.
|
||||
if (!npc.Inventory.TryGetValue(MaterialKind.Food, out float carried) || carried <= 8f)
|
||||
return false;
|
||||
|
||||
float space = FoodStockCap - FoodStock;
|
||||
float taken = Mathf.Min(carried - 5f, space);
|
||||
if (taken <= 0f) return false;
|
||||
|
||||
npc.Inventory[MaterialKind.Food] = carried - taken;
|
||||
FoodStock += taken;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Shelter: reserve first — never expand a shelter you can't heat.
|
||||
if (!npc.Inventory.TryGetValue(MaterialKind.Wood, out float wood) || wood <= 0f)
|
||||
return false;
|
||||
|
||||
float woodSpace = UpkeepStockCap - UpkeepStock;
|
||||
if (woodSpace > 0f)
|
||||
{
|
||||
float taken = Mathf.Min(wood, woodSpace);
|
||||
npc.Inventory[MaterialKind.Wood] = wood - taken;
|
||||
UpkeepStock += taken;
|
||||
return taken > 0f;
|
||||
}
|
||||
|
||||
if (Modules < ModuleCap)
|
||||
{
|
||||
float take = Mathf.Min(wood, ModuleCost - ModuleDelivered);
|
||||
npc.Inventory[MaterialKind.Wood] = wood - take;
|
||||
ModuleDelivered += take;
|
||||
|
||||
if (ModuleDelivered >= ModuleCost)
|
||||
{
|
||||
Modules++;
|
||||
ModuleDelivered = 0f;
|
||||
}
|
||||
return take > 0f;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ruins persist in the world — they are not removed. What a place was
|
||||
// remains visible in what it left behind.
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://telyx7lw4jh3
|
||||
@@ -0,0 +1,126 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace WorldSim;
|
||||
|
||||
/// <summary>
|
||||
/// Main loop and time system (SPEC §9).
|
||||
/// 1 tick = 1 in-game minute; 1440 ticks = 1 day.
|
||||
/// Day/night drives activity bias: gather/build by day, rest/socialize by night.
|
||||
/// </summary>
|
||||
public partial class GameManager : Node2D
|
||||
{
|
||||
public static GameManager? Instance { get; private set; }
|
||||
|
||||
[Export] public int Seed = 12345;
|
||||
[Export] public int NpcCount = 15;
|
||||
|
||||
[Export(PropertyHint.Range, "0,15,1")]
|
||||
public int SelfishCount = 3; // the contrast test (SPEC §11 amendment)
|
||||
|
||||
[Export(PropertyHint.Range, "1,2000,1")]
|
||||
public float TicksPerRealSecond = 20f; // sim speed; ~1500 ≈ one day/sec for soaks
|
||||
|
||||
public World World { get; private set; } = null!;
|
||||
public List<Npc> Npcs { get; } = new();
|
||||
public List<Building> Buildings { get; } = new();
|
||||
|
||||
public long TotalTicks { get; private set; }
|
||||
public int Day => (int)(TotalTicks / 1440);
|
||||
public int MinuteOfDay => (int)(TotalTicks % 1440);
|
||||
public bool IsNight => MinuteOfDay < 6 * 60 || MinuteOfDay >= 22 * 60;
|
||||
|
||||
// --- Seasons: the pressure that makes community structure matter ------
|
||||
|
||||
[Export] public int DaysPerSeason = 30;
|
||||
|
||||
public int SeasonIndex => (Day / DaysPerSeason) % 4;
|
||||
|
||||
public string SeasonName => SeasonIndex switch
|
||||
{
|
||||
0 => "spring", 1 => "summer", 2 => "autumn", _ => "winter",
|
||||
};
|
||||
|
||||
/// <summary>Food regen multiplier — winter gives almost nothing.</summary>
|
||||
public float SeasonFoodMult => SeasonIndex switch
|
||||
{
|
||||
0 => 1.5f, 1 => 1.0f, 2 => 0.8f, _ => 0.05f,
|
||||
};
|
||||
|
||||
/// <summary>Wood regen multiplier — growth slows in the cold.</summary>
|
||||
public float SeasonWoodMult => SeasonIndex switch
|
||||
{
|
||||
0 => 1.2f, 1 => 1.0f, 2 => 1.0f, _ => 0.4f,
|
||||
};
|
||||
|
||||
private double _tickAccumulator;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Instance = this;
|
||||
TotalTicks = 8 * 60; // start at 08:00 — day one begins mid-morning, not asleep
|
||||
World = new World(Seed);
|
||||
World.Generate();
|
||||
SpawnNpcs();
|
||||
StatsLogger.Init();
|
||||
GD.Print($"[WorldSim] World generated (seed {Seed}), {Npcs.Count} NPCs dropped in.");
|
||||
}
|
||||
|
||||
private void SpawnNpcs()
|
||||
{
|
||||
// Dropped near the map center with scatter — no scripted town layout.
|
||||
// Where the community forms is the experiment (SPEC §11).
|
||||
var rng = new System.Random(Seed);
|
||||
for (int i = 0; i < NpcCount; i++)
|
||||
{
|
||||
bool selfish = i >= NpcCount - SelfishCount;
|
||||
Npcs.Add(new Npc
|
||||
{
|
||||
Name = $"Villager_{(char)('A' + i)}",
|
||||
Soul = selfish ? SoulProfile.SelfishSoul() : SoulProfile.NatureSoul(),
|
||||
PersonalityModifier = (float)(rng.NextDouble() * 0.4 - 0.2),
|
||||
Position = new Vector2(
|
||||
World.GridSize / 2f + rng.Next(-20, 21),
|
||||
World.GridSize / 2f + rng.Next(-20, 21)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
_tickAccumulator += delta * TicksPerRealSecond;
|
||||
while (_tickAccumulator >= 1.0)
|
||||
{
|
||||
_tickAccumulator -= 1.0;
|
||||
Tick();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Where the community's feet actually are — new communal
|
||||
/// buildings are founded here, not at a scripted town center.</summary>
|
||||
public Vector2 CommunityCentroid()
|
||||
{
|
||||
if (Npcs.Count == 0) return new Vector2(World.GridSize / 2f, World.GridSize / 2f);
|
||||
Vector2 sum = Vector2.Zero;
|
||||
foreach (var npc in Npcs) sum += npc.Position;
|
||||
return sum / Npcs.Count;
|
||||
}
|
||||
|
||||
private void Tick()
|
||||
{
|
||||
TotalTicks++;
|
||||
|
||||
foreach (var npc in Npcs)
|
||||
npc.Tick(World, this);
|
||||
|
||||
if (TotalTicks % 1440 == 0)
|
||||
{
|
||||
World.RegenerateDaily(SeasonFoodMult, SeasonWoodMult);
|
||||
foreach (var building in Buildings)
|
||||
building.DailyUpkeep();
|
||||
StatsLogger.LogDay(this);
|
||||
if (Day % 10 == 0)
|
||||
GD.Print($"[WorldSim] Day {Day} complete.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://cvugyvd1fuj7l
|
||||
+495
@@ -0,0 +1,495 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace WorldSim;
|
||||
|
||||
public enum NpcState { Gathering, Building, Trading, Resting, Socializing }
|
||||
|
||||
/// <summary>
|
||||
/// NPC data + state machine (SPEC §9, §10). A plain simulation object —
|
||||
/// GameManager ticks it, Visualization draws it. No Node per NPC.
|
||||
///
|
||||
/// Constrained emergence: this machine defines what an NPC CAN do;
|
||||
/// the SoulProfile weights decide which valid option they take.
|
||||
///
|
||||
/// The loop is demand-driven so the sim never saturates:
|
||||
/// the community's current need (construction material, then firewood
|
||||
/// upkeep, then personal variety) decides what gets gathered; surplus
|
||||
/// flows to neighbors who lack it or to the buildings that burn it;
|
||||
/// night pulls everyone home to shelter; morning releases them.
|
||||
/// </summary>
|
||||
public class Npc
|
||||
{
|
||||
// --- Identity & soul -------------------------------------------------
|
||||
public string Name = "";
|
||||
public SoulProfile Soul = SoulProfile.NatureSoul();
|
||||
public SoulImprint Imprint = new(); // triad — see SPEC §2 amendment
|
||||
public float PersonalityModifier; // per-NPC variance within a soul type
|
||||
|
||||
// --- Body & position -------------------------------------------------
|
||||
public Vector2 Position;
|
||||
public float Fatigue; // 0–100; forced rest above 80
|
||||
public float Health = 100f; // forced rest below 30
|
||||
public float Nourishment = 1f; // 1 = fed; drains daily, eats from pack
|
||||
|
||||
// --- State -----------------------------------------------------------
|
||||
public NpcState State = NpcState.Gathering;
|
||||
|
||||
/// <summary>Human-readable description of the current action — for the
|
||||
/// debug roster only. The player-facing game will never show this.</summary>
|
||||
public string Activity = "waking up";
|
||||
|
||||
// --- Soul-derived behavior (constrained emergence: same possibility
|
||||
// --- space for everyone; the weights only change the choices) --------
|
||||
|
||||
/// <summary>Communal souls do communal work; low-bias souls simply don't.</summary>
|
||||
public bool IsCommunal => Soul.CommunityBias >= 0.3f;
|
||||
|
||||
/// <summary>Hoarders carry double — accumulation is a way of being.</summary>
|
||||
public float EffectiveCapacity => Soul.Accumulation > 0.5f ? CarryCapacity * 2f : CarryCapacity;
|
||||
|
||||
/// <summary>What fraction of a node this soul leaves standing. Nature
|
||||
/// souls honor the 50% commons floor; selfish souls strip to ~15%.</summary>
|
||||
public float HarvestSeekFloor => Mathf.Max(0.05f, Soul.Sustainability * 0.5f) + 0.05f;
|
||||
|
||||
/// <summary>Ticks spent in each state since the last daily snapshot —
|
||||
/// the raw material of emergent roles: what a person mostly does is
|
||||
/// who they are becoming. Indexed by (int)NpcState.</summary>
|
||||
public int[] StateTicksToday = new int[5];
|
||||
|
||||
public NpcState DominantStateToday()
|
||||
{
|
||||
int best = 0;
|
||||
for (int i = 1; i < StateTicksToday.Length; i++)
|
||||
if (StateTicksToday[i] > StateTicksToday[best]) best = i;
|
||||
return (NpcState)best;
|
||||
}
|
||||
public Dictionary<MaterialKind, float> Inventory = new();
|
||||
public float CarryCapacity = 20f;
|
||||
|
||||
private const float MoveSpeed = 0.8f; // grid cells per tick
|
||||
private const float ArriveDist = 1.5f;
|
||||
|
||||
private ResourceNode? _targetNode;
|
||||
private Npc? _targetNpc;
|
||||
private Building? _foodSource; // granary being visited for food
|
||||
private Npc? _giftSource; // neighbor being asked for food
|
||||
private float _stateTimer;
|
||||
|
||||
private static readonly MaterialKind[] GatherKinds =
|
||||
{ MaterialKind.Wood, MaterialKind.Stone, MaterialKind.Clay };
|
||||
|
||||
/// <summary>
|
||||
/// One simulation tick = one in-game minute (SPEC §9).
|
||||
/// Order per SPEC §10: vitals check → day/night bias → state behavior → fatigue.
|
||||
/// </summary>
|
||||
public void Tick(World world, GameManager gm)
|
||||
{
|
||||
_stateTimer += 1f;
|
||||
StateTicksToday[(int)State]++;
|
||||
|
||||
// 1. Vitals override everything
|
||||
if (State != NpcState.Resting && (Fatigue > 80f || Health < 30f))
|
||||
Enter(NpcState.Resting);
|
||||
|
||||
// Day/night bias (SPEC §10): work by day, rest/socialize by night
|
||||
if (gm.IsNight && State is NpcState.Gathering or NpcState.Trading or NpcState.Building)
|
||||
Enter(Fatigue > 50f ? NpcState.Resting : NpcState.Socializing);
|
||||
|
||||
// 2–3. State behavior
|
||||
switch (State)
|
||||
{
|
||||
case NpcState.Gathering: TickGathering(world, gm); break;
|
||||
case NpcState.Building: TickBuilding(gm); break;
|
||||
case NpcState.Trading: TickTrading(gm); break;
|
||||
case NpcState.Resting: TickResting(gm); break;
|
||||
case NpcState.Socializing: TickSocializing(gm); break;
|
||||
}
|
||||
|
||||
// Metabolism: nourishment drains slowly; eat from the pack when hungry.
|
||||
// This is the per-capita demand that keeps the economy from saturating.
|
||||
Nourishment -= 0.35f / 1440f;
|
||||
if (Nourishment < 0.7f &&
|
||||
Inventory.TryGetValue(MaterialKind.Food, out float food) && food >= 1f)
|
||||
{
|
||||
Inventory[MaterialKind.Food] = food - 1f;
|
||||
Nourishment = Mathf.Min(1f, Nourishment + 0.25f);
|
||||
}
|
||||
if (Nourishment <= 0.05f) Health = Mathf.Max(0f, Health - 0.01f);
|
||||
Nourishment = Mathf.Clamp(Nourishment, 0f, 1f);
|
||||
|
||||
// 4. Fatigue deltas (SPEC §10) — scaled per tick
|
||||
Fatigue += State switch
|
||||
{
|
||||
NpcState.Gathering => 2f,
|
||||
NpcState.Building => 2f,
|
||||
NpcState.Trading => 1f,
|
||||
NpcState.Resting => -5f,
|
||||
NpcState.Socializing => -2f,
|
||||
_ => 0f,
|
||||
} * 0.1f;
|
||||
Fatigue = Mathf.Clamp(Fatigue, 0f, 100f);
|
||||
}
|
||||
|
||||
public void Enter(NpcState state)
|
||||
{
|
||||
State = state;
|
||||
_stateTimer = 0f;
|
||||
_targetNode = null;
|
||||
_targetNpc = null;
|
||||
_foodSource = null;
|
||||
_giftSource = null;
|
||||
}
|
||||
|
||||
public float TotalCarried()
|
||||
{
|
||||
float sum = 0f;
|
||||
foreach (var kv in Inventory) sum += kv.Value;
|
||||
return sum;
|
||||
}
|
||||
|
||||
private void MoveToward(Vector2 target) =>
|
||||
Position = Position.MoveToward(target, MoveSpeed);
|
||||
|
||||
// --- Demand ----------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// What the community needs next, in priority order:
|
||||
/// construction material for an in-progress building, firewood for a
|
||||
/// shelter running low, else personal variety (least-carried).
|
||||
/// This is what turns fifteen individuals into a town coordinating.
|
||||
/// </summary>
|
||||
private MaterialKind PickGatherTarget(GameManager gm)
|
||||
{
|
||||
// 1. Feed yourself — keep a food reserve in the pack. Hoarding souls
|
||||
// keep a much bigger one, hungry or not.
|
||||
Inventory.TryGetValue(MaterialKind.Food, out float foodCarried);
|
||||
float reserveTarget = 3f + Soul.Accumulation * 7f;
|
||||
if (foodCarried < reserveTarget && (Nourishment < 0.8f || Soul.Accumulation > 0.5f))
|
||||
return MaterialKind.Food;
|
||||
|
||||
if (IsCommunal)
|
||||
{
|
||||
// 2. Construction sites.
|
||||
foreach (var b in gm.Buildings)
|
||||
if (!b.IsComplete && b.NeededMaterial is MaterialKind needed)
|
||||
return needed;
|
||||
|
||||
// 3. Firewood for shelters running low.
|
||||
foreach (var b in gm.Buildings)
|
||||
if (b.WantsUpkeepWood)
|
||||
return MaterialKind.Wood;
|
||||
|
||||
// 4. Winter insurance: stock the granary while the land still gives.
|
||||
foreach (var b in gm.Buildings)
|
||||
if (b.WantsFood)
|
||||
return MaterialKind.Food;
|
||||
|
||||
// 5. Ambition: expansion modules for sound, stocked shelters.
|
||||
foreach (var b in gm.Buildings)
|
||||
if (b.WantsExpansion)
|
||||
return MaterialKind.Wood;
|
||||
}
|
||||
|
||||
MaterialKind best = GatherKinds[(int)(Mathf.Abs(PersonalityModifier) * 100f) % GatherKinds.Length];
|
||||
float least = float.MaxValue;
|
||||
foreach (var kind in GatherKinds)
|
||||
{
|
||||
Inventory.TryGetValue(kind, out float have);
|
||||
if (have < least) { least = have; best = kind; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When full but mostly carrying the wrong thing, set the excess down to
|
||||
/// make room for what's actually needed. Lossy for now —
|
||||
/// TODO(Week 2): ground stockpiles so set-down materials persist.
|
||||
/// </summary>
|
||||
private void MakeRoomFor(MaterialKind demanded)
|
||||
{
|
||||
Inventory.TryGetValue(demanded, out float haveDemanded);
|
||||
if (haveDemanded >= EffectiveCapacity * 0.25f) return;
|
||||
|
||||
foreach (var kind in GatherKinds)
|
||||
{
|
||||
if (kind == demanded) continue;
|
||||
if (TotalCarried() <= EffectiveCapacity * 0.5f) break;
|
||||
if (Inventory.TryGetValue(kind, out float have) && have > 0f)
|
||||
Inventory[kind] = have * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
// --- States ----------------------------------------------------------
|
||||
|
||||
private void TickGathering(World world, GameManager gm)
|
||||
{
|
||||
// Mid-errand food runs: fetching from the granary or asking a neighbor.
|
||||
if (_foodSource != null)
|
||||
{
|
||||
Activity = "fetching food from the granary";
|
||||
MoveToward(_foodSource.Site);
|
||||
if (Position.DistanceTo(_foodSource.Site) < ArriveDist)
|
||||
{
|
||||
float got = _foodSource.WithdrawFood(8f);
|
||||
Inventory.TryGetValue(MaterialKind.Food, out float haveF);
|
||||
Inventory[MaterialKind.Food] = haveF + got;
|
||||
_foodSource = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_giftSource != null)
|
||||
{
|
||||
Activity = $"asking {_giftSource.Name} for food";
|
||||
MoveToward(_giftSource.Position);
|
||||
if (Position.DistanceTo(_giftSource.Position) < ArriveDist)
|
||||
{
|
||||
if (TradeSystem.CanTrade(_giftSource, this, out var mat))
|
||||
TradeSystem.Execute(_giftSource, this, mat);
|
||||
_giftSource = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var demanded = PickGatherTarget(gm);
|
||||
|
||||
if (TotalCarried() >= EffectiveCapacity)
|
||||
{
|
||||
// A full pack means different things to different souls:
|
||||
// communal souls take it to the village; hoarders sit on it.
|
||||
if (!IsCommunal) { Enter(NpcState.Socializing); return; }
|
||||
|
||||
Inventory.TryGetValue(demanded, out float haveDemanded);
|
||||
if (haveDemanded >= EffectiveCapacity * 0.25f) { Enter(NpcState.Building); return; }
|
||||
MakeRoomFor(demanded);
|
||||
if (TotalCarried() >= EffectiveCapacity) { Enter(NpcState.Trading); return; }
|
||||
}
|
||||
|
||||
if (_targetNode == null || _targetNode.IsDepleted || _targetNode.Kind != demanded)
|
||||
{
|
||||
_targetNode = world.FindNearest(demanded, Position, HarvestSeekFloor);
|
||||
|
||||
// Hungry and the land has nothing: the granary, then a neighbor.
|
||||
// This is where winter turns scarcity into community.
|
||||
if (_targetNode == null && demanded == MaterialKind.Food)
|
||||
{
|
||||
foreach (var b in gm.Buildings)
|
||||
if (b.Kind == BuildingKind.Granary && b.IsComplete && b.FoodStock > 0f)
|
||||
{
|
||||
_foodSource = b;
|
||||
return;
|
||||
}
|
||||
|
||||
float bestDist = float.MaxValue;
|
||||
foreach (var other in gm.Npcs)
|
||||
{
|
||||
if (other == this) continue;
|
||||
// Everyone knows better than to ask the ungenerous.
|
||||
if (other.Soul.Generosity < 0.3f) continue;
|
||||
if (!other.Inventory.TryGetValue(MaterialKind.Food, out float f) || f <= 3f) continue;
|
||||
float d = Position.DistanceSquaredTo(other.Position);
|
||||
if (d < bestDist) { bestDist = d; _giftSource = other; }
|
||||
}
|
||||
if (_giftSource != null) return;
|
||||
}
|
||||
|
||||
_targetNode ??= world.FindNearest(MaterialKind.Food, Position, HarvestSeekFloor)
|
||||
?? world.FindNearest(MaterialKind.Wood, Position, HarvestSeekFloor)
|
||||
?? world.FindNearest(MaterialKind.Stone, Position, HarvestSeekFloor)
|
||||
?? world.FindNearest(MaterialKind.Clay, Position, HarvestSeekFloor);
|
||||
if (_targetNode == null) { Enter(NpcState.Socializing); return; }
|
||||
}
|
||||
|
||||
MoveToward(_targetNode.Cell);
|
||||
if (Position.DistanceTo(_targetNode.Cell) >= ArriveDist)
|
||||
{
|
||||
Activity = $"walking to {_targetNode.Kind.ToString().ToLower()}";
|
||||
}
|
||||
else
|
||||
{
|
||||
Activity = $"harvesting {_targetNode.Kind.ToString().ToLower()}";
|
||||
float taken = world.Harvest(_targetNode, requested: 2f, Soul);
|
||||
if (taken <= 0f) { _targetNode = null; return; } // node at its floor — re-seek
|
||||
Inventory.TryGetValue(_targetNode.Kind, out float have);
|
||||
Inventory[_targetNode.Kind] = have + taken;
|
||||
|
||||
// Enough of what's needed? Communal souls go deliver it.
|
||||
Inventory.TryGetValue(PickGatherTarget(gm), out float demandedHave);
|
||||
if (IsCommunal && demandedHave >= EffectiveCapacity * 0.5f) Enter(NpcState.Building);
|
||||
}
|
||||
}
|
||||
|
||||
private void TickTrading(GameManager gm)
|
||||
{
|
||||
// Give up after a while — surplus can go to the buildings instead.
|
||||
if (_stateTimer > 120f) { Enter(NpcState.Building); return; }
|
||||
|
||||
if (_targetNpc == null)
|
||||
{
|
||||
float bestDist = float.MaxValue;
|
||||
foreach (var other in gm.Npcs)
|
||||
{
|
||||
if (other == this) continue;
|
||||
if (!TradeSystem.CanTrade(this, other, out _)) continue;
|
||||
float d = Position.DistanceSquaredTo(other.Position);
|
||||
if (d < bestDist) { bestDist = d; _targetNpc = other; }
|
||||
}
|
||||
if (_targetNpc == null) { Enter(NpcState.Building); return; }
|
||||
}
|
||||
|
||||
Activity = $"bringing goods to {_targetNpc.Name}";
|
||||
MoveToward(_targetNpc.Position);
|
||||
if (Position.DistanceTo(_targetNpc.Position) < ArriveDist)
|
||||
{
|
||||
if (TradeSystem.CanTrade(this, _targetNpc, out var material))
|
||||
TradeSystem.Execute(this, _targetNpc, material);
|
||||
Enter(NpcState.Building);
|
||||
}
|
||||
}
|
||||
|
||||
private void TickBuilding(GameManager gm)
|
||||
{
|
||||
// Communal work is for communal souls. The others still sleep in the
|
||||
// shelters and eat from the granary — freeriding is the whole point.
|
||||
if (!IsCommunal) { Enter(NpcState.Socializing); return; }
|
||||
|
||||
// Priority 1: construction sites. Priority 2: shelters low on firewood.
|
||||
Building? site = null;
|
||||
float bestDist = float.MaxValue;
|
||||
foreach (var b in gm.Buildings)
|
||||
{
|
||||
if (b.IsComplete) continue;
|
||||
float d = Position.DistanceSquaredTo(b.Site);
|
||||
if (d < bestDist) { bestDist = d; site = b; }
|
||||
}
|
||||
if (site == null)
|
||||
{
|
||||
foreach (var b in gm.Buildings)
|
||||
{
|
||||
if (!b.WantsUpkeepWood) continue;
|
||||
float d = Position.DistanceSquaredTo(b.Site);
|
||||
if (d < bestDist) { bestDist = d; site = b; }
|
||||
}
|
||||
}
|
||||
if (site == null)
|
||||
{
|
||||
// Granary deposits — only if carrying real food surplus.
|
||||
Inventory.TryGetValue(MaterialKind.Food, out float foodCarried);
|
||||
if (foodCarried > 8f)
|
||||
{
|
||||
foreach (var b in gm.Buildings)
|
||||
{
|
||||
if (!b.WantsFood) continue;
|
||||
float d = Position.DistanceSquaredTo(b.Site);
|
||||
if (d < bestDist) { bestDist = d; site = b; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (site == null)
|
||||
{
|
||||
foreach (var b in gm.Buildings)
|
||||
{
|
||||
if (!b.WantsExpansion) continue;
|
||||
float d = Position.DistanceSquaredTo(b.Site);
|
||||
if (d < bestDist) { bestDist = d; site = b; }
|
||||
}
|
||||
}
|
||||
|
||||
if (site == null)
|
||||
{
|
||||
// Communal-first founding, near where the community actually
|
||||
// gathers. Shelters first; once the village stands, the granary.
|
||||
bool carriesStone = Inventory.TryGetValue(MaterialKind.Stone, out float stone) && stone > 0f;
|
||||
int shelters = 0;
|
||||
bool granaryExists = false;
|
||||
foreach (var b in gm.Buildings)
|
||||
{
|
||||
if (b.Kind == BuildingKind.Granary) granaryExists = true;
|
||||
else shelters++;
|
||||
}
|
||||
|
||||
var jitter = new Vector2(PersonalityModifier * 25f, -PersonalityModifier * 25f);
|
||||
if (shelters < gm.NpcCount / 3 && carriesStone)
|
||||
{
|
||||
site = Building.NewShelter(gm.CommunityCentroid() + jitter);
|
||||
gm.Buildings.Add(site);
|
||||
}
|
||||
else if (!granaryExists && shelters >= gm.NpcCount / 3 && carriesStone)
|
||||
{
|
||||
site = Building.NewGranary(gm.CommunityCentroid() + jitter);
|
||||
gm.Buildings.Add(site);
|
||||
}
|
||||
else { Enter(NpcState.Socializing); return; }
|
||||
}
|
||||
|
||||
int siteIndex = gm.Buildings.IndexOf(site) + 1;
|
||||
string kindName = site.Kind == BuildingKind.Granary ? "granary" : $"shelter {siteIndex}";
|
||||
Activity = !site.IsComplete
|
||||
? $"building {kindName} ({site.NeededMaterial.ToString()!.ToLower()})"
|
||||
: site.Kind == BuildingKind.Granary
|
||||
? "stocking the granary"
|
||||
: site.WantsUpkeepWood
|
||||
? $"hauling firewood to {kindName}"
|
||||
: $"expanding {kindName}";
|
||||
|
||||
MoveToward(site.Site);
|
||||
if (Position.DistanceTo(site.Site) < ArriveDist)
|
||||
{
|
||||
bool delivered = site.Deliver(this);
|
||||
if (!delivered || site.IsComplete) Enter(NpcState.Socializing);
|
||||
}
|
||||
}
|
||||
|
||||
private void TickResting(GameManager gm)
|
||||
{
|
||||
// Rest at home: walk to the nearest sound shelter and recover faster there.
|
||||
Building? shelter = null;
|
||||
float bestDist = float.MaxValue;
|
||||
foreach (var b in gm.Buildings)
|
||||
{
|
||||
if (!b.IsComplete || b.IsRuined) continue;
|
||||
float d = Position.DistanceSquaredTo(b.Site);
|
||||
if (d < bestDist) { bestDist = d; shelter = b; }
|
||||
}
|
||||
|
||||
bool sheltered = false;
|
||||
if (shelter != null)
|
||||
{
|
||||
if (Position.DistanceTo(shelter.Site) > 2.5f) MoveToward(shelter.Site);
|
||||
else sheltered = true;
|
||||
}
|
||||
Activity = sheltered ? "resting at shelter"
|
||||
: shelter != null ? "walking home to rest"
|
||||
: "resting in the open";
|
||||
|
||||
Health = Mathf.Min(100f, Health + (sheltered ? 0.4f : 0.1f));
|
||||
if (sheltered) Fatigue = Mathf.Max(0f, Fatigue - 0.3f); // on top of the state delta
|
||||
|
||||
if (Fatigue < 40f && !gm.IsNight) Enter(NpcState.Gathering);
|
||||
}
|
||||
|
||||
private void TickSocializing(GameManager gm)
|
||||
{
|
||||
// Drift toward the nearest neighbor — community forms where feet do.
|
||||
Npc? nearest = null;
|
||||
float bestDist = float.MaxValue;
|
||||
foreach (var other in gm.Npcs)
|
||||
{
|
||||
if (other == this) continue;
|
||||
float d = Position.DistanceSquaredTo(other.Position);
|
||||
if (d < bestDist) { bestDist = d; nearest = other; }
|
||||
}
|
||||
if (nearest != null && Position.DistanceTo(nearest.Position) > 3f)
|
||||
{
|
||||
Activity = $"walking over to {nearest.Name}";
|
||||
MoveToward(nearest.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
Activity = nearest != null ? $"chatting with {nearest.Name}" : "idling";
|
||||
}
|
||||
|
||||
// TODO(Week 3): bonds + Generosity-weighted spontaneous help.
|
||||
if (!gm.IsNight && _stateTimer > 15f) Enter(NpcState.Gathering);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://di0nhnfp2o8ev
|
||||
@@ -0,0 +1,34 @@
|
||||
using Godot;
|
||||
|
||||
namespace WorldSim;
|
||||
|
||||
/// <summary>
|
||||
/// World-state serialization (SPEC §9 amendment).
|
||||
/// "The world is the save file" — persistence is a first-class MVP system,
|
||||
/// not a Phase 4 afterthought. Baseline test: save → load → the simulation
|
||||
/// continues identically (round-trip).
|
||||
///
|
||||
/// TODO(Week 4): serialize World.Nodes, Npcs (position, state, inventory,
|
||||
/// fatigue, health, imprint triad), Buildings, and TotalTicks to JSON at
|
||||
/// user://worldsim_save.json; load must rebuild identical state.
|
||||
/// Design note carried from DESIGN.md: when object imprint histories arrive
|
||||
/// (Phase 3), old imprints compress into net values + a few significant
|
||||
/// preserved entries — "what the world remembers in detail vs. aggregate"
|
||||
/// is a design decision expressed as a data policy.
|
||||
/// </summary>
|
||||
public static class SaveSystem
|
||||
{
|
||||
public static string SavePath => "user://worldsim_save.json";
|
||||
|
||||
public static void Save(GameManager gm)
|
||||
{
|
||||
// TODO(Week 4): implement JSON round-trip.
|
||||
GD.Print("[SaveSystem] Save not yet implemented.");
|
||||
}
|
||||
|
||||
public static bool TryLoad(GameManager gm)
|
||||
{
|
||||
// TODO(Week 4): implement JSON round-trip.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://ulislfbwqaa8
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
|
||||
namespace WorldSim;
|
||||
|
||||
/// <summary>
|
||||
/// Soul types — the four founding philosophies (SPEC §1).
|
||||
/// MVP uses Nature only; the others exist so nothing hardcodes a single type.
|
||||
/// </summary>
|
||||
public enum SoulType
|
||||
{
|
||||
Nature, // South — take only what you need
|
||||
Generational, // North — reverence for lineage
|
||||
Materialist, // West — worth measured in visible possessions
|
||||
Selfish, // East — personal gain, no guilt attached
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The soul imprint triad (SPEC §2 amendment, DESIGN.md debris model).
|
||||
/// The soul itself never changes — these are the layers over it.
|
||||
/// MVP behavior may read only Total, but all three sources are carried
|
||||
/// so later phases (residual-soul verdicts, awareness thresholds,
|
||||
/// source-specific clearing) need no data migration.
|
||||
/// </summary>
|
||||
public class SoulImprint
|
||||
{
|
||||
public float SelfRegard; // internal — how the soul sees itself
|
||||
public float OthersRegard; // internal — how the soul sees others
|
||||
public float ExternalPerception; // external — what others' souls pressed onto it
|
||||
|
||||
public float Total => (SelfRegard + OthersRegard + ExternalPerception) / 3f;
|
||||
public float Radiance => 1f - Total;
|
||||
|
||||
public void Clamp()
|
||||
{
|
||||
SelfRegard = Math.Clamp(SelfRegard, 0f, 1f);
|
||||
OthersRegard = Math.Clamp(OthersRegard, 0f, 1f);
|
||||
ExternalPerception = Math.Clamp(ExternalPerception, 0f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Behavior weights per soul type (SPEC §9). Constrained emergence:
|
||||
/// these weights pick WHICH valid option an NPC takes, never WHETHER
|
||||
/// a solution exists. Swappable per type — the engine stays agnostic.
|
||||
/// </summary>
|
||||
public class SoulProfile
|
||||
{
|
||||
public SoulType Type;
|
||||
|
||||
public float Generosity; // trade ratios, spontaneous help
|
||||
public float Sustainability; // harvest caps (South: ~50% of a node)
|
||||
public float CommunityBias; // communal-first building, socializing pull
|
||||
public float StatusBias; // West: visible-wealth preference (unused in MVP)
|
||||
public float Accumulation; // East: hoarding pressure (unused in MVP)
|
||||
|
||||
public static SoulProfile NatureSoul() => new()
|
||||
{
|
||||
Type = SoulType.Nature,
|
||||
Generosity = 0.9f,
|
||||
Sustainability = 0.9f,
|
||||
CommunityBias = 0.8f,
|
||||
StatusBias = 0.1f,
|
||||
Accumulation = 0.1f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// East-weighted profile — not part of MVP behavior, but defined now for
|
||||
/// the end-of-MVP contrast test (SPEC §11 amendment): 3 selfish NPCs
|
||||
/// dropped into the South town to prove weights alone change the society.
|
||||
/// </summary>
|
||||
public static SoulProfile SelfishSoul() => new()
|
||||
{
|
||||
Type = SoulType.Selfish,
|
||||
Generosity = 0.1f,
|
||||
Sustainability = 0.2f,
|
||||
CommunityBias = 0.1f,
|
||||
StatusBias = 0.5f,
|
||||
Accumulation = 0.9f,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dvoko7auk4m5g
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Godot;
|
||||
|
||||
namespace WorldSim;
|
||||
|
||||
/// <summary>
|
||||
/// Daily stats to CSV (SPEC §11 behavioral validation).
|
||||
/// One row per simulated day — the soak test's flight recorder.
|
||||
/// Written to the project folder as soak_stats.csv, fresh each run.
|
||||
/// </summary>
|
||||
public static class StatsLogger
|
||||
{
|
||||
private static string _path = "";
|
||||
private static string _npcPath = "";
|
||||
|
||||
private const string Header =
|
||||
"day,season,wood,stone,clay,food," +
|
||||
"harvested,trades," +
|
||||
"avg_fatigue,avg_nourish,avg_health,carried," +
|
||||
"gather_ticks,build_ticks,trade_ticks,rest_ticks,social_ticks," +
|
||||
"shelters_done,ruins,avg_cond,upkeep_stock,burn_per_day,modules,granary_food";
|
||||
|
||||
private const string NpcHeader =
|
||||
"day,npc,name,soul,dominant,gather_ticks,build_ticks,trade_ticks,rest_ticks,social_ticks";
|
||||
|
||||
private const string GiftHeader =
|
||||
"day,giver,giver_soul,receiver,receiver_soul,material,amount";
|
||||
|
||||
private static string _giftPath = "";
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
string dir = ProjectSettings.GlobalizePath("res://");
|
||||
_path = Path.Combine(dir, "soak_stats.csv");
|
||||
_npcPath = Path.Combine(dir, "soak_npc_days.csv");
|
||||
_giftPath = Path.Combine(dir, "soak_gifts.csv");
|
||||
File.WriteAllText(_path, Header + "\n");
|
||||
File.WriteAllText(_npcPath, NpcHeader + "\n");
|
||||
File.WriteAllText(_giftPath, GiftHeader + "\n");
|
||||
GD.Print($"[Stats] Logging to {_path}, {_npcPath}, {_giftPath}");
|
||||
}
|
||||
|
||||
/// <summary>Every gift, with names — so we can see who carries the town.</summary>
|
||||
public static void LogGift(Npc giver, Npc receiver, MaterialKind material, float amount)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_giftPath)) return;
|
||||
int day = GameManager.Instance?.Day ?? -1;
|
||||
File.AppendAllText(_giftPath,
|
||||
$"{day},{giver.Name},{giver.Soul.Type},{receiver.Name},{receiver.Soul.Type}," +
|
||||
$"{material},{amount.ToString("0.0", CultureInfo.InvariantCulture)}\n");
|
||||
}
|
||||
|
||||
public static void LogDay(GameManager gm)
|
||||
{
|
||||
var ci = CultureInfo.InvariantCulture;
|
||||
|
||||
// World resource totals
|
||||
float wood = 0f, stone = 0f, clay = 0f, food = 0f;
|
||||
foreach (var n in gm.World.Nodes)
|
||||
{
|
||||
switch (n.Kind)
|
||||
{
|
||||
case MaterialKind.Wood: wood += n.Amount; break;
|
||||
case MaterialKind.Stone: stone += n.Amount; break;
|
||||
case MaterialKind.Clay: clay += n.Amount; break;
|
||||
case MaterialKind.Food: food += n.Amount; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Villagers — state time from full-day tick counts, not a midnight
|
||||
// snapshot (the night rule made snapshots useless).
|
||||
float fatigue = 0f, nourish = 0f, health = 0f, carried = 0f;
|
||||
int gathering = 0, building = 0, trading = 0, resting = 0, socializing = 0;
|
||||
foreach (var npc in gm.Npcs)
|
||||
{
|
||||
fatigue += npc.Fatigue;
|
||||
nourish += npc.Nourishment;
|
||||
health += npc.Health;
|
||||
carried += npc.TotalCarried();
|
||||
gathering += npc.StateTicksToday[(int)NpcState.Gathering];
|
||||
building += npc.StateTicksToday[(int)NpcState.Building];
|
||||
trading += npc.StateTicksToday[(int)NpcState.Trading];
|
||||
resting += npc.StateTicksToday[(int)NpcState.Resting];
|
||||
socializing += npc.StateTicksToday[(int)NpcState.Socializing];
|
||||
}
|
||||
int count = Mathf.Max(1, gm.Npcs.Count);
|
||||
|
||||
// Buildings
|
||||
int done = 0, ruins = 0, modules = 0;
|
||||
float cond = 0f, stock = 0f, burn = 0f, granaryFood = 0f;
|
||||
foreach (var b in gm.Buildings)
|
||||
{
|
||||
if (b.IsRuined) { ruins++; continue; }
|
||||
if (!b.IsComplete) continue;
|
||||
if (b.Kind == BuildingKind.Granary) { granaryFood += b.FoodStock; continue; }
|
||||
done++;
|
||||
cond += b.Condition;
|
||||
stock += b.UpkeepStock;
|
||||
burn += b.DailyBurn;
|
||||
modules += b.Modules;
|
||||
}
|
||||
float avgCond = done > 0 ? cond / done : 0f;
|
||||
|
||||
var row = new StringBuilder();
|
||||
row.Append(gm.Day).Append(',');
|
||||
row.Append(gm.SeasonName).Append(',');
|
||||
row.Append(wood.ToString("0.0", ci)).Append(',');
|
||||
row.Append(stone.ToString("0.0", ci)).Append(',');
|
||||
row.Append(clay.ToString("0.0", ci)).Append(',');
|
||||
row.Append(food.ToString("0.0", ci)).Append(',');
|
||||
row.Append(gm.World.HarvestedToday.ToString("0.0", ci)).Append(',');
|
||||
row.Append(TradeSystem.TradesToday).Append(',');
|
||||
row.Append((fatigue / count).ToString("0.0", ci)).Append(',');
|
||||
row.Append((nourish / count).ToString("0.000", ci)).Append(',');
|
||||
row.Append((health / count).ToString("0.0", ci)).Append(',');
|
||||
row.Append(carried.ToString("0.0", ci)).Append(',');
|
||||
row.Append(gathering).Append(',').Append(building).Append(',')
|
||||
.Append(trading).Append(',').Append(resting).Append(',').Append(socializing).Append(',');
|
||||
row.Append(done).Append(',').Append(ruins).Append(',');
|
||||
row.Append(avgCond.ToString("0.000", ci)).Append(',');
|
||||
row.Append(stock.ToString("0.0", ci)).Append(',');
|
||||
row.Append(burn.ToString("0.0", ci)).Append(',');
|
||||
row.Append(modules).Append(',');
|
||||
row.Append(granaryFood.ToString("0.0", ci));
|
||||
|
||||
File.AppendAllText(_path, row + "\n");
|
||||
|
||||
// Per-villager day rows: what each person mostly did — the raw
|
||||
// material of emergent roles ("the role follows from the life").
|
||||
var npcRows = new StringBuilder();
|
||||
for (int i = 0; i < gm.Npcs.Count; i++)
|
||||
{
|
||||
var npc = gm.Npcs[i];
|
||||
var t = npc.StateTicksToday;
|
||||
npcRows.Append(gm.Day).Append(',')
|
||||
.Append(i + 1).Append(',')
|
||||
.Append(npc.Name).Append(',')
|
||||
.Append(npc.Soul.Type).Append(',')
|
||||
.Append(npc.DominantStateToday()).Append(',')
|
||||
.Append(t[(int)NpcState.Gathering]).Append(',')
|
||||
.Append(t[(int)NpcState.Building]).Append(',')
|
||||
.Append(t[(int)NpcState.Trading]).Append(',')
|
||||
.Append(t[(int)NpcState.Resting]).Append(',')
|
||||
.Append(t[(int)NpcState.Socializing]).Append('\n');
|
||||
System.Array.Clear(npc.StateTicksToday, 0, npc.StateTicksToday.Length);
|
||||
}
|
||||
File.AppendAllText(_npcPath, npcRows.ToString());
|
||||
|
||||
// Reset per-day counters
|
||||
gm.World.HarvestedToday = 0f;
|
||||
TradeSystem.TradesToday = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://2fgiwcgq1eh8
|
||||
@@ -0,0 +1,64 @@
|
||||
namespace WorldSim;
|
||||
|
||||
/// <summary>
|
||||
/// Trade evaluation and execution (SPEC §9). South MVP: generous, voluntary,
|
||||
/// no currency — a gift economy where the ratio favors the receiver.
|
||||
///
|
||||
/// Soul consequences: a generous completed exchange eases both parties'
|
||||
/// imprints (external perception — the world pressed kindly on them).
|
||||
/// Later soul types change the ratio logic only; the mechanism is shared.
|
||||
/// </summary>
|
||||
public static class TradeSystem
|
||||
{
|
||||
/// <summary>Trades since the last daily stats snapshot.</summary>
|
||||
public static int TradesToday;
|
||||
|
||||
/// <summary>
|
||||
/// Need-based: the trigger is a person in need, not an inventory gap.
|
||||
/// Food flows to the hungry first; raw material surplus vs. genuine
|
||||
/// lack remains as a fallback.
|
||||
/// </summary>
|
||||
public static bool CanTrade(Npc giver, Npc receiver, out MaterialKind material)
|
||||
{
|
||||
material = MaterialKind.Food;
|
||||
|
||||
// The ungenerous do not give. Not from an empty pack — from a full one.
|
||||
if (giver.Soul.Generosity < 0.3f) return false;
|
||||
|
||||
giver.Inventory.TryGetValue(MaterialKind.Food, out float giverFood);
|
||||
receiver.Inventory.TryGetValue(MaterialKind.Food, out float receiverFood);
|
||||
if (receiver.Nourishment < 0.6f && receiverFood < 2f && giverFood > 3f)
|
||||
return true;
|
||||
|
||||
foreach (var kv in giver.Inventory)
|
||||
{
|
||||
bool receiverLacks = !receiver.Inventory.TryGetValue(kv.Key, out float has) || has < 2f;
|
||||
if (kv.Value > 5f && receiverLacks) { material = kv.Key; return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Execute(Npc giver, Npc receiver, MaterialKind material)
|
||||
{
|
||||
TradesToday++;
|
||||
|
||||
// Generosity decides how much of the surplus is given.
|
||||
float surplus = giver.Inventory[material] - 2f;
|
||||
float given = surplus * giver.Soul.Generosity;
|
||||
|
||||
giver.Inventory[material] -= given;
|
||||
receiver.Inventory.TryGetValue(material, out float has);
|
||||
receiver.Inventory[material] = has + given;
|
||||
|
||||
StatsLogger.LogGift(giver, receiver, material, given);
|
||||
|
||||
// The kindness lands on both atmospheres — gently.
|
||||
giver.Imprint.ExternalPerception -= 0.002f;
|
||||
receiver.Imprint.ExternalPerception -= 0.002f;
|
||||
giver.Imprint.Clamp();
|
||||
receiver.Imprint.Clamp();
|
||||
|
||||
// TODO(Week 3): soul-imprint-weighted PARTNER selection (trusted vs
|
||||
// tainted history) — the constrained-emergence reference pattern (SPEC §7).
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://hcte3kpn8ton
|
||||
@@ -0,0 +1,173 @@
|
||||
using Godot;
|
||||
|
||||
namespace WorldSim;
|
||||
|
||||
/// <summary>
|
||||
/// Top-down 2D view + debug roster (SPEC §9).
|
||||
/// Draws straight from simulation state every frame — no per-NPC nodes,
|
||||
/// no camera: fixed screen-space layout, map on the left, roster on the
|
||||
/// right. The player-facing game has no meters; the designer sees everything.
|
||||
/// </summary>
|
||||
public partial class Visualization : Node2D
|
||||
{
|
||||
private GameManager _gm = null!;
|
||||
|
||||
// Layout: 1600×900 window — map fills the left square, panel the right.
|
||||
private const float MapOffset = 10f;
|
||||
private const float MapPixels = 880f;
|
||||
private const float CellPixels = MapPixels / World.GridSize; // ~3.44
|
||||
private const float PanelX = 910f;
|
||||
private const float PanelWidth = 680f;
|
||||
|
||||
private static readonly Color PanelBg = new(0.10f, 0.10f, 0.12f);
|
||||
private static readonly Color MapBg = new(0.13f, 0.13f, 0.15f);
|
||||
private static readonly Color TextDim = new(0.65f, 0.65f, 0.70f);
|
||||
private static readonly Color TextMain = new(0.92f, 0.92f, 0.95f);
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_gm = GetParent<GameManager>();
|
||||
}
|
||||
|
||||
public override void _Process(double delta) => QueueRedraw();
|
||||
|
||||
private static Color StateColor(NpcState state) => state switch
|
||||
{
|
||||
NpcState.Gathering => Colors.White,
|
||||
NpcState.Building => Colors.Orange,
|
||||
NpcState.Trading => Colors.Yellow,
|
||||
NpcState.Resting => new Color(0.45f, 0.45f, 0.95f),
|
||||
NpcState.Socializing => new Color(0.9f, 0.4f, 0.9f),
|
||||
_ => Colors.Red,
|
||||
};
|
||||
|
||||
private static Vector2 MapPos(Vector2 gridPos) =>
|
||||
new(MapOffset + gridPos.X * CellPixels, MapOffset + gridPos.Y * CellPixels);
|
||||
|
||||
public override void _Draw()
|
||||
{
|
||||
if (_gm.World == null) return;
|
||||
var font = ThemeDB.FallbackFont;
|
||||
|
||||
// --- Backgrounds --------------------------------------------------
|
||||
DrawRect(new Rect2(MapOffset, MapOffset, MapPixels, MapPixels), MapBg);
|
||||
DrawRect(new Rect2(PanelX, 0, PanelWidth, 900), PanelBg);
|
||||
|
||||
// --- Map: resource nodes -----------------------------------------
|
||||
foreach (var node in _gm.World.Nodes)
|
||||
{
|
||||
Color c = node.Kind switch
|
||||
{
|
||||
MaterialKind.Wood => new Color(0.20f, 0.55f, 0.20f),
|
||||
MaterialKind.Stone => new Color(0.55f, 0.55f, 0.55f),
|
||||
MaterialKind.Clay => new Color(0.75f, 0.45f, 0.25f),
|
||||
MaterialKind.Water => new Color(0.25f, 0.45f, 0.85f),
|
||||
MaterialKind.Food => new Color(0.80f, 0.30f, 0.40f),
|
||||
_ => Colors.Magenta,
|
||||
};
|
||||
// Fade toward depletion so dying groves read at a glance
|
||||
if (node.Kind != MaterialKind.Water)
|
||||
c = c.Darkened(0.65f * (1f - node.Amount / node.MaxAmount));
|
||||
var p = MapPos(node.Cell);
|
||||
DrawRect(new Rect2(p.X, p.Y, CellPixels, CellPixels), c);
|
||||
}
|
||||
|
||||
// --- Map: buildings ----------------------------------------------
|
||||
for (int i = 0; i < _gm.Buildings.Count; i++)
|
||||
{
|
||||
var b = _gm.Buildings[i];
|
||||
Color c = b.IsRuined ? new Color(0.25f, 0.23f, 0.20f)
|
||||
: b.Kind == BuildingKind.Granary
|
||||
? (b.IsComplete ? new Color(0.55f, 0.70f, 0.40f)
|
||||
: new Color(0.45f, 0.52f, 0.32f))
|
||||
: b.IsComplete ? new Color(0.9f, 0.85f, 0.6f).Darkened((1f - b.Condition) * 0.5f)
|
||||
: new Color(0.6f, 0.5f, 0.3f);
|
||||
float size = CellPixels + 3f + (b.CurrentStage + b.StageProgress) * 2f + b.Modules * 1.5f;
|
||||
var p = MapPos(b.Site);
|
||||
DrawRect(new Rect2(p.X - size / 2f, p.Y - size / 2f, size, size), c);
|
||||
DrawString(font, p + new Vector2(size / 2f + 2f, 4f), $"{i + 1}",
|
||||
fontSize: 10, modulate: TextDim);
|
||||
}
|
||||
|
||||
// --- Map: NPCs with roster numbers -------------------------------
|
||||
for (int i = 0; i < _gm.Npcs.Count; i++)
|
||||
{
|
||||
var npc = _gm.Npcs[i];
|
||||
var p = MapPos(npc.Position);
|
||||
// Selfish souls get a dark red ring — same dot, different halo.
|
||||
if (npc.Soul.Type == SoulType.Selfish)
|
||||
DrawCircle(p, 5.5f, new Color(0.75f, 0.15f, 0.15f));
|
||||
DrawCircle(p, 3.5f, StateColor(npc.State));
|
||||
DrawString(font, p + new Vector2(5f, -4f), $"{i + 1}",
|
||||
fontSize: 11, modulate: TextMain);
|
||||
}
|
||||
|
||||
// --- Panel: clock header -----------------------------------------
|
||||
DrawString(font, new Vector2(PanelX + 16, 30),
|
||||
$"Day {_gm.Day} · {_gm.SeasonName} {_gm.MinuteOfDay / 60:00}:{_gm.MinuteOfDay % 60:00} " +
|
||||
$"{(_gm.IsNight ? "· night" : "· day")} {_gm.TicksPerRealSecond:0}x",
|
||||
fontSize: 18, modulate: TextMain);
|
||||
|
||||
// --- Panel: villager roster --------------------------------------
|
||||
float y = 66f;
|
||||
DrawString(font, new Vector2(PanelX + 16, y), "VILLAGERS",
|
||||
fontSize: 13, modulate: TextDim);
|
||||
y += 10f;
|
||||
|
||||
for (int i = 0; i < _gm.Npcs.Count; i++)
|
||||
{
|
||||
var npc = _gm.Npcs[i];
|
||||
y += 22f;
|
||||
|
||||
if (npc.Soul.Type == SoulType.Selfish)
|
||||
DrawCircle(new Vector2(PanelX + 24, y - 5f), 6f, new Color(0.75f, 0.15f, 0.15f));
|
||||
DrawCircle(new Vector2(PanelX + 24, y - 5f), 4.5f, StateColor(npc.State));
|
||||
DrawString(font, new Vector2(PanelX + 36, y),
|
||||
$"{i + 1,2} {npc.State,-11} {npc.Activity}",
|
||||
fontSize: 13, modulate: TextMain);
|
||||
DrawString(font, new Vector2(PanelX + 420, y),
|
||||
$"F {npc.Fatigue,3:0} N {npc.Nourishment * 100,3:0} C {npc.TotalCarried(),4:0.0}" +
|
||||
$" mostly {npc.DominantStateToday().ToString().ToLower()[..4]}",
|
||||
fontSize: 12, modulate: TextDim);
|
||||
}
|
||||
|
||||
// --- Panel: buildings --------------------------------------------
|
||||
y += 34f;
|
||||
DrawString(font, new Vector2(PanelX + 16, y), "BUILDINGS",
|
||||
fontSize: 13, modulate: TextDim);
|
||||
y += 10f;
|
||||
|
||||
for (int i = 0; i < _gm.Buildings.Count; i++)
|
||||
{
|
||||
var b = _gm.Buildings[i];
|
||||
y += 20f;
|
||||
string status = b.IsRuined ? "RUIN"
|
||||
: !b.IsComplete
|
||||
? $"building: {b.Stages[b.CurrentStage].Name} {b.StageProgress * 100,3:0}%"
|
||||
: b.Kind == BuildingKind.Granary
|
||||
? $"granary food {b.FoodStock,5:0.0}/{Building.FoodStockCap:0}"
|
||||
: $"cond {b.Condition * 100,3:0}% wood {b.UpkeepStock,4:0.0}/{Building.UpkeepStockCap:0}" +
|
||||
$" mods {b.Modules}/{Building.ModuleCap} burn {b.DailyBurn:0.0}/day";
|
||||
DrawString(font, new Vector2(PanelX + 36, y),
|
||||
$"{i + 1} {status}", fontSize: 13,
|
||||
modulate: b.IsRuined ? TextDim : TextMain);
|
||||
}
|
||||
|
||||
// --- Panel: world totals -----------------------------------------
|
||||
y += 34f;
|
||||
float wood = 0f, stone = 0f, clay = 0f, foodTotal = 0f;
|
||||
foreach (var n in _gm.World.Nodes)
|
||||
{
|
||||
switch (n.Kind)
|
||||
{
|
||||
case MaterialKind.Wood: wood += n.Amount; break;
|
||||
case MaterialKind.Stone: stone += n.Amount; break;
|
||||
case MaterialKind.Clay: clay += n.Amount; break;
|
||||
case MaterialKind.Food: foodTotal += n.Amount; break;
|
||||
}
|
||||
}
|
||||
DrawString(font, new Vector2(PanelX + 16, y),
|
||||
$"WORLD wood {wood:0} stone {stone:0} clay {clay:0} food {foodTotal:0}",
|
||||
fontSize: 13, modulate: TextDim);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://b822hloehyggd
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace WorldSim;
|
||||
|
||||
public enum MaterialKind { Wood, Stone, Clay, Water, Food }
|
||||
|
||||
/// <summary>
|
||||
/// A harvestable resource node on the grid (SPEC §6).
|
||||
/// Wood regenerates; stone is static; clay sits near water; water is unlimited.
|
||||
/// </summary>
|
||||
public class ResourceNode
|
||||
{
|
||||
public MaterialKind Kind;
|
||||
public Vector2I Cell;
|
||||
public float Amount;
|
||||
public float MaxAmount;
|
||||
public float RegenPerDay; // 0 for stone; water ignores Amount entirely
|
||||
|
||||
public bool IsDepleted => Kind != MaterialKind.Water && Amount <= 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// World generation, resource placement, and the shared resource pool (SPEC §9).
|
||||
/// Grid is 256×256 (SPEC §9 performance target). No scripted town layout —
|
||||
/// NPCs are dropped in and the community forms where it forms.
|
||||
/// </summary>
|
||||
public class World
|
||||
{
|
||||
public const int GridSize = 256;
|
||||
|
||||
public List<ResourceNode> Nodes { get; } = new();
|
||||
|
||||
/// <summary>Units harvested since the last daily stats snapshot.</summary>
|
||||
public float HarvestedToday;
|
||||
|
||||
private readonly Random _rng;
|
||||
|
||||
public World(int seed)
|
||||
{
|
||||
_rng = new Random(seed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Place resources: water source tiles first, clay near water,
|
||||
/// wood in clusters (groves), stone in a few deposits.
|
||||
/// TODO(Week 1): replace uniform scatter with noise-based clustering.
|
||||
/// </summary>
|
||||
public void Generate()
|
||||
{
|
||||
PlaceMany(MaterialKind.Water, count: 6, amount: float.PositiveInfinity, regen: 0f);
|
||||
PlaceMany(MaterialKind.Clay, count: 10, amount: 40f, regen: 0.5f); // TODO: constrain near water
|
||||
PlaceMany(MaterialKind.Wood, count: 60, amount: 30f, regen: 2f);
|
||||
PlaceMany(MaterialKind.Stone, count: 12, amount: 80f, regen: 0f);
|
||||
PlaceMany(MaterialKind.Food, count: 40, amount: 20f, regen: 4f); // South: flora most abundant
|
||||
}
|
||||
|
||||
private void PlaceMany(MaterialKind kind, int count, float amount, float regen)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Nodes.Add(new ResourceNode
|
||||
{
|
||||
Kind = kind,
|
||||
Cell = new Vector2I(_rng.Next(GridSize), _rng.Next(GridSize)),
|
||||
Amount = amount,
|
||||
MaxAmount = amount,
|
||||
RegenPerDay = regen,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Harvest respecting the harvester's sustainability weight:
|
||||
/// a South soul stops at ~50% of the node (SPEC §10) so nodes stay healthy.
|
||||
/// Returns the amount actually taken.
|
||||
/// </summary>
|
||||
public float Harvest(ResourceNode node, float requested, SoulProfile soul)
|
||||
{
|
||||
if (node.Kind == MaterialKind.Water) return requested; // unlimited at source
|
||||
|
||||
float floorFraction = soul.Sustainability * 0.5f; // how much of the node they leave standing
|
||||
float takeable = Math.Max(0f, node.Amount - node.MaxAmount * floorFraction);
|
||||
float taken = Math.Min(requested, takeable);
|
||||
node.Amount -= taken;
|
||||
HarvestedToday += taken;
|
||||
return taken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once per in-game day by GameManager. Season multipliers throttle
|
||||
/// regeneration — winter is when the granary earns its clay.
|
||||
/// </summary>
|
||||
public void RegenerateDaily(float foodMult = 1f, float woodMult = 1f)
|
||||
{
|
||||
bool frost = foodMult < 0.1f; // deep winter
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
// Frost kills standing food — the wild larder rots on the stem.
|
||||
// Without this, the sustainability floors act as a huge hidden
|
||||
// savings account and winter never reaches an actual villager.
|
||||
if (frost && node.Kind == MaterialKind.Food)
|
||||
{
|
||||
node.Amount = Math.Max(node.Amount * 0.93f, node.MaxAmount * 0.15f);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.RegenPerDay <= 0f) continue;
|
||||
float mult = node.Kind switch
|
||||
{
|
||||
MaterialKind.Food => foodMult,
|
||||
MaterialKind.Wood => woodMult,
|
||||
_ => 1f,
|
||||
};
|
||||
node.Amount = Math.Min(node.MaxAmount, node.Amount + node.RegenPerDay * mult);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nearest node this particular soul considers harvestable. What counts
|
||||
/// as "worth taking" is a moral judgment, not a world fact: a nature
|
||||
/// soul walks past a node at half strength; a selfish soul strips it to
|
||||
/// the stem. The commons' savings account only exists for those who
|
||||
/// honor it.
|
||||
/// </summary>
|
||||
public ResourceNode? FindNearest(MaterialKind kind, Vector2 position, float minFraction = 0.5f)
|
||||
{
|
||||
ResourceNode? best = null;
|
||||
float bestDist = float.MaxValue;
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (node.Kind != kind || node.IsDepleted) continue;
|
||||
if (node.Kind != MaterialKind.Water && node.Amount <= node.MaxAmount * minFraction)
|
||||
continue;
|
||||
float d = position.DistanceSquaredTo(node.Cell);
|
||||
if (d < bestDist) { best = node; bestDist = d; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://jxxevuacgomc
|
||||
@@ -0,0 +1,12 @@
|
||||
day,giver,giver_soul,receiver,receiver_soul,material,amount
|
||||
117,Villager_F,Nature,Villager_G,Nature,Food,5.2
|
||||
117,Villager_G,Nature,Villager_A,Nature,Food,3.8
|
||||
234,Villager_A,Nature,Villager_K,Nature,Wood,4.0
|
||||
237,Villager_C,Nature,Villager_L,Nature,Food,2.8
|
||||
237,Villager_A,Nature,Villager_H,Nature,Food,6.3
|
||||
237,Villager_H,Nature,Villager_A,Nature,Food,4.8
|
||||
237,Villager_A,Nature,Villager_B,Nature,Food,4.0
|
||||
237,Villager_B,Nature,Villager_D,Nature,Food,2.7
|
||||
238,Villager_K,Nature,Villager_A,Nature,Food,3.6
|
||||
238,Villager_A,Nature,Villager_B,Nature,Food,2.7
|
||||
239,Villager_B,Nature,Villager_C,Nature,Food,0.9
|
||||
|
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="csv_translation"
|
||||
type="Translation"
|
||||
uid="uid://dpef17p6lhdnb"
|
||||
|
||||
[deps]
|
||||
|
||||
files=["res://soak_gifts.giver.translation", "res://soak_gifts.giver.translation", "res://soak_gifts.receiver.translation", "res://soak_gifts.receiver.translation", "res://soak_gifts.material.translation", "res://soak_gifts.amount.translation"]
|
||||
|
||||
source_file="res://soak_gifts.csv"
|
||||
dest_files=["res://soak_gifts.giver.translation", "res://soak_gifts.giver.translation", "res://soak_gifts.receiver.translation", "res://soak_gifts.receiver.translation", "res://soak_gifts.material.translation", "res://soak_gifts.amount.translation"]
|
||||
|
||||
[params]
|
||||
|
||||
compress=1
|
||||
delimiter=0
|
||||
unescape_keys=false
|
||||
unescape_translations=true
|
||||
+3646
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="csv_translation"
|
||||
type="Translation"
|
||||
uid="uid://b1b2os1svftw2"
|
||||
|
||||
[deps]
|
||||
|
||||
files=["res://soak_npc_days.npc.translation", "res://soak_npc_days.name.translation", "res://soak_npc_days.soul.translation", "res://soak_npc_days.dominant.translation", "res://soak_npc_days.gather.translation", "res://soak_npc_days.build.translation", "res://soak_npc_days.trade.translation", "res://soak_npc_days.rest.translation", "res://soak_npc_days.social.translation"]
|
||||
|
||||
source_file="res://soak_npc_days.csv"
|
||||
dest_files=["res://soak_npc_days.npc.translation", "res://soak_npc_days.name.translation", "res://soak_npc_days.soul.translation", "res://soak_npc_days.dominant.translation", "res://soak_npc_days.gather.translation", "res://soak_npc_days.build.translation", "res://soak_npc_days.trade.translation", "res://soak_npc_days.rest.translation", "res://soak_npc_days.social.translation"]
|
||||
|
||||
[params]
|
||||
|
||||
compress=1
|
||||
delimiter=0
|
||||
unescape_keys=false
|
||||
unescape_translations=true
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
day,season,wood,stone,clay,food,harvested,trades,avg_fatigue,avg_nourish,avg_health,carried,gather_ticks,build_ticks,trade_ticks,rest_ticks,social_ticks,shelters_done,ruins,avg_cond,upkeep_stock,burn_per_day,modules,granary_food
|
||||
1,spring,1571.9,840.0,322.5,772.0,532.5,0,10.9,0.767,100.0,292.5,7682,1652,0,2112,2954,2,0,1.000,12.5,7.5,7,0.0
|
||||
2,spring,1367.1,826.0,325.0,773.0,340.2,0,2.1,0.917,100.0,338.6,6945,2679,0,4497,7479,4,0,1.000,25.0,15.0,14,0.0
|
||||
3,spring,1332.8,772.0,289.5,798.0,226.3,0,6.7,0.817,100.0,333.5,6773,2729,0,7288,4810,5,0,1.000,30.0,20.0,20,0.0
|
||||
4,spring,1354.7,772.0,292.0,742.0,207.7,0,0.0,0.717,100.0,351.4,4540,2463,0,5136,9461,5,0,1.000,30.0,20.0,20,82.0
|
||||
5,spring,1354.7,690.0,230.5,745.0,334.8,0,7.5,0.867,100.0,354.1,7219,1321,0,4595,8465,5,0,1.000,30.0,20.0,20,150.0
|
||||
6,spring,1355.1,636.0,189.0,800.0,203.7,0,6.1,0.767,100.0,310.4,6724,470,0,4852,9554,5,0,1.000,30.0,20.0,20,150.0
|
||||
7,spring,1338.6,560.0,186.0,756.0,328.1,0,9.2,0.917,100.0,351.1,7686,470,45,4675,8724,5,0,1.000,30.0,20.0,20,150.0
|
||||
8,spring,1376.9,526.0,184.0,768.0,205.1,0,4.7,0.817,100.0,362.9,6099,1199,189,3654,10459,5,0,1.000,30.0,20.0,20,150.0
|
||||
9,spring,1449.8,516.0,189.0,796.0,64.0,0,0.0,0.717,100.0,371.0,1950,1903,466,1080,16201,5,0,1.000,22.1,20.0,20,150.0
|
||||
10,spring,1494.7,506.0,187.0,800.0,81.0,0,9.6,0.866,100.0,364.3,4091,1446,342,516,15205,5,0,1.000,20.9,20.0,20,150.0
|
||||
11,spring,1544.8,488.0,187.0,800.0,78.6,0,0.8,0.766,100.0,363.2,4343,1202,324,1305,14426,5,0,1.000,19.5,20.0,20,150.0
|
||||
12,spring,1582.5,470.0,187.0,798.0,88.2,0,3.1,0.916,100.0,358.0,4610,837,340,1287,14526,5,0,1.000,18.3,20.0,20,150.0
|
||||
13,spring,1603.5,454.0,187.0,796.0,92.2,0,18.0,0.816,100.0,372.2,4922,1883,267,486,14042,5,0,1.000,19.9,20.0,20,150.0
|
||||
14,spring,1623.1,454.0,190.0,800.0,42.0,0,0.0,0.716,100.0,364.0,1293,3212,424,62,16609,5,0,1.000,23.5,20.0,20,150.0
|
||||
15,spring,1625.5,454.0,190.0,794.0,73.4,0,0.0,0.866,100.0,364.0,3460,1259,409,135,16337,5,0,1.000,19.9,20.0,20,150.0
|
||||
16,spring,1628.5,454.0,191.0,800.0,63.6,0,0.0,0.766,100.0,371.0,1697,1411,511,0,17981,5,0,1.000,21.5,20.0,20,150.0
|
||||
17,spring,1638.7,454.0,193.5,800.0,39.3,0,0.0,0.916,100.0,353.0,1514,1244,527,0,18315,5,0,1.000,21.9,20.0,20,150.0
|
||||
18,spring,1637.2,454.0,196.5,798.0,63.6,0,0.0,0.816,100.0,364.0,1706,1002,535,0,18357,5,0,1.000,19.9,20.0,20,150.0
|
||||
19,spring,1632.8,454.0,201.5,798.0,59.2,0,0.0,0.716,100.0,371.0,1760,1484,504,0,17852,5,0,1.000,21.5,20.0,20,150.0
|
||||
20,spring,1625.2,454.0,206.5,794.0,68.8,0,0.0,0.866,100.0,371.1,1985,1054,516,0,18045,5,0,1.000,19.1,20.0,20,150.0
|
||||
21,spring,1629.6,454.0,211.5,800.0,34.8,0,0.0,0.766,100.0,364.0,1211,1313,548,0,18528,5,0,1.000,20.0,20.0,20,150.0
|
||||
22,spring,1625.6,454.0,216.5,796.0,61.6,0,0.0,0.916,100.0,358.0,2144,1166,499,0,17791,5,0,1.000,20.4,20.0,20,150.0
|
||||
23,spring,1617.2,454.0,221.5,796.0,72.4,0,0.0,0.816,100.0,371.0,2201,1057,503,0,17839,5,0,1.000,21.6,20.0,20,150.0
|
||||
24,spring,1627.2,454.0,226.5,800.0,34.0,0,0.0,0.716,100.0,364.0,1257,1784,512,0,18047,5,0,1.000,22.8,20.0,20,150.0
|
||||
25,spring,1621.6,454.0,231.5,798.0,72.4,0,0.0,0.866,100.0,364.0,2206,989,505,0,17900,5,0,1.000,22.8,20.0,20,150.0
|
||||
26,spring,1626.0,454.0,236.5,796.0,52.8,0,0.0,0.766,100.0,371.0,1567,1203,532,0,18298,5,0,1.000,18.8,20.0,20,150.0
|
||||
27,spring,1633.2,454.0,241.5,800.0,36.4,0,0.0,0.916,100.0,351.0,1378,1200,539,0,18483,5,0,1.000,20.8,20.0,20,150.0
|
||||
28,spring,1628.4,454.0,246.5,798.0,70.4,0,0.0,0.816,100.0,364.0,1945,948,523,0,18184,5,0,1.000,20.0,20.0,20,150.0
|
||||
29,spring,1626.8,454.0,251.5,796.0,58.4,0,0.0,0.716,100.0,371.0,1692,1427,511,0,17970,5,0,1.000,17.6,20.0,20,150.0
|
||||
30,summer,1609.9,454.0,256.5,786.0,74.9,0,0.0,0.866,100.0,371.2,2227,1131,498,0,17744,5,0,1.000,20.0,20.0,20,150.0
|
||||
31,summer,1615.1,454.0,261.5,796.0,30.8,0,0.0,0.766,100.0,364.0,1097,1279,552,0,18672,5,0,1.000,18.2,20.0,20,150.0
|
||||
32,summer,1607.5,454.0,266.5,796.0,63.6,0,0.0,0.916,100.0,358.0,2261,1213,486,0,17640,5,0,1.000,19.4,20.0,20,150.0
|
||||
33,summer,1595.5,454.0,271.5,788.0,72.0,0,0.0,0.816,100.0,371.0,2297,1096,496,0,17711,5,0,1.000,19.4,20.0,20,150.0
|
||||
34,summer,1600.3,454.0,276.5,792.0,33.2,0,0.0,0.716,100.0,364.0,1198,1332,547,0,18523,5,0,1.000,20.2,20.0,20,150.0
|
||||
35,summer,1590.3,454.0,281.5,788.0,74.0,0,0.0,0.866,100.0,364.0,2471,1293,474,0,17362,5,0,1.000,20.2,20.0,20,150.0
|
||||
36,summer,1589.0,454.0,286.5,788.0,59.3,0,0.0,0.766,100.0,371.0,2009,1503,487,0,17601,5,0,1.000,20.2,20.0,20,150.0
|
||||
37,summer,1595.0,454.0,291.5,798.0,38.0,0,0.0,0.916,100.0,353.0,1462,1204,533,0,18401,5,0,1.000,20.2,20.0,20,150.0
|
||||
38,summer,1587.0,454.0,296.5,796.0,70.0,0,0.0,0.816,100.0,364.0,2086,1135,505,0,17874,5,0,1.000,20.2,20.0,20,150.0
|
||||
39,summer,1585.0,454.0,301.5,794.0,60.0,0,0.0,0.716,100.0,371.0,1899,1716,482,0,17503,5,0,1.000,20.2,20.0,20,150.0
|
||||
40,summer,1578.5,454.0,306.5,784.0,72.5,0,0.0,0.866,100.0,371.2,2191,1319,486,0,17604,5,0,1.000,20.2,20.0,20,150.0
|
||||
41,summer,1584.5,454.0,311.5,792.0,34.0,0,0.5,0.766,100.0,364.0,1299,1848,510,0,17943,5,0,1.000,20.4,20.0,20,150.0
|
||||
42,summer,1580.5,454.0,316.5,790.0,62.0,0,0.0,0.916,100.0,358.0,2482,1118,479,0,17521,5,0,1.000,20.4,20.0,20,150.0
|
||||
43,summer,1570.5,454.0,321.5,784.0,72.0,0,0.0,0.816,100.0,371.0,2349,1149,488,0,17614,5,0,1.000,20.4,20.0,20,150.0
|
||||
44,summer,1578.5,454.0,326.5,790.0,34.0,0,0.0,0.716,100.0,364.0,1220,1455,538,0,18387,5,0,1.000,20.4,20.0,20,150.0
|
||||
45,summer,1568.5,454.0,331.5,788.0,74.0,0,0.0,0.866,100.0,364.0,2568,1247,468,0,17317,5,0,1.000,20.4,20.0,20,150.0
|
||||
46,summer,1566.5,454.0,336.5,788.0,60.0,0,0.0,0.766,100.0,371.0,2101,1297,493,0,17709,5,0,1.000,20.5,20.0,20,150.0
|
||||
47,summer,1576.5,454.0,341.5,798.0,36.0,0,0.0,0.916,100.0,351.0,1497,1276,527,0,18300,5,0,1.000,20.5,20.0,20,150.0
|
||||
48,summer,1570.5,454.0,346.5,788.0,72.0,0,0.0,0.816,100.0,364.0,2092,1234,500,0,17774,5,0,1.000,20.5,20.0,20,150.0
|
||||
49,summer,1568.5,454.0,351.5,790.0,60.0,0,0.0,0.716,100.0,371.0,2068,1456,488,0,17588,5,0,1.000,20.5,20.0,20,150.0
|
||||
50,summer,1564.0,454.0,356.5,786.0,72.5,0,0.4,0.866,100.0,371.5,2449,1245,476,0,17430,5,0,1.000,20.5,20.0,20,150.0
|
||||
51,summer,1576.0,454.0,361.5,798.0,34.0,0,0.9,0.766,100.0,364.0,1332,1385,533,0,18350,5,0,1.000,21.0,20.0,20,150.0
|
||||
52,summer,1574.0,454.0,366.5,792.0,62.0,0,0.0,0.916,100.0,358.0,2476,1165,479,0,17480,5,0,1.000,21.0,20.0,20,150.0
|
||||
53,summer,1566.0,454.0,371.5,790.0,72.0,0,0.0,0.816,100.0,371.0,2352,1103,493,0,17652,5,0,1.000,21.0,20.0,20,150.0
|
||||
54,summer,1576.0,454.0,376.5,796.0,34.0,0,0.0,0.716,100.0,364.0,1223,1505,533,0,18339,5,0,1.000,21.0,20.0,20,150.0
|
||||
55,summer,1566.0,454.0,381.5,790.0,74.0,0,0.0,0.866,100.0,364.0,2652,1084,473,0,17391,5,0,1.000,21.0,20.0,20,150.0
|
||||
56,summer,1564.0,454.0,386.0,790.0,60.0,0,0.0,0.766,100.0,371.0,2019,1372,494,0,17715,5,0,1.000,21.0,20.0,20,150.0
|
||||
57,summer,1574.0,454.0,389.5,798.0,38.0,0,0.0,0.916,100.0,353.0,1445,1303,528,0,18324,5,0,1.000,21.0,20.0,20,150.0
|
||||
58,summer,1566.0,454.0,393.0,792.0,70.0,0,0.0,0.816,100.0,364.0,2010,1097,511,0,17982,5,0,1.000,21.0,20.0,20,150.0
|
||||
59,summer,1564.0,454.0,394.5,794.0,60.0,0,0.0,0.716,100.0,371.0,2132,1572,477,0,17419,5,0,1.000,21.0,20.0,20,150.0
|
||||
60,autumn,1558.0,454.0,396.0,784.0,74.0,0,0.4,0.866,100.0,371.5,2449,1212,479,0,17460,5,0,1.000,21.0,20.0,20,150.0
|
||||
61,autumn,1572.0,454.0,396.5,792.4,32.0,0,0.0,0.766,100.0,364.0,1194,1423,540,0,18443,5,0,1.000,19.5,20.0,20,150.0
|
||||
62,autumn,1572.0,454.0,397.0,788.0,62.0,0,0.0,0.916,100.0,358.0,2309,1108,492,0,17691,5,0,1.000,19.5,20.0,20,150.0
|
||||
63,autumn,1564.0,454.0,397.5,779.6,72.0,0,0.0,0.816,100.0,371.0,2451,1089,487,0,17573,5,0,1.000,19.5,20.0,20,150.0
|
||||
64,autumn,1576.0,454.0,398.0,790.4,34.0,0,0.0,0.716,100.0,364.0,1318,1404,533,0,18345,5,0,1.000,19.5,20.0,20,150.0
|
||||
65,autumn,1568.0,454.0,398.5,780.4,74.0,0,0.0,0.866,100.0,364.0,2654,1160,471,0,17315,5,0,1.000,19.5,20.0,20,150.0
|
||||
66,autumn,1566.0,454.0,399.0,778.2,59.8,0,0.5,0.766,100.0,370.8,2463,1466,462,0,17209,5,0,1.000,19.5,20.0,20,150.0
|
||||
67,autumn,1572.0,454.0,399.5,785.4,44.0,0,0.0,0.915,100.0,356.8,1437,1237,533,0,18393,5,0,1.000,19.5,20.0,20,150.0
|
||||
68,autumn,1564.0,454.0,400.0,777.8,68.0,0,0.0,0.815,100.0,365.8,2114,990,509,0,17987,5,0,1.000,19.5,20.0,20,150.0
|
||||
69,autumn,1564.0,454.0,400.0,776.2,55.6,0,0.0,0.715,100.0,370.4,1831,1605,493,0,17671,5,0,1.000,19.5,20.0,20,150.0
|
||||
70,autumn,1558.0,454.0,400.0,773.4,73.2,0,0.0,0.865,100.0,369.9,2428,1057,490,0,17625,5,0,1.000,19.5,20.0,20,150.0
|
||||
71,autumn,1560.0,454.0,400.0,777.4,49.2,0,0.0,0.765,100.0,369.8,1779,1604,494,0,17723,5,0,1.000,19.8,20.0,20,150.0
|
||||
72,autumn,1564.0,454.0,400.0,778.2,45.2,0,0.0,0.915,100.0,355.0,1809,1143,516,0,18132,5,0,1.000,19.8,20.0,20,150.0
|
||||
73,autumn,1556.0,454.0,400.0,774.6,72.0,0,0.0,0.815,100.0,368.0,2064,1149,505,0,17882,5,0,1.000,19.8,20.0,20,150.0
|
||||
74,autumn,1556.0,454.0,400.0,778.6,54.0,0,0.0,0.715,100.0,371.0,2037,1623,477,0,17463,5,0,1.000,19.8,20.0,20,150.0
|
||||
75,autumn,1550.0,454.0,400.0,778.0,75.2,0,0.0,0.865,100.0,372.4,2409,1370,470,0,17351,5,0,1.000,19.8,20.0,20,150.0
|
||||
76,autumn,1562.0,454.0,400.0,791.0,36.0,0,0.3,0.765,100.0,367.2,1312,2039,494,0,17755,5,0,1.000,20.0,20.0,20,150.0
|
||||
77,autumn,1562.0,454.0,400.0,785.8,62.0,0,0.0,0.915,100.0,361.2,2493,1140,478,0,17489,5,0,1.000,20.0,20.0,20,150.0
|
||||
78,autumn,1556.0,454.0,400.0,781.0,70.0,0,0.0,0.815,100.0,372.2,2277,1313,484,0,17526,5,0,1.000,20.0,20.0,20,150.0
|
||||
79,autumn,1566.0,454.0,400.0,787.8,36.0,0,0.0,0.715,100.0,367.2,1256,1314,544,0,18486,5,0,1.000,20.0,20.0,20,150.0
|
||||
80,autumn,1558.0,454.0,400.0,780.8,73.4,0,0.0,0.865,100.0,366.6,2588,1295,466,0,17251,5,0,1.000,20.0,20.0,20,150.0
|
||||
81,autumn,1557.5,454.0,400.0,780.0,59.7,0,0.0,0.765,100.0,372.8,2335,1254,481,0,17530,5,0,1.000,20.0,20.0,20,150.0
|
||||
82,autumn,1567.5,454.0,400.0,784.0,38.0,0,0.0,0.915,100.0,354.8,1395,1333,529,0,18343,5,0,1.000,20.0,20.0,20,150.0
|
||||
83,autumn,1561.5,454.0,400.0,778.0,72.0,0,0.0,0.815,100.0,367.8,2006,1322,499,0,17773,5,0,1.000,20.0,20.0,20,150.0
|
||||
84,autumn,1561.5,454.0,400.0,778.2,57.6,0,0.0,0.715,100.0,372.4,1919,1319,503,0,17859,5,0,1.000,20.0,20.0,20,150.0
|
||||
85,autumn,1555.5,454.0,400.0,770.0,75.4,0,0.0,0.865,100.0,374.0,2308,1244,485,0,17563,5,0,1.000,20.0,20.0,20,150.0
|
||||
86,autumn,1565.5,454.0,400.0,779.2,35.2,0,0.5,0.765,100.0,368.0,1220,1832,510,0,18038,5,0,1.000,20.2,20.0,20,150.0
|
||||
87,autumn,1561.5,454.0,400.0,774.8,65.6,0,0.0,0.915,100.0,363.6,2716,1103,466,0,17315,5,0,1.000,20.2,20.0,20,150.0
|
||||
88,autumn,1553.5,454.0,400.0,771.2,70.0,0,0.0,0.815,100.0,374.6,2320,1392,477,0,17411,5,0,1.000,20.2,20.0,20,150.0
|
||||
89,autumn,1565.5,454.0,400.0,784.2,32.0,0,0.0,0.715,100.0,367.6,1204,1323,544,0,18529,5,0,1.000,20.2,20.0,20,150.0
|
||||
90,winter,1535.9,454.0,400.0,701.4,74.0,0,0.0,0.865,100.0,367.6,2507,1155,478,0,17460,5,0,1.000,20.2,20.0,20,150.0
|
||||
91,winter,1513.4,454.0,400.0,632.6,59.7,0,0.0,0.765,100.0,373.8,2223,1526,475,0,17376,5,0,1.000,20.2,20.0,20,150.0
|
||||
92,winter,1505.8,454.0,400.0,577.2,36.4,0,0.0,0.915,100.0,355.8,1468,1570,511,0,18051,5,0,1.000,19.8,20.0,20,150.0
|
||||
93,winter,1482.8,454.0,400.0,510.5,69.6,0,0.0,0.815,100.0,369.0,3047,1573,422,0,16558,5,0,1.000,20.2,20.0,20,150.0
|
||||
94,winter,1466.8,454.0,400.0,456.7,54.1,0,1.4,0.715,100.0,373.7,3028,2627,339,385,15221,5,0,1.000,18.6,20.0,20,150.0
|
||||
95,winter,1444.3,454.0,400.0,409.0,59.6,0,6.2,0.865,100.0,365.1,3565,2089,338,633,14975,5,0,1.000,18.6,20.0,20,150.0
|
||||
96,winter,1433.9,454.0,400.0,356.7,57.0,0,2.3,0.765,100.0,371.6,2427,2511,395,882,15385,5,0,1.000,19.8,20.0,20,150.0
|
||||
97,winter,1426.1,454.0,400.0,318.3,45.3,0,5.3,0.915,100.0,356.8,3537,2125,322,601,15015,5,0,1.000,19.2,20.0,20,150.0
|
||||
98,winter,1406.1,454.0,400.0,281.6,59.2,0,1.8,0.815,100.0,359.5,5686,1459,216,1578,12661,5,0,1.000,21.4,20.0,20,150.0
|
||||
99,winter,1140.0,454.0,400.0,255.3,311.1,0,1.3,0.715,100.0,361.5,5280,1776,223,3529,10792,5,0,1.000,21.3,20.0,20,150.0
|
||||
100,winter,986.1,454.0,400.0,234.2,205.3,0,6.4,0.865,100.0,345.3,7979,1401,53,5522,6645,5,0,1.000,20.4,20.0,20,150.0
|
||||
101,winter,914.9,454.0,400.0,219.1,117.6,0,5.4,0.765,100.0,346.6,8731,909,10,7641,4309,5,0,1.000,27.9,20.0,20,150.0
|
||||
102,winter,891.2,454.0,400.0,202.7,76.8,0,12.7,0.915,100.0,322.7,9486,265,1,7678,4170,5,0,1.000,29.9,20.0,20,150.0
|
||||
103,winter,880.0,454.0,400.0,186.8,65.0,0,6.0,0.815,100.0,331.6,9368,562,2,7673,3995,5,0,1.000,30.0,20.0,20,150.0
|
||||
104,winter,884.5,454.0,341.5,176.3,104.7,0,0.0,0.715,100.0,333.8,9459,322,0,7616,4203,5,0,1.000,30.0,20.0,20,150.0
|
||||
105,winter,890.4,454.0,343.0,163.3,47.9,0,9.4,0.865,100.0,332.5,9621,400,0,7560,4019,5,0,1.000,22.0,20.0,20,150.0
|
||||
106,winter,909.2,454.0,344.5,154.2,31.2,0,11.8,0.765,100.0,320.7,9511,206,0,7704,4179,5,0,1.000,30.0,20.0,20,150.0
|
||||
107,winter,882.0,454.0,287.5,145.4,138.1,0,8.7,0.915,100.0,328.9,9201,767,0,7640,3992,5,0,1.000,30.0,20.0,20,150.0
|
||||
108,winter,890.8,454.0,243.5,137.6,92.3,0,6.2,0.815,100.0,313.6,9552,264,0,7697,4087,5,0,1.000,30.0,20.0,20,150.0
|
||||
109,winter,879.2,454.0,247.5,130.4,65.5,0,9.8,0.715,100.0,361.7,8946,951,0,7590,4113,5,0,1.000,26.0,20.0,20,126.0
|
||||
110,winter,920.8,454.0,251.5,125.1,11.8,0,0.0,0.865,100.0,335.7,3511,541,0,4320,13228,5,0,1.000,30.0,20.0,20,102.8
|
||||
111,winter,963.2,454.0,255.5,122.8,5.6,0,0.0,0.765,100.0,304.6,3442,822,0,0,17336,5,0,1.000,30.0,20.0,20,98.8
|
||||
112,winter,971.2,454.0,259.5,120.6,44.0,0,0.0,0.915,100.0,298.6,5959,598,0,534,14509,5,0,1.000,30.0,20.0,20,90.8
|
||||
113,winter,976.8,454.0,263.5,120.0,46.9,0,0.5,0.815,100.0,330.5,6770,741,0,759,13330,5,0,1.000,30.0,20.0,20,70.8
|
||||
114,winter,1005.6,454.0,267.5,120.0,19.2,0,0.0,0.715,100.0,367.3,5324,688,0,0,15588,5,0,1.000,30.0,20.0,20,16.0
|
||||
115,winter,1035.2,454.0,271.5,120.0,18.4,0,4.2,0.865,100.0,349.9,5899,953,0,381,14367,5,0,1.000,30.0,20.0,20,0.0
|
||||
116,winter,1072.0,454.0,275.5,120.0,11.2,0,0.0,0.765,100.0,325.0,9609,276,0,3166,8549,5,0,1.000,30.0,20.0,20,0.0
|
||||
117,winter,1090.0,454.0,279.5,120.0,30.0,0,5.6,0.915,100.0,305.0,9472,513,0,6706,4909,5,0,1.000,30.0,20.0,20,0.0
|
||||
118,winter,1106.4,454.0,283.5,120.0,31.6,2,0.0,0.815,100.0,301.6,9174,610,0,5595,6221,5,0,1.000,30.0,20.0,20,0.0
|
||||
119,winter,1141.6,454.0,287.5,120.0,12.8,0,0.0,0.715,100.0,279.4,9434,437,0,7484,4245,5,0,1.000,30.0,20.0,20,0.0
|
||||
120,spring,984.0,454.0,291.5,360.0,301.6,0,8.1,0.665,100.0,297.1,9156,672,0,7620,4152,5,0,1.000,30.0,20.0,20,0.0
|
||||
121,spring,1043.6,454.0,295.5,594.0,90.4,0,2.2,0.415,100.0,280.6,9626,244,0,7557,4173,5,0,1.000,30.0,20.0,20,0.0
|
||||
122,spring,1168.9,454.0,299.5,671.0,166.4,0,1.6,0.862,100.0,340.8,6567,819,0,6130,8084,5,0,1.000,30.0,20.0,20,23.6
|
||||
123,spring,1281.7,454.0,303.5,696.0,156.8,0,0.0,0.846,100.0,331.2,4513,3198,0,4220,9669,5,0,1.000,30.0,20.0,20,133.8
|
||||
124,spring,1394.2,454.0,307.5,676.0,224.0,0,6.0,0.746,100.0,368.9,5187,1468,227,3447,11271,5,0,1.000,30.0,20.0,20,150.0
|
||||
125,spring,1508.6,454.0,311.5,781.0,50.8,0,0.0,0.862,100.0,364.8,1079,2105,505,0,17911,5,0,1.000,20.0,20.0,20,150.0
|
||||
126,spring,1596.9,454.0,315.5,791.0,65.2,0,0.0,0.796,100.0,369.9,1614,974,542,0,18470,5,0,1.000,21.6,20.0,20,150.0
|
||||
127,spring,1648.2,454.0,319.5,795.0,42.8,0,0.0,0.862,100.0,358.8,1448,1214,536,0,18402,5,0,1.000,22.2,20.0,20,150.0
|
||||
128,spring,1655.9,454.0,323.5,795.0,67.2,0,0.0,0.846,100.0,367.3,1873,981,524,0,18222,5,0,1.000,21.8,20.0,20,150.0
|
||||
129,spring,1658.6,454.0,327.5,795.0,55.6,0,0.0,0.746,100.0,369.8,1672,1457,507,0,17964,5,0,1.000,23.5,20.0,20,150.0
|
||||
130,spring,1661.1,454.0,331.5,796.0,59.2,0,0.0,0.862,100.0,367.9,1720,834,542,0,18504,5,0,1.000,18.7,20.0,20,150.0
|
||||
131,spring,1658.3,454.0,335.5,796.0,49.2,0,0.0,0.796,100.0,368.8,1444,1197,537,0,18422,5,0,1.000,19.5,20.0,20,150.0
|
||||
132,spring,1652.7,454.0,339.5,800.0,58.0,0,0.0,0.862,100.0,365.8,1711,1343,513,0,18033,5,0,1.000,21.5,20.0,20,150.0
|
||||
133,spring,1646.6,454.0,343.5,798.0,60.9,0,0.0,0.846,100.0,367.9,1758,1113,525,0,18204,5,0,1.000,21.9,20.0,20,150.0
|
||||
134,spring,1646.6,454.0,347.5,798.0,44.8,0,0.0,0.746,100.0,368.8,1262,1434,536,0,18368,5,0,1.000,22.4,20.0,20,150.0
|
||||
135,spring,1641.0,454.0,351.5,798.0,58.4,0,0.0,0.862,100.0,364.8,1716,1132,527,0,18225,5,0,1.000,21.6,20.0,20,150.0
|
||||
136,spring,1638.9,454.0,355.5,798.0,55.3,0,0.0,0.796,100.0,369.9,1879,991,524,0,18206,5,0,1.000,18.8,20.0,20,150.0
|
||||
137,spring,1643.7,454.0,359.5,800.0,41.6,0,0.0,0.862,100.0,358.8,1332,1605,518,0,18145,5,0,1.000,19.6,20.0,20,150.0
|
||||
138,spring,1638.1,454.0,363.5,800.0,66.8,0,0.0,0.846,100.0,364.8,1855,983,526,0,18236,5,0,1.000,22.4,20.0,20,150.0
|
||||
139,spring,1637.7,454.0,367.5,796.0,56.0,0,0.0,0.746,100.0,369.8,1825,1027,526,0,18222,5,0,1.000,19.2,20.0,20,150.0
|
||||
140,spring,1632.9,454.0,371.5,796.0,63.6,0,0.0,0.862,100.0,367.8,1651,1456,510,0,17983,5,0,1.000,18.4,20.0,20,150.0
|
||||
141,spring,1630.1,454.0,375.5,796.0,52.0,0,0.0,0.795,100.0,368.8,1468,1277,533,0,18322,5,0,1.000,21.3,20.0,20,150.0
|
||||
142,spring,1627.7,454.0,379.5,800.0,57.6,0,0.0,0.862,100.0,365.8,1725,1120,527,0,18228,5,0,1.000,22.1,20.0,20,150.0
|
||||
143,spring,1624.0,454.0,383.5,796.0,59.3,0,0.0,0.845,100.0,366.8,1956,995,518,0,18131,5,0,1.000,22.1,20.0,20,150.0
|
||||
144,spring,1629.2,454.0,387.0,796.0,45.6,0,0.0,0.745,100.0,368.8,1386,1308,537,0,18369,5,0,1.000,22.3,20.0,20,150.0
|
||||
145,spring,1626.8,454.0,390.5,798.0,57.6,0,0.0,0.862,100.0,364.8,1777,1246,517,0,18060,5,0,1.000,21.9,20.0,20,150.0
|
||||
146,spring,1624.8,454.0,394.0,798.0,57.6,0,0.0,0.795,100.0,370.1,1825,1115,520,0,18140,5,0,1.000,21.1,20.0,20,150.0
|
||||
147,spring,1633.2,454.0,397.0,800.0,39.2,0,0.0,0.862,100.0,358.8,1406,1252,534,0,18408,5,0,1.000,19.7,20.0,20,150.0
|
||||
148,spring,1623.9,454.0,398.0,798.0,67.7,0,0.0,0.845,100.0,363.8,1888,1130,513,0,18069,5,0,1.000,20.1,20.0,20,150.0
|
||||
149,spring,1620.3,454.0,399.0,798.0,61.6,0,0.0,0.745,100.0,369.8,1745,1178,520,0,18157,5,0,1.000,19.9,20.0,20,150.0
|
||||
150,summer,1614.3,454.0,400.0,790.0,63.6,0,0.0,0.862,100.0,367.8,1786,1409,508,0,17897,5,0,1.000,21.1,20.0,20,150.0
|
||||
151,summer,1613.5,454.0,400.0,790.0,50.8,0,0.0,0.795,100.0,368.8,1622,1218,527,0,18233,5,0,1.000,21.9,20.0,20,150.0
|
||||
152,summer,1610.7,454.0,400.0,792.0,56.8,0,0.0,0.862,100.0,365.8,1883,1079,517,0,18121,5,0,1.000,20.8,20.0,20,150.0
|
||||
153,summer,1600.7,454.0,400.0,792.0,62.0,0,0.0,0.845,100.0,366.3,2061,998,513,0,18028,5,0,1.000,20.8,20.0,20,150.0
|
||||
154,summer,1600.7,454.0,400.0,792.0,50.0,0,0.0,0.745,100.0,368.8,1390,1435,528,0,18247,5,0,1.000,21.3,20.0,20,150.0
|
||||
155,summer,1597.9,454.0,400.0,792.0,62.8,0,0.0,0.862,100.0,364.8,2071,1170,502,0,17857,5,0,1.000,21.3,20.0,20,150.0
|
||||
156,summer,1591.9,454.0,400.0,792.0,64.0,0,0.0,0.795,100.0,369.9,2231,1229,490,0,17650,5,0,1.000,21.3,20.0,20,150.0
|
||||
157,summer,1595.9,454.0,400.0,794.0,42.0,0,0.0,0.862,100.0,358.8,1410,1917,496,0,17777,5,0,1.000,21.4,20.0,20,150.0
|
||||
158,summer,1585.9,454.0,400.0,788.0,68.0,0,0.0,0.845,100.0,365.8,1832,1044,523,0,18201,5,0,1.000,21.4,20.0,20,150.0
|
||||
159,summer,1583.0,454.0,400.0,788.0,58.9,0,0.0,0.745,100.0,369.8,1976,1562,486,0,17576,5,0,1.000,20.4,20.0,20,150.0
|
||||
160,summer,1581.0,454.0,400.0,786.0,66.0,0,0.0,0.862,100.0,367.9,1901,1736,478,0,17485,5,0,1.000,20.4,20.0,20,150.0
|
||||
161,summer,1583.0,454.0,400.0,786.0,50.0,0,0.0,0.795,100.0,368.8,1641,1129,530,0,18300,5,0,1.000,20.5,20.0,20,150.0
|
||||
162,summer,1582.5,454.0,400.0,786.0,56.5,0,0.0,0.862,100.0,365.8,1882,1218,507,0,17993,5,0,1.000,20.5,20.0,20,150.0
|
||||
163,summer,1574.5,454.0,400.0,790.0,62.0,0,0.0,0.845,100.0,366.8,1961,1159,510,0,17970,5,0,1.000,20.5,20.0,20,150.0
|
||||
164,summer,1576.5,454.0,400.0,790.0,50.0,0,0.0,0.745,100.0,368.8,1597,1326,521,0,18156,5,0,1.000,21.5,20.0,20,150.0
|
||||
165,summer,1574.5,454.0,400.0,788.0,62.0,0,0.0,0.862,100.0,364.8,1932,1357,501,0,17810,5,0,1.000,21.5,20.0,20,150.0
|
||||
166,summer,1570.5,454.0,400.0,790.0,64.0,0,0.0,0.795,100.0,369.9,2264,1168,493,0,17675,5,0,1.000,21.5,20.0,20,150.0
|
||||
167,summer,1578.5,454.0,400.0,792.0,42.0,0,0.0,0.862,100.0,358.8,1456,1958,489,0,17697,5,0,1.000,21.6,20.0,20,150.0
|
||||
168,summer,1574.5,454.0,400.0,786.0,64.0,0,0.0,0.845,100.0,363.3,2417,1232,479,0,17472,5,0,1.000,21.6,20.0,20,150.0
|
||||
169,summer,1572.0,454.0,400.0,786.0,60.5,0,0.0,0.745,100.0,369.8,2085,1022,510,0,17983,5,0,1.000,20.1,20.0,20,150.0
|
||||
170,summer,1568.0,454.0,400.0,782.0,66.0,0,0.0,0.862,100.0,368.0,2052,1509,483,0,17556,5,0,1.000,20.1,20.0,20,150.0
|
||||
171,summer,1570.0,454.0,400.0,780.0,50.0,0,0.0,0.795,100.0,368.8,1637,1356,517,0,18090,5,0,1.000,20.3,20.0,20,150.0
|
||||
172,summer,1570.0,454.0,400.0,780.0,58.0,0,0.0,0.862,100.0,365.8,2118,1250,496,0,17736,5,0,1.000,20.3,20.0,20,150.0
|
||||
173,summer,1566.0,454.0,400.0,780.0,62.0,0,0.0,0.845,100.0,366.8,2220,1250,491,0,17639,5,0,1.000,20.3,20.0,20,150.0
|
||||
174,summer,1572.5,454.0,400.0,782.0,48.0,0,0.0,0.745,100.0,368.8,1548,1395,522,0,18135,5,0,1.000,19.3,20.0,20,150.0
|
||||
175,summer,1568.5,454.0,400.0,786.0,62.0,0,0.0,0.862,100.0,364.8,2157,1146,498,0,17799,5,0,1.000,19.3,20.0,20,150.0
|
||||
176,summer,1564.5,454.0,400.0,788.0,64.0,0,0.0,0.795,100.0,369.9,2141,1305,491,0,17663,5,0,1.000,19.3,20.0,20,150.0
|
||||
177,summer,1574.5,454.0,400.0,786.0,42.0,0,0.0,0.862,100.0,358.8,1508,1437,519,0,18136,5,0,1.000,19.4,20.0,20,150.0
|
||||
178,summer,1568.5,454.0,400.0,782.0,64.0,0,0.0,0.845,100.0,363.3,2376,1088,488,0,17648,5,0,1.000,19.4,20.0,20,150.0
|
||||
179,summer,1564.5,454.0,400.0,784.0,64.0,0,0.0,0.745,100.0,369.8,1978,1420,495,0,17707,5,0,1.000,19.9,20.0,20,150.0
|
||||
180,autumn,1562.5,454.0,400.0,779.2,66.0,0,0.0,0.861,100.0,367.9,1882,1638,486,0,17594,5,0,1.000,19.9,20.0,20,150.0
|
||||
181,autumn,1568.5,454.0,400.0,776.4,50.0,0,0.0,0.795,100.0,368.8,1525,1449,518,0,18108,5,0,1.000,19.9,20.0,20,150.0
|
||||
182,autumn,1570.5,454.0,400.0,774.4,58.0,0,0.0,0.861,100.0,365.8,1885,1274,508,0,17933,5,0,1.000,19.9,20.0,20,150.0
|
||||
183,autumn,1566.5,454.0,400.0,771.6,62.0,0,0.0,0.845,100.0,366.8,2127,1142,501,0,17830,5,0,1.000,19.9,20.0,20,150.0
|
||||
184,autumn,1572.5,454.0,400.0,772.2,49.4,0,0.0,0.745,100.0,368.2,1507,1292,528,0,18273,5,0,1.000,20.9,20.0,20,150.0
|
||||
185,autumn,1572.5,454.0,400.0,769.4,62.0,0,0.0,0.861,100.0,364.2,2098,1343,491,0,17668,5,0,1.000,20.9,20.0,20,150.0
|
||||
186,autumn,1564.5,454.0,400.0,769.4,68.0,0,0.0,0.795,100.0,371.5,2395,1066,490,0,17649,5,0,1.000,20.9,20.0,20,150.0
|
||||
187,autumn,1574.5,454.0,400.0,777.4,36.0,0,0.0,0.861,100.0,358.2,1274,1656,519,0,18151,5,0,1.000,19.2,20.0,20,150.0
|
||||
188,autumn,1566.5,454.0,400.0,776.0,65.6,0,0.0,0.845,100.0,363.8,1963,1143,512,0,17982,5,0,1.000,19.2,20.0,20,150.0
|
||||
189,autumn,1560.0,454.0,400.0,768.6,67.5,0,0.0,0.745,100.0,371.8,2132,1614,472,0,17382,5,0,1.000,19.2,20.0,20,150.0
|
||||
190,autumn,1554.0,454.0,400.0,766.2,69.2,0,0.0,0.861,100.0,371.2,2186,1692,465,0,17257,5,0,1.000,19.2,20.0,20,150.0
|
||||
191,autumn,1560.0,454.0,400.0,771.4,43.6,0,0.0,0.795,100.0,367.7,1458,1380,527,0,18235,5,0,1.000,19.4,20.0,20,150.0
|
||||
192,autumn,1560.0,454.0,400.0,772.6,59.2,0,0.0,0.861,100.0,363.8,2373,1068,494,0,17665,5,0,1.000,19.5,20.0,20,150.0
|
||||
193,autumn,1556.0,454.0,400.0,771.4,67.2,0,0.0,0.845,100.0,370.0,2323,1208,486,0,17583,5,0,1.000,19.5,20.0,20,150.0
|
||||
194,autumn,1564.0,454.0,400.0,775.8,43.2,0,0.0,0.745,100.0,367.2,1356,1780,506,0,17958,5,0,1.000,20.5,20.0,20,150.0
|
||||
195,autumn,1564.0,454.0,400.0,774.6,61.2,0,0.0,0.861,100.0,362.4,2164,1261,492,0,17683,5,0,1.000,20.5,20.0,20,150.0
|
||||
196,autumn,1558.0,454.0,400.0,771.8,71.2,0,0.0,0.795,100.0,372.6,2587,1107,474,0,17432,5,0,1.000,20.5,20.0,20,150.0
|
||||
197,autumn,1568.0,454.0,400.0,778.2,35.2,0,0.0,0.861,100.0,356.8,1397,1982,495,0,17726,5,0,1.000,20.5,20.0,20,150.0
|
||||
198,autumn,1564.0,454.0,400.0,778.4,64.0,0,0.0,0.844,100.0,361.8,2279,1226,487,0,17608,5,0,1.000,20.5,20.0,20,150.0
|
||||
199,autumn,1560.0,454.0,400.0,774.0,70.0,0,0.0,0.744,100.0,373.8,2075,1176,500,0,17849,5,0,1.000,19.5,20.0,20,150.0
|
||||
200,autumn,1558.0,454.0,400.0,770.8,66.0,0,0.0,0.861,100.0,371.9,2133,1599,473,0,17395,5,0,1.000,19.5,20.0,20,150.0
|
||||
201,autumn,1564.0,454.0,400.0,778.8,42.0,0,0.0,0.794,100.0,366.9,1473,1338,528,0,18261,5,0,1.000,19.6,20.0,20,150.0
|
||||
202,autumn,1564.0,454.0,400.0,777.6,62.0,0,0.0,0.861,100.0,365.8,2243,1072,496,0,17789,5,0,1.000,19.7,20.0,20,150.0
|
||||
203,autumn,1559.5,454.0,400.0,771.6,64.5,0,0.0,0.844,100.0,370.3,2218,1253,490,0,17639,5,0,1.000,19.7,20.0,20,150.0
|
||||
204,autumn,1565.5,454.0,400.0,780.2,41.8,0,0.0,0.744,100.0,366.6,1497,1379,519,73,18132,5,0,1.000,20.2,20.0,20,150.0
|
||||
205,autumn,1561.5,454.0,400.0,773.4,66.0,0,0.0,0.861,100.0,364.6,2277,1369,478,0,17476,5,0,1.000,20.2,20.0,20,150.0
|
||||
206,autumn,1553.5,454.0,400.0,769.6,71.4,0,0.0,0.794,100.0,375.0,2466,1054,487,0,17593,5,0,1.000,20.2,20.0,20,150.0
|
||||
207,autumn,1563.5,454.0,400.0,774.0,37.2,0,0.5,0.861,100.0,361.2,1388,1570,518,0,18124,5,0,1.000,20.2,20.0,20,150.0
|
||||
208,autumn,1559.5,454.0,400.0,770.0,67.2,0,0.0,0.844,100.0,366.4,2359,1168,486,0,17587,5,0,1.000,20.2,20.0,20,150.0
|
||||
209,autumn,1557.0,454.0,400.0,767.6,61.7,0,0.0,0.744,100.0,374.6,2059,1347,496,0,17698,5,0,1.000,20.2,20.0,20,150.0
|
||||
210,winter,1536.2,454.0,400.0,694.2,58.0,0,0.0,0.861,100.0,368.6,1656,1433,514,0,17997,5,0,1.000,20.2,20.0,20,150.0
|
||||
211,winter,1516.2,454.0,400.0,629.3,53.7,0,0.0,0.794,100.0,369.5,2127,1656,464,120,17233,5,0,1.000,20.2,20.0,20,150.0
|
||||
212,winter,1498.6,454.0,400.0,564.6,56.9,0,0.0,0.861,100.0,366.8,2224,1275,489,0,17612,5,0,1.000,18.4,20.0,20,150.0
|
||||
213,winter,1478.8,454.0,400.0,506.5,59.2,0,2.7,0.844,100.0,367.0,2993,1500,416,250,16441,5,0,1.000,20.1,20.0,20,150.0
|
||||
214,winter,1465.2,454.0,400.0,455.5,50.8,0,0.0,0.744,100.0,369.2,1978,2377,437,0,16808,5,0,1.000,19.3,20.0,20,150.0
|
||||
215,winter,1447.9,454.0,400.0,398.7,64.6,0,0.0,0.861,100.0,368.5,3118,2007,386,81,16008,5,0,1.000,18.6,20.0,20,150.0
|
||||
216,winter,1431.4,454.0,400.0,352.4,59.9,0,0.4,0.794,100.0,373.4,2735,2008,409,79,16369,5,0,1.000,18.2,20.0,20,150.0
|
||||
217,winter,1422.1,454.0,400.0,322.8,37.7,0,5.5,0.861,100.0,355.5,2758,2797,340,754,14951,5,0,1.000,20.7,20.0,20,150.0
|
||||
218,winter,1404.1,454.0,400.0,278.3,66.6,0,3.5,0.844,100.0,362.3,5946,1351,220,2130,11953,5,0,1.000,20.2,20.0,20,150.0
|
||||
219,winter,1284.8,454.0,400.0,255.1,156.7,0,6.8,0.744,100.0,362.9,5154,2015,213,3447,10771,5,0,1.000,23.7,20.0,20,150.0
|
||||
220,winter,889.8,454.0,322.0,234.4,528.4,0,2.8,0.861,100.0,350.1,7834,407,123,4730,8506,5,0,1.000,20.0,20.0,20,150.0
|
||||
221,winter,898.1,454.0,238.0,215.3,133.1,0,3.1,0.794,100.0,345.1,8887,804,10,6903,4996,5,0,1.000,20.0,20.0,20,150.0
|
||||
222,winter,880.8,454.0,242.0,199.9,67.3,0,12.6,0.861,100.0,330.5,9293,510,4,7610,4183,5,0,1.000,27.7,20.0,20,150.0
|
||||
223,winter,888.6,454.0,246.0,183.2,46.7,0,12.9,0.844,100.0,325.0,9357,528,0,7657,4058,5,0,1.000,30.0,20.0,20,150.0
|
||||
224,winter,896.3,454.0,250.0,169.6,44.3,0,12.2,0.744,100.0,338.2,9554,316,0,7672,4058,5,0,1.000,26.0,20.0,20,150.0
|
||||
225,winter,881.7,454.0,248.0,158.2,72.8,0,7.4,0.861,100.0,330.1,9438,583,0,7655,3924,5,0,1.000,30.0,20.0,20,150.0
|
||||
226,winter,889.7,454.0,240.0,149.0,55.3,0,7.7,0.794,100.0,318.3,9452,372,0,7599,4177,5,0,1.000,30.0,20.0,20,150.0
|
||||
227,winter,901.2,454.0,244.0,141.3,40.0,0,0.0,0.861,100.0,317.3,9423,319,0,7687,4171,5,0,1.000,26.0,20.0,20,150.0
|
||||
228,winter,900.8,454.0,248.0,135.3,52.7,0,9.1,0.844,100.0,312.4,9029,787,0,7633,4151,5,0,1.000,30.0,20.0,20,150.0
|
||||
229,winter,919.3,454.0,252.0,129.9,32.8,0,1.6,0.744,100.0,295.6,8818,466,0,7334,4982,5,0,1.000,30.0,20.0,20,150.0
|
||||
230,winter,875.7,454.0,216.5,125.4,137.4,0,0.0,0.861,100.0,300.2,9818,226,0,7324,4232,5,0,1.000,30.0,20.0,20,150.0
|
||||
231,winter,909.3,454.0,221.0,122.9,17.1,0,0.0,0.794,100.0,328.0,3772,1460,0,4320,12048,5,0,1.000,30.0,20.0,20,98.1
|
||||
232,winter,938.1,454.0,225.5,120.9,23.3,0,0.0,0.861,100.0,317.3,5247,733,0,0,15620,5,0,1.000,30.0,20.0,20,79.1
|
||||
233,winter,965.7,454.0,230.0,120.0,25.3,0,0.0,0.844,100.0,332.5,5062,1048,0,268,15222,5,0,1.000,30.0,20.0,20,47.1
|
||||
234,winter,1000.9,454.0,234.5,120.0,12.8,0,0.0,0.744,100.0,329.3,4645,792,0,0,16163,5,0,1.000,30.0,20.0,20,26.1
|
||||
235,winter,1030.1,454.0,239.0,120.0,18.8,1,2.4,0.860,100.0,320.1,7948,713,7,1983,10949,5,0,1.000,27.5,20.0,20,0.0
|
||||
236,winter,1032.5,454.0,243.5,120.0,45.6,0,0.0,0.794,100.0,326.2,9558,314,0,6747,4981,5,0,1.000,30.0,20.0,20,0.0
|
||||
237,winter,1057.7,454.0,248.0,120.0,22.8,0,3.4,0.860,100.0,304.0,9098,899,0,6918,4685,5,0,1.000,30.0,20.0,20,0.0
|
||||
238,winter,1082.1,454.0,252.5,120.0,23.6,5,4.5,0.844,100.0,287.6,8983,996,0,5768,5853,5,0,1.000,30.0,20.0,20,0.0
|
||||
239,winter,1080.5,454.0,257.0,120.0,49.6,2,11.7,0.744,100.0,276.4,9458,418,0,6007,5717,5,0,1.000,30.0,20.0,20,0.0
|
||||
240,spring,970.5,454.0,243.5,360.0,272.0,1,6.6,0.710,100.0,288.2,9820,368,0,7697,3715,5,0,1.000,30.0,20.0,20,0.0
|
||||
241,spring,988.9,454.0,248.0,594.0,131.6,0,12.5,0.444,100.0,303.9,9510,336,0,7573,4181,5,0,1.000,30.0,20.0,20,0.0
|
||||
242,spring,1117.3,454.0,252.5,644.0,195.6,0,4.2,0.829,100.0,354.1,7273,1308,0,6775,6244,5,0,1.000,30.0,20.0,20,29.3
|
||||
243,spring,1245.3,454.0,257.0,647.0,216.0,0,0.8,0.862,100.0,328.5,5689,2003,46,5297,8565,5,0,1.000,30.0,20.0,20,150.0
|
||||
|
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="csv_translation"
|
||||
type="Translation"
|
||||
uid="uid://da6vou2v16yvt"
|
||||
|
||||
[deps]
|
||||
|
||||
files=["res://soak_stats.season.translation", "res://soak_stats.wood.translation", "res://soak_stats.stone.translation", "res://soak_stats.clay.translation", "res://soak_stats.food.translation", "res://soak_stats.harvested.translation", "res://soak_stats.trades.translation", "res://soak_stats.avg.translation", "res://soak_stats.avg.translation", "res://soak_stats.avg.translation", "res://soak_stats.carried.translation", "res://soak_stats.gather.translation", "res://soak_stats.build.translation", "res://soak_stats.trade.translation", "res://soak_stats.rest.translation", "res://soak_stats.social.translation", "res://soak_stats.shelters.translation", "res://soak_stats.ruins.translation", "res://soak_stats.avg.translation", "res://soak_stats.upkeep.translation", "res://soak_stats.burn.translation", "res://soak_stats.modules.translation", "res://soak_stats.granary.translation"]
|
||||
|
||||
source_file="res://soak_stats.csv"
|
||||
dest_files=["res://soak_stats.season.translation", "res://soak_stats.wood.translation", "res://soak_stats.stone.translation", "res://soak_stats.clay.translation", "res://soak_stats.food.translation", "res://soak_stats.harvested.translation", "res://soak_stats.trades.translation", "res://soak_stats.avg.translation", "res://soak_stats.avg.translation", "res://soak_stats.avg.translation", "res://soak_stats.carried.translation", "res://soak_stats.gather.translation", "res://soak_stats.build.translation", "res://soak_stats.trade.translation", "res://soak_stats.rest.translation", "res://soak_stats.social.translation", "res://soak_stats.shelters.translation", "res://soak_stats.ruins.translation", "res://soak_stats.avg.translation", "res://soak_stats.upkeep.translation", "res://soak_stats.burn.translation", "res://soak_stats.modules.translation", "res://soak_stats.granary.translation"]
|
||||
|
||||
[params]
|
||||
|
||||
compress=1
|
||||
delimiter=0
|
||||
unescape_keys=false
|
||||
unescape_translations=true
|
||||
Reference in New Issue
Block a user