ecopoesis 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ecopoesis-0.1.0/PKG-INFO +266 -0
- ecopoesis-0.1.0/README.md +239 -0
- ecopoesis-0.1.0/pyproject.toml +78 -0
- ecopoesis-0.1.0/setup.cfg +4 -0
- ecopoesis-0.1.0/src/ecopoesis/__init__.py +71 -0
- ecopoesis-0.1.0/src/ecopoesis/__version__.py +9 -0
- ecopoesis-0.1.0/src/ecopoesis/_gen_pb2.py +37 -0
- ecopoesis-0.1.0/src/ecopoesis/actions.py +547 -0
- ecopoesis-0.1.0/src/ecopoesis/cli.py +1338 -0
- ecopoesis-0.1.0/src/ecopoesis/climate.py +310 -0
- ecopoesis-0.1.0/src/ecopoesis/controls.py +102 -0
- ecopoesis-0.1.0/src/ecopoesis/geology.py +376 -0
- ecopoesis-0.1.0/src/ecopoesis/life.py +572 -0
- ecopoesis-0.1.0/src/ecopoesis/persistence.py +667 -0
- ecopoesis-0.1.0/src/ecopoesis/render.py +971 -0
- ecopoesis-0.1.0/src/ecopoesis/resources.py +316 -0
- ecopoesis-0.1.0/src/ecopoesis/simulation.py +484 -0
- ecopoesis-0.1.0/src/ecopoesis/simulation_pb2.py +36 -0
- ecopoesis-0.1.0/src/ecopoesis/state.py +194 -0
- ecopoesis-0.1.0/src/ecopoesis/terrain.py +209 -0
- ecopoesis-0.1.0/src/ecopoesis.egg-info/PKG-INFO +266 -0
- ecopoesis-0.1.0/src/ecopoesis.egg-info/SOURCES.txt +43 -0
- ecopoesis-0.1.0/src/ecopoesis.egg-info/dependency_links.txt +1 -0
- ecopoesis-0.1.0/src/ecopoesis.egg-info/entry_points.txt +2 -0
- ecopoesis-0.1.0/src/ecopoesis.egg-info/requires.txt +1 -0
- ecopoesis-0.1.0/src/ecopoesis.egg-info/top_level.txt +1 -0
- ecopoesis-0.1.0/tests/test_actions.py +205 -0
- ecopoesis-0.1.0/tests/test_cli_smoke.py +201 -0
- ecopoesis-0.1.0/tests/test_controls.py +139 -0
- ecopoesis-0.1.0/tests/test_diff.py +258 -0
- ecopoesis-0.1.0/tests/test_geology.py +279 -0
- ecopoesis-0.1.0/tests/test_install.py +42 -0
- ecopoesis-0.1.0/tests/test_life.py +362 -0
- ecopoesis-0.1.0/tests/test_performance.py +95 -0
- ecopoesis-0.1.0/tests/test_persistence.py +690 -0
- ecopoesis-0.1.0/tests/test_population_determinism.py +220 -0
- ecopoesis-0.1.0/tests/test_property_roundtrips.py +193 -0
- ecopoesis-0.1.0/tests/test_render.py +709 -0
- ecopoesis-0.1.0/tests/test_resources.py +153 -0
- ecopoesis-0.1.0/tests/test_save_load.py +235 -0
- ecopoesis-0.1.0/tests/test_save_management.py +330 -0
- ecopoesis-0.1.0/tests/test_simulation.py +82 -0
- ecopoesis-0.1.0/tests/test_simulation_tick.py +60 -0
- ecopoesis-0.1.0/tests/test_terrain_climate.py +303 -0
- ecopoesis-0.1.0/tests/test_watch.py +462 -0
ecopoesis-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ecopoesis
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ecopoesis: The Planetary Simulator — a deterministic terminal-driven planet simulator.
|
|
5
|
+
Author-email: Ecopoesis Engineering <engineering@ecopoesis.dev>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ecopoesis/ecopoesis
|
|
8
|
+
Project-URL: Repository, https://github.com/ecopoesis/ecopoesis.git
|
|
9
|
+
Project-URL: Issues, https://github.com/ecopoesis/ecopoesis/issues
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/ecopoesis/ecopoesis/issues
|
|
11
|
+
Keywords: ecopoesis,simulation,terraforming,procedural,deterministic,game,planetary,earth
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
|
+
Classifier: Topic :: Games/Entertainment :: Simulation
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Hydrology
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: protobuf<7,>=6.33.5
|
|
27
|
+
|
|
28
|
+
# Ecopoesis
|
|
29
|
+
|
|
30
|
+
Project workspace for the modern Ecopoesis remake.
|
|
31
|
+
|
|
32
|
+
## Status
|
|
33
|
+
Slice 12 implemented - canonical "Getting Started" walkthrough (`docs/GETTING_STARTED.md`), end-to-end demo (`scripts/demo.py` + `scripts/demo.sh`), consolidated README Quickstart sections, public API listing. **V1 is complete** — all 5 V1 acceptance criteria met.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install ecopoesis
|
|
39
|
+
ecopoesis run --seed demo --ticks 5
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or for development:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/ecopoesis/ecopoesis.git
|
|
46
|
+
cd ecopoesis
|
|
47
|
+
uv sync
|
|
48
|
+
uv run ecopoesis run --seed demo --ticks 5
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The current version is `0.1.0` (V1 candidate). See `CHANGELOG.md` for what's in this release and `RELEASE.md` for the maintainer release procedure.
|
|
52
|
+
|
|
53
|
+
## Visualising the world
|
|
54
|
+
|
|
55
|
+
Run the renderer against any save file to see the simulation as a grid of ASCII glyphs. Example:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python -m ecopoesis.cli run --seed demo --ticks 50 --out save.json
|
|
59
|
+
python -m ecopoesis.cli render --in save.json --layer terrain
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Terrain glyphs: ` ` ocean, `.` coast, `,` lowland, `:` plain, `o` hill, `^` mountain, `#` peak. Life glyphs: `M` microbe, `P` plant, `.` none. Resources glyphs: `+`/`-`/`0`-`9` for minerals, `0`-`9` for water and energy. Use `--layer all` to stack all four layers (terrain → climate → life → resources). Optional `--out PATH` mirrors the same bytes to a file; `--no-color` is accepted as a no-op forward-compat flag; `--scale` is currently locked to `1`.
|
|
63
|
+
|
|
64
|
+
## Slice 7 Refactor: Unified RNG-state pattern
|
|
65
|
+
|
|
66
|
+
All four RNG-driven layers (Climate, Life, Resources, Geology) now share the same
|
|
67
|
+
save/load pattern: each engine's `get_state()` captures its live `rng_state`
|
|
68
|
+
(via `random.Random.getstate()`) into the dataclass, and `restore_state()`
|
|
69
|
+
restores via `rng.setstate(...)`. This eliminates the count-based replay
|
|
70
|
+
pattern that caused the pre-Slice-7 climate RNG bug (5 determinism tests were
|
|
71
|
+
skipped; they now pass with the refactor in place). Legacy saves without
|
|
72
|
+
`rng_state` still load — the engines just start fresh from their constructed
|
|
73
|
+
RNG.
|
|
74
|
+
|
|
75
|
+
## What's Implemented (Slice 1-4)
|
|
76
|
+
|
|
77
|
+
Slices 1-2 (core simulation shell):
|
|
78
|
+
- **Project Scaffold**: Complete Python package structure
|
|
79
|
+
- **Deterministic Simulation**: `Simulation` class initialized from a seed
|
|
80
|
+
- **Fixed Tick Progression**: Simulation advances ticks deterministically
|
|
81
|
+
- **Serializable State**: Simulation state can be saved and loaded as JSON
|
|
82
|
+
- **File-based Persistence**: Save/load helpers write and read versioned save files
|
|
83
|
+
- **Determinism Verification**: Tests showing same seed + same operations produce identical results
|
|
84
|
+
- **Different Seed Behavior**: Different seeds produce different initial states
|
|
85
|
+
|
|
86
|
+
Slice 3 (terrain + climate):
|
|
87
|
+
- **Terrain Layer**: Heightmap grid (default 8x8, elevation values 0-255) with deterministic generation from seed and per-tick diffusion-based erosion
|
|
88
|
+
- **Climate Layer**: Temperature and precipitation grids (same dimensions), with per-tick thermal diffusion plus bounded random perturbation
|
|
89
|
+
- **Fixed Update Order**: Core LCG → Terrain erosion → Climate diffusion → Snapshot
|
|
90
|
+
- **Backward-Compatible Serialization**: Existing saves without terrain/climate data still load; new saves include the grid data
|
|
91
|
+
|
|
92
|
+
Slice 4 (life + population dynamics):
|
|
93
|
+
- **Life Layer**: `Species` (MICROBE, PLANT) population grids on the same simulation grid, with deterministic placement via `Life.place()` and per-tick logistic population update (`pop' = pop + r*pop*(1 - pop/K)` integer math)
|
|
94
|
+
- **Habitat Predicate**: a single `is_habitable(elevation, temperature, precipitation)` gate; populations only placed and only positive on habitable cells; non-habitable cells stay exactly 0
|
|
95
|
+
- **Carrying Capacity**: per-cell K derived from local terrain + climate; clamp to `>= 0` (no negative populations, verified across 500 adversarial ticks)
|
|
96
|
+
- **Wired into Simulation**: life step runs after climate in the fixed tick order (core LCG → terrain erosion → climate diffusion → life step → snapshot)
|
|
97
|
+
- **Backward-Compatible Saves**: Slice-3 saves without `life_state` still load; new saves include `populations: dict[str, list[int]]` + `life_ticks`
|
|
98
|
+
|
|
99
|
+
Slice 5 (CLI front-end):
|
|
100
|
+
- **Deterministic CLI**: `python -m ecopoesis.cli {run,save,load,summary}` with subcommands for seeding, ticking, optional `--seed-life SPECIES` placement, save/load round-trip, and an `argparse --help` page
|
|
101
|
+
- **Stable ASCII summary**: `format_summary()` emits one field per line (`seed=`, `tick=`, `terrain_dims=`, `climate_dims=`, `populations={MICROBE=N,PLANT=N}`, `life_ticks=`) so output is byte-stable across runs and easy to assert on
|
|
102
|
+
- **Friendly errors**: `main()` swallows exceptions and prints a single-line error to stderr with no traceback; missing files / bad seeds / negative ticks all return exit code 2
|
|
103
|
+
- **Console script**: `pyproject.toml` exposes `ecopoesis = "ecopoesis.cli:main"` so `pip install -e .` installs an entry point; `ecopoesis/__init__.py` re-exports `main` for library callers
|
|
104
|
+
- **Persistence gap closed**: `SimulationState.to_dict()` now emits the `"life"` envelope whenever `life_ticks > 0` (not only when populations exist), so save → load round-trips preserve `life_ticks` correctly. Backward-compat is preserved — old Slice-2/3 saves without `"life"` still load with `LifeState()` defaults.
|
|
105
|
+
|
|
106
|
+
Slice 6 (resource layer + environment controls):
|
|
107
|
+
- **Resources**: `ResourceState` with per-cell int arrays for minerals, water, energy, plus `resource_ticks`. `Resources` engine seeded deterministically at `seed_int + 3`, advances 3*cells draws per tick (one per array, row-major), all values clamped to `[0, MAX_RESOURCE=1000]`. RNG budget is replayable so post-load ticks remain deterministic.
|
|
108
|
+
- **Environment controls**: `EnvironmentControls(temperature_offset: int, rainfall_offset: int)` with `apply(climate_state)` that clamps in place to `[-500, 500]` and `[0, 255]`. Pure RNG-free transformation — does not perturb any layer's RNG budget.
|
|
109
|
+
- **Wired into Simulation**: resources step runs after climate in the fixed tick order (core LCG → terrain erosion → climate diffusion → life → resources → snapshot). Controls are applied at `Simulation.__init__` startup; `from_dict` RNG-skip block now imports `BOUNDS` from `.resources` so save → load → advance remains byte-identical to no-save advance.
|
|
110
|
+
- **CLI flags**: `python -m ecopoesis.cli run --temperature-offset 10 --rainfall-offset -5 --ticks 50`; `format_summary()` now emits `minerals=X water=Y energy=Z` and the controls echo.
|
|
111
|
+
- **Backward-compatible saves**: Slice-2/3/4 saves without `"resources"` or `"controls"` keys still load with defaults; new saves round-trip both layers faithfully.
|
|
112
|
+
|
|
113
|
+
Slice 7 (geology: tectonic shifts + volcanic eruptions):
|
|
114
|
+
- **Geology layer**: `GeologyState` tracks `tectonic_events_count`, `eruption_events_count`, and the live `rng_state`. `GeologyEngine` is seeded deterministically at `seed_int + 4`; per tick it draws two unconditional Bernoulli values against `tectonic_freq`/`eruption_freq` plus conditional draws for any events that fire (cell coords + signed delta / mineral deposit).
|
|
115
|
+
- **Tectonic shifts**: when triggered, picks a random cell and applies a small signed int delta, clamped to terrain bounds `[0, 255]`.
|
|
116
|
+
- **Volcanic eruptions**: when triggered, deposits `+10` minerals at a random cell, clamped to `[0, MAX_RESOURCE]`.
|
|
117
|
+
- **RNG state in snapshot**: `SimulationState` carries `geology_state.rng_state` (the engine's `random.Random` `getstate()` tuple), so `from_dict` restores via `setstate` rather than replaying by count — this avoids the count-based climate-RNG bug pattern entirely.
|
|
118
|
+
- **CLI flags**: `python -m ecopoesis.cli run --tectonic-frequency 0.1 --eruption-frequency 0.05 --ticks 500`; `format_summary()` appends `tectonic_events=N eruption_events=M`. Frequencies are validated to `[0.0, 1.0]` with friendly stderr errors and exit code 2.
|
|
119
|
+
- **Backward-compatible saves**: pre-Slice-7 saves without `"geology"` load with `GeologyState()` defaults; new saves round-trip the live RNG state so save → load → advance is byte-identical to no-save advance.
|
|
120
|
+
|
|
121
|
+
## Key Features
|
|
122
|
+
|
|
123
|
+
### Core Components
|
|
124
|
+
1. `src/ecopoesis/` - Main package with:
|
|
125
|
+
- `Simulation` class for deterministic simulation (with terrain and climate layers)
|
|
126
|
+
- `SimulationState` dataclass with optional terrain/climate state fields
|
|
127
|
+
- `Terrain` / `Climate` engine classes (seeded PRNG, tick-based evolution)
|
|
128
|
+
- `save_simulation()` / `load_simulation()` file persistence helpers
|
|
129
|
+
- Deterministic random number generation based on seed+tick
|
|
130
|
+
|
|
131
|
+
2. `tests/` - Test suite with:
|
|
132
|
+
- Basic tick functionality tests
|
|
133
|
+
- Terrain and climate determinism (same-seed identity, different-seed divergence)
|
|
134
|
+
- Tick order equivalence (`advance(N)` == `advance(1)` * N times)
|
|
135
|
+
- Bounds/invariant checks after many ticks
|
|
136
|
+
- Serialization round-trip including terrain/climate data
|
|
137
|
+
- Backward compatibility for saves without terrain/climate fields
|
|
138
|
+
|
|
139
|
+
### Determinism Guarantees
|
|
140
|
+
- Same seeds + same tick sequences = identical state at all times (including grids)
|
|
141
|
+
- Different seeds = different initial deterministic behavior
|
|
142
|
+
- State can be saved and loaded without loss of determinism
|
|
143
|
+
- Fixed tick progression ensures consistent updates
|
|
144
|
+
- Terrain elevation values bounded to [0, 255], temperature to [-500, 500], precipitation to [0, 255]
|
|
145
|
+
|
|
146
|
+
## Verification
|
|
147
|
+
|
|
148
|
+
The implementation passes:
|
|
149
|
+
1. Simulation initialization with seed
|
|
150
|
+
2. Tick advancement functionality
|
|
151
|
+
3. Deterministic behavior for same seed + same operations
|
|
152
|
+
4. Different behavior for different seeds
|
|
153
|
+
5. Serialization and deserialization of state
|
|
154
|
+
6. Round-trip save/load operations
|
|
155
|
+
7. File-based save/load persistence and version handling
|
|
156
|
+
8. Terrain elevation bounds [0, 255] after many ticks
|
|
157
|
+
9. Climate temperature [-500, 500] and precipitation [0, 255] invariants
|
|
158
|
+
10. Terrain/climate determinism across independent Simulation instances
|
|
159
|
+
|
|
160
|
+
## Getting Started
|
|
161
|
+
|
|
162
|
+
The canonical 60-second walkthrough lives in
|
|
163
|
+
[`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md). It captures the
|
|
164
|
+
full seed → run → save → render → info → saves → load → replay → info
|
|
165
|
+
loop in a byte-equal transcript, and the companion
|
|
166
|
+
[`scripts/demo.py`](scripts/demo.py) reproduces it on any host
|
|
167
|
+
(`uv run python scripts/demo.py`).
|
|
168
|
+
|
|
169
|
+
If you `pip install -e .` from the repo root, a `ecopoesis` console
|
|
170
|
+
script is exposed (via the `[project.scripts]` entry in
|
|
171
|
+
`pyproject.toml`), so you can run `ecopoesis run --seed demo --ticks 5`
|
|
172
|
+
directly. The CLI also supports the `python -m ecopoesis.cli …` form.
|
|
173
|
+
|
|
174
|
+
The CLI has 9 subcommands: `run`, `save`, `load`, `replay`, `summary`,
|
|
175
|
+
`info`, `saves`, `delete`, `export`, and `render`. Each exits `0` on
|
|
176
|
+
success and prints a deterministic multi-line summary or ASCII grid
|
|
177
|
+
suitable for diffing.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Install in development mode
|
|
181
|
+
uv sync
|
|
182
|
+
|
|
183
|
+
# Run all tests
|
|
184
|
+
uv run python -m unittest discover -s tests
|
|
185
|
+
|
|
186
|
+
# Or run specific suites:
|
|
187
|
+
uv run python -m unittest tests.test_terrain_climate
|
|
188
|
+
uv run python -m unittest tests.test_simulation_tick
|
|
189
|
+
|
|
190
|
+
# Reproduce the canonical walkthrough
|
|
191
|
+
uv run python scripts/demo.py
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Public API
|
|
195
|
+
|
|
196
|
+
The complete public surface is `ecopoesis.__all__`. Anything not listed
|
|
197
|
+
here is internal and may change without notice.
|
|
198
|
+
|
|
199
|
+
<!-- generated from ecopoesis/__init__.py -->
|
|
200
|
+
```
|
|
201
|
+
__version__
|
|
202
|
+
Simulation
|
|
203
|
+
SimulationState
|
|
204
|
+
SUPPORTED_VERSIONS
|
|
205
|
+
Terrain
|
|
206
|
+
TerrainState
|
|
207
|
+
ELEVATION_MIN
|
|
208
|
+
ELEVATION_MAX
|
|
209
|
+
Climate
|
|
210
|
+
ClimateState
|
|
211
|
+
TEMP_MIN
|
|
212
|
+
TEMP_MAX
|
|
213
|
+
PRECIP_MIN
|
|
214
|
+
PRECIP_MAX
|
|
215
|
+
Life
|
|
216
|
+
LifeState
|
|
217
|
+
Species
|
|
218
|
+
is_habitable
|
|
219
|
+
Resources
|
|
220
|
+
ResourceState
|
|
221
|
+
MAX_RESOURCE
|
|
222
|
+
EnvironmentControls
|
|
223
|
+
GeologyEngine
|
|
224
|
+
GeologyState
|
|
225
|
+
Format
|
|
226
|
+
CURRENT_SCHEMA
|
|
227
|
+
save_simulation
|
|
228
|
+
load_simulation
|
|
229
|
+
save_simulation_json
|
|
230
|
+
load_simulation_json
|
|
231
|
+
save_simulation_protobuf
|
|
232
|
+
load_simulation_protobuf
|
|
233
|
+
save_simulation_v1
|
|
234
|
+
load_simulation_v1
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Managing saves
|
|
238
|
+
|
|
239
|
+
Slice 8 adds four save-management subcommands so you can list, inspect,
|
|
240
|
+
delete, and re-export save files from the terminal without writing a
|
|
241
|
+
script. All four respect the same deterministic, traceback-free error
|
|
242
|
+
policy as the rest of the CLI.
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
python -m ecopoesis.cli saves --dir ./out
|
|
246
|
+
python -m ecopoesis.cli info --in save.json
|
|
247
|
+
python -m ecopoesis.cli delete --in save.json --yes
|
|
248
|
+
python -m ecopoesis.cli export --in save.json --format json --out save.min.json
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
* ``saves`` — lists every ``.json`` / ``.sim`` file in ``--dir`` as
|
|
252
|
+
``path | size | tick | seed``, sorted by path. An empty directory
|
|
253
|
+
prints ``(no saves found)`` and exits ``0``.
|
|
254
|
+
* ``info`` — prints the file's metadata header (schema, format, version,
|
|
255
|
+
seed, tick, present layers, and per-layer ``rng_state`` presence)
|
|
256
|
+
without instantiating a ``Simulation``. Useful as a cheap sanity
|
|
257
|
+
check before loading.
|
|
258
|
+
* ``delete`` — refuses to run without ``--yes``; with ``--yes`` it
|
|
259
|
+
removes the file. Files outside the allow-listed extensions
|
|
260
|
+
(``.json`` / ``.sim``) are never touched.
|
|
261
|
+
* ``export`` — re-emits a save in ``--format json`` (minified, no
|
|
262
|
+
whitespace) or ``--format protobuf`` (feature-gated: prints a
|
|
263
|
+
friendly stderr error and exits non-zero when the ``protobuf``
|
|
264
|
+
runtime is missing).
|
|
265
|
+
|
|
266
|
+
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Ecopoesis
|
|
2
|
+
|
|
3
|
+
Project workspace for the modern Ecopoesis remake.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
Slice 12 implemented - canonical "Getting Started" walkthrough (`docs/GETTING_STARTED.md`), end-to-end demo (`scripts/demo.py` + `scripts/demo.sh`), consolidated README Quickstart sections, public API listing. **V1 is complete** — all 5 V1 acceptance criteria met.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install ecopoesis
|
|
12
|
+
ecopoesis run --seed demo --ticks 5
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or for development:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/ecopoesis/ecopoesis.git
|
|
19
|
+
cd ecopoesis
|
|
20
|
+
uv sync
|
|
21
|
+
uv run ecopoesis run --seed demo --ticks 5
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The current version is `0.1.0` (V1 candidate). See `CHANGELOG.md` for what's in this release and `RELEASE.md` for the maintainer release procedure.
|
|
25
|
+
|
|
26
|
+
## Visualising the world
|
|
27
|
+
|
|
28
|
+
Run the renderer against any save file to see the simulation as a grid of ASCII glyphs. Example:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python -m ecopoesis.cli run --seed demo --ticks 50 --out save.json
|
|
32
|
+
python -m ecopoesis.cli render --in save.json --layer terrain
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Terrain glyphs: ` ` ocean, `.` coast, `,` lowland, `:` plain, `o` hill, `^` mountain, `#` peak. Life glyphs: `M` microbe, `P` plant, `.` none. Resources glyphs: `+`/`-`/`0`-`9` for minerals, `0`-`9` for water and energy. Use `--layer all` to stack all four layers (terrain → climate → life → resources). Optional `--out PATH` mirrors the same bytes to a file; `--no-color` is accepted as a no-op forward-compat flag; `--scale` is currently locked to `1`.
|
|
36
|
+
|
|
37
|
+
## Slice 7 Refactor: Unified RNG-state pattern
|
|
38
|
+
|
|
39
|
+
All four RNG-driven layers (Climate, Life, Resources, Geology) now share the same
|
|
40
|
+
save/load pattern: each engine's `get_state()` captures its live `rng_state`
|
|
41
|
+
(via `random.Random.getstate()`) into the dataclass, and `restore_state()`
|
|
42
|
+
restores via `rng.setstate(...)`. This eliminates the count-based replay
|
|
43
|
+
pattern that caused the pre-Slice-7 climate RNG bug (5 determinism tests were
|
|
44
|
+
skipped; they now pass with the refactor in place). Legacy saves without
|
|
45
|
+
`rng_state` still load — the engines just start fresh from their constructed
|
|
46
|
+
RNG.
|
|
47
|
+
|
|
48
|
+
## What's Implemented (Slice 1-4)
|
|
49
|
+
|
|
50
|
+
Slices 1-2 (core simulation shell):
|
|
51
|
+
- **Project Scaffold**: Complete Python package structure
|
|
52
|
+
- **Deterministic Simulation**: `Simulation` class initialized from a seed
|
|
53
|
+
- **Fixed Tick Progression**: Simulation advances ticks deterministically
|
|
54
|
+
- **Serializable State**: Simulation state can be saved and loaded as JSON
|
|
55
|
+
- **File-based Persistence**: Save/load helpers write and read versioned save files
|
|
56
|
+
- **Determinism Verification**: Tests showing same seed + same operations produce identical results
|
|
57
|
+
- **Different Seed Behavior**: Different seeds produce different initial states
|
|
58
|
+
|
|
59
|
+
Slice 3 (terrain + climate):
|
|
60
|
+
- **Terrain Layer**: Heightmap grid (default 8x8, elevation values 0-255) with deterministic generation from seed and per-tick diffusion-based erosion
|
|
61
|
+
- **Climate Layer**: Temperature and precipitation grids (same dimensions), with per-tick thermal diffusion plus bounded random perturbation
|
|
62
|
+
- **Fixed Update Order**: Core LCG → Terrain erosion → Climate diffusion → Snapshot
|
|
63
|
+
- **Backward-Compatible Serialization**: Existing saves without terrain/climate data still load; new saves include the grid data
|
|
64
|
+
|
|
65
|
+
Slice 4 (life + population dynamics):
|
|
66
|
+
- **Life Layer**: `Species` (MICROBE, PLANT) population grids on the same simulation grid, with deterministic placement via `Life.place()` and per-tick logistic population update (`pop' = pop + r*pop*(1 - pop/K)` integer math)
|
|
67
|
+
- **Habitat Predicate**: a single `is_habitable(elevation, temperature, precipitation)` gate; populations only placed and only positive on habitable cells; non-habitable cells stay exactly 0
|
|
68
|
+
- **Carrying Capacity**: per-cell K derived from local terrain + climate; clamp to `>= 0` (no negative populations, verified across 500 adversarial ticks)
|
|
69
|
+
- **Wired into Simulation**: life step runs after climate in the fixed tick order (core LCG → terrain erosion → climate diffusion → life step → snapshot)
|
|
70
|
+
- **Backward-Compatible Saves**: Slice-3 saves without `life_state` still load; new saves include `populations: dict[str, list[int]]` + `life_ticks`
|
|
71
|
+
|
|
72
|
+
Slice 5 (CLI front-end):
|
|
73
|
+
- **Deterministic CLI**: `python -m ecopoesis.cli {run,save,load,summary}` with subcommands for seeding, ticking, optional `--seed-life SPECIES` placement, save/load round-trip, and an `argparse --help` page
|
|
74
|
+
- **Stable ASCII summary**: `format_summary()` emits one field per line (`seed=`, `tick=`, `terrain_dims=`, `climate_dims=`, `populations={MICROBE=N,PLANT=N}`, `life_ticks=`) so output is byte-stable across runs and easy to assert on
|
|
75
|
+
- **Friendly errors**: `main()` swallows exceptions and prints a single-line error to stderr with no traceback; missing files / bad seeds / negative ticks all return exit code 2
|
|
76
|
+
- **Console script**: `pyproject.toml` exposes `ecopoesis = "ecopoesis.cli:main"` so `pip install -e .` installs an entry point; `ecopoesis/__init__.py` re-exports `main` for library callers
|
|
77
|
+
- **Persistence gap closed**: `SimulationState.to_dict()` now emits the `"life"` envelope whenever `life_ticks > 0` (not only when populations exist), so save → load round-trips preserve `life_ticks` correctly. Backward-compat is preserved — old Slice-2/3 saves without `"life"` still load with `LifeState()` defaults.
|
|
78
|
+
|
|
79
|
+
Slice 6 (resource layer + environment controls):
|
|
80
|
+
- **Resources**: `ResourceState` with per-cell int arrays for minerals, water, energy, plus `resource_ticks`. `Resources` engine seeded deterministically at `seed_int + 3`, advances 3*cells draws per tick (one per array, row-major), all values clamped to `[0, MAX_RESOURCE=1000]`. RNG budget is replayable so post-load ticks remain deterministic.
|
|
81
|
+
- **Environment controls**: `EnvironmentControls(temperature_offset: int, rainfall_offset: int)` with `apply(climate_state)` that clamps in place to `[-500, 500]` and `[0, 255]`. Pure RNG-free transformation — does not perturb any layer's RNG budget.
|
|
82
|
+
- **Wired into Simulation**: resources step runs after climate in the fixed tick order (core LCG → terrain erosion → climate diffusion → life → resources → snapshot). Controls are applied at `Simulation.__init__` startup; `from_dict` RNG-skip block now imports `BOUNDS` from `.resources` so save → load → advance remains byte-identical to no-save advance.
|
|
83
|
+
- **CLI flags**: `python -m ecopoesis.cli run --temperature-offset 10 --rainfall-offset -5 --ticks 50`; `format_summary()` now emits `minerals=X water=Y energy=Z` and the controls echo.
|
|
84
|
+
- **Backward-compatible saves**: Slice-2/3/4 saves without `"resources"` or `"controls"` keys still load with defaults; new saves round-trip both layers faithfully.
|
|
85
|
+
|
|
86
|
+
Slice 7 (geology: tectonic shifts + volcanic eruptions):
|
|
87
|
+
- **Geology layer**: `GeologyState` tracks `tectonic_events_count`, `eruption_events_count`, and the live `rng_state`. `GeologyEngine` is seeded deterministically at `seed_int + 4`; per tick it draws two unconditional Bernoulli values against `tectonic_freq`/`eruption_freq` plus conditional draws for any events that fire (cell coords + signed delta / mineral deposit).
|
|
88
|
+
- **Tectonic shifts**: when triggered, picks a random cell and applies a small signed int delta, clamped to terrain bounds `[0, 255]`.
|
|
89
|
+
- **Volcanic eruptions**: when triggered, deposits `+10` minerals at a random cell, clamped to `[0, MAX_RESOURCE]`.
|
|
90
|
+
- **RNG state in snapshot**: `SimulationState` carries `geology_state.rng_state` (the engine's `random.Random` `getstate()` tuple), so `from_dict` restores via `setstate` rather than replaying by count — this avoids the count-based climate-RNG bug pattern entirely.
|
|
91
|
+
- **CLI flags**: `python -m ecopoesis.cli run --tectonic-frequency 0.1 --eruption-frequency 0.05 --ticks 500`; `format_summary()` appends `tectonic_events=N eruption_events=M`. Frequencies are validated to `[0.0, 1.0]` with friendly stderr errors and exit code 2.
|
|
92
|
+
- **Backward-compatible saves**: pre-Slice-7 saves without `"geology"` load with `GeologyState()` defaults; new saves round-trip the live RNG state so save → load → advance is byte-identical to no-save advance.
|
|
93
|
+
|
|
94
|
+
## Key Features
|
|
95
|
+
|
|
96
|
+
### Core Components
|
|
97
|
+
1. `src/ecopoesis/` - Main package with:
|
|
98
|
+
- `Simulation` class for deterministic simulation (with terrain and climate layers)
|
|
99
|
+
- `SimulationState` dataclass with optional terrain/climate state fields
|
|
100
|
+
- `Terrain` / `Climate` engine classes (seeded PRNG, tick-based evolution)
|
|
101
|
+
- `save_simulation()` / `load_simulation()` file persistence helpers
|
|
102
|
+
- Deterministic random number generation based on seed+tick
|
|
103
|
+
|
|
104
|
+
2. `tests/` - Test suite with:
|
|
105
|
+
- Basic tick functionality tests
|
|
106
|
+
- Terrain and climate determinism (same-seed identity, different-seed divergence)
|
|
107
|
+
- Tick order equivalence (`advance(N)` == `advance(1)` * N times)
|
|
108
|
+
- Bounds/invariant checks after many ticks
|
|
109
|
+
- Serialization round-trip including terrain/climate data
|
|
110
|
+
- Backward compatibility for saves without terrain/climate fields
|
|
111
|
+
|
|
112
|
+
### Determinism Guarantees
|
|
113
|
+
- Same seeds + same tick sequences = identical state at all times (including grids)
|
|
114
|
+
- Different seeds = different initial deterministic behavior
|
|
115
|
+
- State can be saved and loaded without loss of determinism
|
|
116
|
+
- Fixed tick progression ensures consistent updates
|
|
117
|
+
- Terrain elevation values bounded to [0, 255], temperature to [-500, 500], precipitation to [0, 255]
|
|
118
|
+
|
|
119
|
+
## Verification
|
|
120
|
+
|
|
121
|
+
The implementation passes:
|
|
122
|
+
1. Simulation initialization with seed
|
|
123
|
+
2. Tick advancement functionality
|
|
124
|
+
3. Deterministic behavior for same seed + same operations
|
|
125
|
+
4. Different behavior for different seeds
|
|
126
|
+
5. Serialization and deserialization of state
|
|
127
|
+
6. Round-trip save/load operations
|
|
128
|
+
7. File-based save/load persistence and version handling
|
|
129
|
+
8. Terrain elevation bounds [0, 255] after many ticks
|
|
130
|
+
9. Climate temperature [-500, 500] and precipitation [0, 255] invariants
|
|
131
|
+
10. Terrain/climate determinism across independent Simulation instances
|
|
132
|
+
|
|
133
|
+
## Getting Started
|
|
134
|
+
|
|
135
|
+
The canonical 60-second walkthrough lives in
|
|
136
|
+
[`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md). It captures the
|
|
137
|
+
full seed → run → save → render → info → saves → load → replay → info
|
|
138
|
+
loop in a byte-equal transcript, and the companion
|
|
139
|
+
[`scripts/demo.py`](scripts/demo.py) reproduces it on any host
|
|
140
|
+
(`uv run python scripts/demo.py`).
|
|
141
|
+
|
|
142
|
+
If you `pip install -e .` from the repo root, a `ecopoesis` console
|
|
143
|
+
script is exposed (via the `[project.scripts]` entry in
|
|
144
|
+
`pyproject.toml`), so you can run `ecopoesis run --seed demo --ticks 5`
|
|
145
|
+
directly. The CLI also supports the `python -m ecopoesis.cli …` form.
|
|
146
|
+
|
|
147
|
+
The CLI has 9 subcommands: `run`, `save`, `load`, `replay`, `summary`,
|
|
148
|
+
`info`, `saves`, `delete`, `export`, and `render`. Each exits `0` on
|
|
149
|
+
success and prints a deterministic multi-line summary or ASCII grid
|
|
150
|
+
suitable for diffing.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Install in development mode
|
|
154
|
+
uv sync
|
|
155
|
+
|
|
156
|
+
# Run all tests
|
|
157
|
+
uv run python -m unittest discover -s tests
|
|
158
|
+
|
|
159
|
+
# Or run specific suites:
|
|
160
|
+
uv run python -m unittest tests.test_terrain_climate
|
|
161
|
+
uv run python -m unittest tests.test_simulation_tick
|
|
162
|
+
|
|
163
|
+
# Reproduce the canonical walkthrough
|
|
164
|
+
uv run python scripts/demo.py
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Public API
|
|
168
|
+
|
|
169
|
+
The complete public surface is `ecopoesis.__all__`. Anything not listed
|
|
170
|
+
here is internal and may change without notice.
|
|
171
|
+
|
|
172
|
+
<!-- generated from ecopoesis/__init__.py -->
|
|
173
|
+
```
|
|
174
|
+
__version__
|
|
175
|
+
Simulation
|
|
176
|
+
SimulationState
|
|
177
|
+
SUPPORTED_VERSIONS
|
|
178
|
+
Terrain
|
|
179
|
+
TerrainState
|
|
180
|
+
ELEVATION_MIN
|
|
181
|
+
ELEVATION_MAX
|
|
182
|
+
Climate
|
|
183
|
+
ClimateState
|
|
184
|
+
TEMP_MIN
|
|
185
|
+
TEMP_MAX
|
|
186
|
+
PRECIP_MIN
|
|
187
|
+
PRECIP_MAX
|
|
188
|
+
Life
|
|
189
|
+
LifeState
|
|
190
|
+
Species
|
|
191
|
+
is_habitable
|
|
192
|
+
Resources
|
|
193
|
+
ResourceState
|
|
194
|
+
MAX_RESOURCE
|
|
195
|
+
EnvironmentControls
|
|
196
|
+
GeologyEngine
|
|
197
|
+
GeologyState
|
|
198
|
+
Format
|
|
199
|
+
CURRENT_SCHEMA
|
|
200
|
+
save_simulation
|
|
201
|
+
load_simulation
|
|
202
|
+
save_simulation_json
|
|
203
|
+
load_simulation_json
|
|
204
|
+
save_simulation_protobuf
|
|
205
|
+
load_simulation_protobuf
|
|
206
|
+
save_simulation_v1
|
|
207
|
+
load_simulation_v1
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Managing saves
|
|
211
|
+
|
|
212
|
+
Slice 8 adds four save-management subcommands so you can list, inspect,
|
|
213
|
+
delete, and re-export save files from the terminal without writing a
|
|
214
|
+
script. All four respect the same deterministic, traceback-free error
|
|
215
|
+
policy as the rest of the CLI.
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
python -m ecopoesis.cli saves --dir ./out
|
|
219
|
+
python -m ecopoesis.cli info --in save.json
|
|
220
|
+
python -m ecopoesis.cli delete --in save.json --yes
|
|
221
|
+
python -m ecopoesis.cli export --in save.json --format json --out save.min.json
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
* ``saves`` — lists every ``.json`` / ``.sim`` file in ``--dir`` as
|
|
225
|
+
``path | size | tick | seed``, sorted by path. An empty directory
|
|
226
|
+
prints ``(no saves found)`` and exits ``0``.
|
|
227
|
+
* ``info`` — prints the file's metadata header (schema, format, version,
|
|
228
|
+
seed, tick, present layers, and per-layer ``rng_state`` presence)
|
|
229
|
+
without instantiating a ``Simulation``. Useful as a cheap sanity
|
|
230
|
+
check before loading.
|
|
231
|
+
* ``delete`` — refuses to run without ``--yes``; with ``--yes`` it
|
|
232
|
+
removes the file. Files outside the allow-listed extensions
|
|
233
|
+
(``.json`` / ``.sim``) are never touched.
|
|
234
|
+
* ``export`` — re-emits a save in ``--format json`` (minified, no
|
|
235
|
+
whitespace) or ``--format protobuf`` (feature-gated: prints a
|
|
236
|
+
friendly stderr error and exits non-zero when the ``protobuf``
|
|
237
|
+
runtime is missing).
|
|
238
|
+
|
|
239
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ecopoesis"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Ecopoesis: The Planetary Simulator — a deterministic terminal-driven planet simulator."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Ecopoesis Engineering", email = "engineering@ecopoesis.dev" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"ecopoesis",
|
|
17
|
+
"simulation",
|
|
18
|
+
"terraforming",
|
|
19
|
+
"procedural",
|
|
20
|
+
"deterministic",
|
|
21
|
+
"game",
|
|
22
|
+
"planetary",
|
|
23
|
+
"earth",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Intended Audience :: Science/Research",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
35
|
+
"Topic :: Games/Entertainment :: Simulation",
|
|
36
|
+
"Topic :: Scientific/Engineering :: Hydrology",
|
|
37
|
+
"Typing :: Typed",
|
|
38
|
+
]
|
|
39
|
+
# Pinned runtime dependencies.
|
|
40
|
+
# `protobuf` is required for save_simulation_protobuf / load_simulation_protobuf
|
|
41
|
+
# (lazy-imported; >=6.33.5 needed for the generated simulation_pb2 module —
|
|
42
|
+
# the runtime MUST match or exceed the gencode version per protobuf's
|
|
43
|
+
# cross-version runtime guarantee).
|
|
44
|
+
# The generated simulation_pb2.py was built with gencode 6.33.5. Pin to
|
|
45
|
+
# the 6.x line to keep gencode/runtime in lock-step. If the generated
|
|
46
|
+
# module is regenerated, update the lower bound accordingly.
|
|
47
|
+
#
|
|
48
|
+
# The protobuf path serialises the FULL SimulationState.to_dict() payload
|
|
49
|
+
# (including per-layer rng_state snapshots) inside a ``state_json``
|
|
50
|
+
# ``bytes`` field, so protobuf round-trip is byte-identical to a no-save
|
|
51
|
+
# baseline. The legacy terrain_* / climate_* fields are kept populated
|
|
52
|
+
# for back-compat with saves produced before the state_json field was
|
|
53
|
+
# added; old saves load via legacy field reconstruction (without
|
|
54
|
+
# rng_state, no replay guarantee).
|
|
55
|
+
dependencies = [
|
|
56
|
+
"protobuf>=6.33.5,<7",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[project.urls]
|
|
60
|
+
Homepage = "https://github.com/ecopoesis/ecopoesis"
|
|
61
|
+
Repository = "https://github.com/ecopoesis/ecopoesis.git"
|
|
62
|
+
Issues = "https://github.com/ecopoesis/ecopoesis/issues"
|
|
63
|
+
"Bug Tracker" = "https://github.com/ecopoesis/ecopoesis/issues"
|
|
64
|
+
|
|
65
|
+
[project.scripts]
|
|
66
|
+
ecopoesis = "ecopoesis.cli:main"
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.package-dir]
|
|
69
|
+
"" = "src"
|
|
70
|
+
|
|
71
|
+
[tool.setuptools.packages.find]
|
|
72
|
+
where = ["src"]
|
|
73
|
+
|
|
74
|
+
[dependency-groups]
|
|
75
|
+
dev = [
|
|
76
|
+
"grpcio-tools>=1.81.1",
|
|
77
|
+
"hypothesis>=6.0",
|
|
78
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""SimEarth package exports."""
|
|
2
|
+
|
|
3
|
+
from .__version__ import __version__ # noqa: F401
|
|
4
|
+
from .cli import main # noqa: F401
|
|
5
|
+
from .controls import EnvironmentControls
|
|
6
|
+
from .geology import GeologyEngine, GeologyState
|
|
7
|
+
from .life import Life, LifeState, Species, is_habitable
|
|
8
|
+
from .resources import MAX_RESOURCE, ResourceState, Resources
|
|
9
|
+
from .simulation import Simulation
|
|
10
|
+
from .state import SUPPORTED_VERSIONS, SimulationState
|
|
11
|
+
from .terrain import Terrain, TerrainState, ELEVATION_MIN, ELEVATION_MAX
|
|
12
|
+
from .climate import Climate, ClimateState, TEMP_MIN, TEMP_MAX, PRECIP_MIN, PRECIP_MAX
|
|
13
|
+
from .persistence import (
|
|
14
|
+
Format,
|
|
15
|
+
CURRENT_SCHEMA,
|
|
16
|
+
load_simulation,
|
|
17
|
+
load_simulation_json,
|
|
18
|
+
load_simulation_protobuf,
|
|
19
|
+
load_simulation_v1,
|
|
20
|
+
save_simulation,
|
|
21
|
+
save_simulation_json,
|
|
22
|
+
save_simulation_protobuf,
|
|
23
|
+
save_simulation_v1,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Package metadata (Slice 11)
|
|
28
|
+
"__version__",
|
|
29
|
+
# Simulation core
|
|
30
|
+
"Simulation",
|
|
31
|
+
"SimulationState",
|
|
32
|
+
"SUPPORTED_VERSIONS",
|
|
33
|
+
# Terrain layer (Slice 3)
|
|
34
|
+
"Terrain",
|
|
35
|
+
"TerrainState",
|
|
36
|
+
"ELEVATION_MIN",
|
|
37
|
+
"ELEVATION_MAX",
|
|
38
|
+
# Climate layer (Slice 3)
|
|
39
|
+
"Climate",
|
|
40
|
+
"ClimateState",
|
|
41
|
+
"TEMP_MIN",
|
|
42
|
+
"TEMP_MAX",
|
|
43
|
+
"PRECIP_MIN",
|
|
44
|
+
"PRECIP_MAX",
|
|
45
|
+
# Life layer (Slice 4)
|
|
46
|
+
"Life",
|
|
47
|
+
"LifeState",
|
|
48
|
+
"Species",
|
|
49
|
+
"is_habitable",
|
|
50
|
+
# Resources layer (Slice 6)
|
|
51
|
+
"Resources",
|
|
52
|
+
"ResourceState",
|
|
53
|
+
"MAX_RESOURCE",
|
|
54
|
+
# Environment controls (Slice 6)
|
|
55
|
+
"EnvironmentControls",
|
|
56
|
+
# Geology layer (Slice 7)
|
|
57
|
+
"GeologyEngine",
|
|
58
|
+
"GeologyState",
|
|
59
|
+
# Persistence
|
|
60
|
+
"Format",
|
|
61
|
+
"CURRENT_SCHEMA",
|
|
62
|
+
"save_simulation",
|
|
63
|
+
"load_simulation",
|
|
64
|
+
"save_simulation_json",
|
|
65
|
+
"load_simulation_json",
|
|
66
|
+
"save_simulation_protobuf",
|
|
67
|
+
"load_simulation_protobuf",
|
|
68
|
+
"save_simulation_v1",
|
|
69
|
+
"load_simulation_v1",
|
|
70
|
+
]
|
|
71
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Single-source-of-truth version for the ecopoesis package.
|
|
2
|
+
|
|
3
|
+
This file is intentionally minimal so it can be imported from any context
|
|
4
|
+
(including build scripts and runtime) without pulling in heavier dependencies
|
|
5
|
+
like ``ecopoesis.simulation_pb2``. Both ``pyproject.toml`` and the public
|
|
6
|
+
``ecopoesis.__version__`` re-export should stay in lock-step with this string.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|