thomasm6m6 commited on
Commit
77354d0
·
verified ·
1 Parent(s): 96c272a

Upload folder using huggingface_hub

Browse files
AGENTS.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ This is a hackathon project--see the brief at hackathon.md. Always read the file before responding.
2
+ Please keep your answers concise.
3
+
4
+ We have an H100. Access it by running this command: `northflank ssh service --projectId hackathon --serviceId civ --proxyOnly` (may already be running)
5
+ then `ssh root@127.0.0.1 -p 35731` (or whatever the `northflank ssh` command prints out as the command to run)
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ARG BASE_IMAGE=openenv-base:latest
2
+ FROM ${BASE_IMAGE} AS builder
3
+
4
+ WORKDIR /app/env
5
+ COPY . /app/env
6
+
7
+ RUN --mount=type=cache,target=/root/.cache/uv \
8
+ if [ -f uv.lock ]; then \
9
+ uv sync --frozen --no-editable; \
10
+ else \
11
+ uv sync --no-editable; \
12
+ fi
13
+
14
+ FROM ${BASE_IMAGE}
15
+
16
+ WORKDIR /app/env
17
+ COPY --from=builder /app/env /app/env
18
+ COPY --from=builder /app/env/.venv /app/env/.venv
19
+
20
+ ENV PATH="/app/env/.venv/bin:$PATH"
21
+ ENV PYTHONPATH="/app/env:$PYTHONPATH"
22
+
23
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
24
+ CMD curl -f http://localhost:8000/health || exit 1
25
+
26
+ ENV ENABLE_WEB_INTERFACE=true
27
+ CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000 --ws-ping-interval 300 --ws-ping-timeout 300"]
README.md CHANGED
@@ -1,10 +1,117 @@
1
  ---
2
- title: Freeciv Env
3
- emoji: 📈
4
- colorFrom: pink
5
- colorTo: purple
6
  sdk: docker
7
  pinned: false
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Freeciv Environment Server
3
+ emoji: 🎮
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
+ app_port: 8000
9
+ base_path: /web
10
+ tags:
11
+ - openenv
12
  ---
13
 
14
+ # freeciv-env
15
+
16
+ OpenEnv environment for Freeciv, built on top of `freeciv-bot`.
17
+
18
+ ## Current scope
19
+
20
+ This environment exposes a small, trainable action surface:
21
+
22
+ - `end_turn`
23
+ - `move_unit(unit_id, direction)`
24
+ - `build_city(unit_id)`
25
+ - `set_city_production(city_id, target)`
26
+ - `set_research(tech_name)`
27
+
28
+ Observations are text-first and include compact structured summaries of:
29
+
30
+ - current turn
31
+ - score
32
+ - known and visible map tiles
33
+ - units
34
+ - cities
35
+ - legal actions
36
+
37
+ ## Local development
38
+
39
+ Install dependencies:
40
+
41
+ ```bash
42
+ uv sync --extra dev
43
+ ```
44
+
45
+ Run tests:
46
+
47
+ ```bash
48
+ uv run pytest
49
+ ```
50
+
51
+ Run the server:
52
+
53
+ ```bash
54
+ uv run uvicorn freeciv_env.server.app:app --host 0.0.0.0 --port 8000
55
+ ```
56
+
57
+ Run the fast GRPO loop:
58
+
59
+ ```bash
60
+ uv sync --extra dev --extra train
61
+ uv run python scripts/train_grpo_fast.py --env-url http://127.0.0.1 --max-steps 50
62
+ ```
63
+
64
+ ## Hackathon / Unsloth notes
65
+
66
+ For the hackathon Colab submission path on H100s, Unsloth recommended the BF16 OpenEnv gpt-oss 20B notebook:
67
+
68
+ - <https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/OpenEnv_gpt_oss_(20B)_Reinforcement_Learning_2048_Game_BF16.ipynb>
69
+
70
+ If you adapt that notebook for this environment, reduce `max_steps` to `300` for a faster run.
71
+
72
+ Useful notebook indexes:
73
+
74
+ - RL notebooks: <https://unsloth.ai/docs/get-started/unsloth-notebooks#grpo-reasoning-rl>
75
+ - all notebooks: <https://unsloth.ai/docs/get-started/unsloth-notebooks>
76
+ - notebook repo: <https://github.com/unslothai/notebooks/tree/main/nb>
77
+
78
+ If GRPO is too slow, start from a smaller notebook with `fast_inference = True` and add the Freeciv/OpenEnv calls:
79
+
80
+ - <https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen3_(4B)-GRPO.ipynb>
81
+ - <https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Advanced_Llama3_2_(3B)_GRPO_LoRA.ipynb>
82
+
83
+ If vLLM GRPO fails, Unsloth suggested a clean virtualenv install:
84
+
85
+ ```bash
86
+ python -m venv unsloth_env
87
+ source unsloth_env/bin/activate
88
+ pip install --upgrade pip && pip install uv
89
+ uv pip install unsloth vllm --torch-backend=auto
90
+ ```
91
+
92
+ If Unsloth is already installed, update it for the latest GRPO fixes:
93
+
94
+ ```bash
95
+ pip install --upgrade --no-cache-dir --no-deps unsloth unsloth_zoo
96
+ ```
97
+
98
+ ## Live runtime requirements
99
+
100
+ The default server app uses `freeciv-bot` against a local Freeciv Web runtime.
101
+
102
+ Environment variables:
103
+
104
+ - `FREECIV_SERVER_URL` (default: `http://localhost`)
105
+ - `FREECIV_USERNAME` (default: `openenvbot`)
106
+ - `FREECIV_CLIENT_PORT` (default: `6000`)
107
+ - `FREECIV_TURN_TIMEOUT_S` (default: `60`)
108
+
109
+ The included automated tests use a fake session backend, so they do not require a live Freeciv server.
110
+
111
+ The GRPO training script uses:
112
+
113
+ - `Qwen/Qwen3.5-0.8B`
114
+ - Unsloth bf16 LoRA loading
115
+ - TRL `GRPOTrainer`
116
+ - integer-only action selection to minimize generated tokens
117
+ - offline GRPO over env-sampled states for maximum throughput
__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from freeciv_env import *
client.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from freeciv_env.client import *
freeciv_env.egg-info/PKG-INFO ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: freeciv-env
3
+ Version: 0.1.0
4
+ Summary: OpenEnv environment for Freeciv via freeciv-bot
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: openenv-core[core]==0.2.1
8
+ Requires-Dist: freecivbot @ git+https://github.com/chris1869/freeciv-bot.git
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest>=8.4.1; extra == "dev"
11
+ Requires-Dist: requests>=2.32.5; extra == "dev"
12
+ Requires-Dist: uvicorn>=0.35.0; extra == "dev"
13
+ Provides-Extra: train
14
+ Requires-Dist: accelerate>=1.10.0; extra == "train"
15
+ Requires-Dist: bitsandbytes>=0.47.0; extra == "train"
16
+ Requires-Dist: datasets>=4.0.0; extra == "train"
17
+ Requires-Dist: trl>=0.24.0; extra == "train"
18
+ Requires-Dist: unsloth>=2026.3.4; extra == "train"
19
+
20
+ # freeciv-env
21
+
22
+ OpenEnv environment for Freeciv, built on top of `freeciv-bot`.
23
+
24
+ ## Current scope
25
+
26
+ This environment exposes a small, trainable action surface:
27
+
28
+ - `end_turn`
29
+ - `move_unit(unit_id, direction)`
30
+ - `build_city(unit_id)`
31
+ - `set_city_production(city_id, target)`
32
+ - `set_research(tech_name)`
33
+
34
+ Observations are text-first and include compact structured summaries of:
35
+
36
+ - current turn
37
+ - score
38
+ - known and visible map tiles
39
+ - units
40
+ - cities
41
+ - legal actions
42
+
43
+ ## Local development
44
+
45
+ Install dependencies:
46
+
47
+ ```bash
48
+ uv sync --extra dev
49
+ ```
50
+
51
+ Run tests:
52
+
53
+ ```bash
54
+ uv run pytest
55
+ ```
56
+
57
+ Run the server:
58
+
59
+ ```bash
60
+ uv run uvicorn freeciv_env.server.app:app --host 0.0.0.0 --port 8000
61
+ ```
62
+
63
+ Run the fast GRPO loop:
64
+
65
+ ```bash
66
+ uv sync --extra dev --extra train
67
+ uv run python scripts/train_grpo_fast.py --env-url http://127.0.0.1 --max-steps 50
68
+ ```
69
+
70
+ ## Hackathon / Unsloth notes
71
+
72
+ For the hackathon Colab submission path on H100s, Unsloth recommended the BF16 OpenEnv gpt-oss 20B notebook:
73
+
74
+ - <https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/OpenEnv_gpt_oss_(20B)_Reinforcement_Learning_2048_Game_BF16.ipynb>
75
+
76
+ If you adapt that notebook for this environment, reduce `max_steps` to `300` for a faster run.
77
+
78
+ Useful notebook indexes:
79
+
80
+ - RL notebooks: <https://unsloth.ai/docs/get-started/unsloth-notebooks#grpo-reasoning-rl>
81
+ - all notebooks: <https://unsloth.ai/docs/get-started/unsloth-notebooks>
82
+ - notebook repo: <https://github.com/unslothai/notebooks/tree/main/nb>
83
+
84
+ If GRPO is too slow, start from a smaller notebook with `fast_inference = True` and add the Freeciv/OpenEnv calls:
85
+
86
+ - <https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen3_(4B)-GRPO.ipynb>
87
+ - <https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Advanced_Llama3_2_(3B)_GRPO_LoRA.ipynb>
88
+
89
+ If vLLM GRPO fails, Unsloth suggested a clean virtualenv install:
90
+
91
+ ```bash
92
+ python -m venv unsloth_env
93
+ source unsloth_env/bin/activate
94
+ pip install --upgrade pip && pip install uv
95
+ uv pip install unsloth vllm --torch-backend=auto
96
+ ```
97
+
98
+ If Unsloth is already installed, update it for the latest GRPO fixes:
99
+
100
+ ```bash
101
+ pip install --upgrade --no-cache-dir --no-deps unsloth unsloth_zoo
102
+ ```
103
+
104
+ ## Live runtime requirements
105
+
106
+ The default server app uses `freeciv-bot` against a local Freeciv Web runtime.
107
+
108
+ Environment variables:
109
+
110
+ - `FREECIV_SERVER_URL` (default: `http://localhost`)
111
+ - `FREECIV_USERNAME` (default: `openenvbot`)
112
+ - `FREECIV_CLIENT_PORT` (default: `6000`)
113
+ - `FREECIV_TURN_TIMEOUT_S` (default: `60`)
114
+
115
+ The included automated tests use a fake session backend, so they do not require a live Freeciv server.
116
+
117
+ The GRPO training script uses:
118
+
119
+ - `Qwen/Qwen3.5-0.8B`
120
+ - Unsloth bf16 LoRA loading
121
+ - TRL `GRPOTrainer`
122
+ - integer-only action selection to minimize generated tokens
123
+ - offline GRPO over env-sampled states for maximum throughput
freeciv_env.egg-info/SOURCES.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ README.md
2
+ pyproject.toml
3
+ freeciv_env/__init__.py
4
+ freeciv_env/adapter.py
5
+ freeciv_env/client.py
6
+ freeciv_env/grpo.py
7
+ freeciv_env/models.py
8
+ freeciv_env/runtime.py
9
+ freeciv_env.egg-info/PKG-INFO
10
+ freeciv_env.egg-info/SOURCES.txt
11
+ freeciv_env.egg-info/dependency_links.txt
12
+ freeciv_env.egg-info/entry_points.txt
13
+ freeciv_env.egg-info/requires.txt
14
+ freeciv_env.egg-info/top_level.txt
15
+ freeciv_env/server/__init__.py
16
+ freeciv_env/server/app.py
17
+ freeciv_env/server/freeciv_environment.py
18
+ server/__init__.py
19
+ server/app.py
20
+ tests/test_adapter.py
21
+ tests/test_environment.py
22
+ tests/test_grpo_utils.py
23
+ tests/test_server_roundtrip.py
freeciv_env.egg-info/dependency_links.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
freeciv_env.egg-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [console_scripts]
2
+ server = server.app:main
freeciv_env.egg-info/requires.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ openenv-core[core]==0.2.1
2
+ freecivbot @ git+https://github.com/chris1869/freeciv-bot.git
3
+
4
+ [dev]
5
+ pytest>=8.4.1
6
+ requests>=2.32.5
7
+ uvicorn>=0.35.0
8
+
9
+ [train]
10
+ accelerate>=1.10.0
11
+ bitsandbytes>=0.47.0
12
+ datasets>=4.0.0
13
+ trl>=0.24.0
14
+ unsloth>=2026.3.4
freeciv_env.egg-info/top_level.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ freeciv_env
2
+ server
freeciv_env/__init__.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from freeciv_env.client import FreecivEnv
2
+ from freeciv_env.models import FreecivAction, FreecivObservation, FreecivState, LegalAction
3
+
4
+ __all__ = [
5
+ "FreecivAction",
6
+ "FreecivEnv",
7
+ "FreecivObservation",
8
+ "FreecivState",
9
+ "LegalAction",
10
+ ]
freeciv_env/adapter.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from freeciv_env.models import CitySummary, FreecivAction, FreecivObservation, LegalAction, UnitSummary
7
+
8
+
9
+ ActionLookupKey = tuple[str, int | None, int | None, str | None]
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class ActionRef:
14
+ controller: str
15
+ actor_id: int | str
16
+ raw_action_key: str
17
+
18
+
19
+ @dataclass
20
+ class RawSnapshot:
21
+ turn: int
22
+ state: dict[str, Any]
23
+ actions: dict[str, Any]
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class SnapshotMetrics:
28
+ score: float
29
+ known_tiles: int
30
+ visible_tiles: int
31
+ city_count: int
32
+ unit_count: int
33
+ techs_researched: int
34
+
35
+
36
+ @dataclass
37
+ class PreparedObservation:
38
+ observation: FreecivObservation
39
+ metrics: SnapshotMetrics
40
+ action_refs: dict[ActionLookupKey, ActionRef]
41
+
42
+
43
+ def _map_status_rows(raw_state: dict[str, Any]) -> list[list[int | float]]:
44
+ raw_map = raw_state.get("map", {})
45
+ status = raw_map.get("status", [])
46
+ return status if isinstance(status, list) else []
47
+
48
+
49
+ def count_known_tiles(raw_state: dict[str, Any]) -> int:
50
+ return sum(1 for row in _map_status_rows(raw_state) for value in row if value and value > 0)
51
+
52
+
53
+ def count_visible_tiles(raw_state: dict[str, Any]) -> int:
54
+ return sum(1 for row in _map_status_rows(raw_state) for value in row if value and value >= 2)
55
+
56
+
57
+ def extract_metrics(snapshot: RawSnapshot) -> SnapshotMetrics:
58
+ player = snapshot.state.get("player", {})
59
+ return SnapshotMetrics(
60
+ score=float(player.get("my_score", 0.0)),
61
+ known_tiles=count_known_tiles(snapshot.state),
62
+ visible_tiles=count_visible_tiles(snapshot.state),
63
+ city_count=len(snapshot.state.get("city", {})),
64
+ unit_count=len(snapshot.state.get("unit", {})),
65
+ techs_researched=int(player.get("my_techs_researched", 0) or 0),
66
+ )
67
+
68
+
69
+ def action_lookup_key(action: FreecivAction) -> ActionLookupKey:
70
+ if action.action_type == "move_unit":
71
+ return ("move_unit", action.unit_id, action.direction, None)
72
+ if action.action_type == "build_city":
73
+ return ("build_city", action.unit_id, None, None)
74
+ if action.action_type == "set_city_production":
75
+ return ("set_city_production", action.city_id, None, action.target)
76
+ if action.action_type == "set_research":
77
+ return ("set_research", None, None, action.target)
78
+ return ("end_turn", None, None, None)
79
+
80
+
81
+ def _parse_target_name(raw_action_key: str, prefix: str) -> str:
82
+ suffix = raw_action_key.removeprefix(prefix)
83
+ name, _sep, _tail = suffix.rpartition("_")
84
+ return name or suffix
85
+
86
+
87
+
88
+ def _controller_actions(snapshot: RawSnapshot, controller: str) -> dict[str, Any]:
89
+ raw_actions = snapshot.actions.get(controller, {})
90
+ if isinstance(raw_actions, dict):
91
+ return raw_actions
92
+ if hasattr(raw_actions, "json_struct"):
93
+ json_actions = raw_actions.json_struct()
94
+ return json_actions if isinstance(json_actions, dict) else {}
95
+ return {}
96
+
97
+
98
+
99
+ def _extract_legal_actions(snapshot: RawSnapshot) -> tuple[list[LegalAction], dict[ActionLookupKey, ActionRef]]:
100
+ legal_actions: list[LegalAction] = [
101
+ LegalAction(
102
+ action_type="end_turn",
103
+ label="End the current turn",
104
+ raw_action_key="__end_turn__",
105
+ )
106
+ ]
107
+ refs: dict[ActionLookupKey, ActionRef] = {}
108
+
109
+ for actor_id, action_map in _controller_actions(snapshot, "unit").items():
110
+ unit_id = int(actor_id)
111
+ if action_map.get("build"):
112
+ legal_actions.append(
113
+ LegalAction(
114
+ action_type="build_city",
115
+ label=f"Build a city with unit {unit_id}",
116
+ unit_id=unit_id,
117
+ raw_action_key="build",
118
+ )
119
+ )
120
+ refs[("build_city", unit_id, None, None)] = ActionRef(
121
+ controller="unit",
122
+ actor_id=unit_id,
123
+ raw_action_key="build",
124
+ )
125
+ for raw_action_key, enabled in sorted(action_map.items()):
126
+ if not enabled or not raw_action_key.startswith("goto_"):
127
+ continue
128
+ direction = int(raw_action_key.split("_", 1)[1])
129
+ legal_actions.append(
130
+ LegalAction(
131
+ action_type="move_unit",
132
+ label=f"Move unit {unit_id} in direction {direction}",
133
+ unit_id=unit_id,
134
+ direction=direction,
135
+ raw_action_key=raw_action_key,
136
+ )
137
+ )
138
+ refs[("move_unit", unit_id, direction, None)] = ActionRef(
139
+ controller="unit",
140
+ actor_id=unit_id,
141
+ raw_action_key=raw_action_key,
142
+ )
143
+
144
+ for actor_id, action_map in _controller_actions(snapshot, "city").items():
145
+ city_id = int(actor_id)
146
+ for raw_action_key, enabled in sorted(action_map.items()):
147
+ if not enabled:
148
+ continue
149
+ if raw_action_key.startswith("change_unit_prod_"):
150
+ target = _parse_target_name(raw_action_key, "change_unit_prod_")
151
+ elif raw_action_key.startswith("change_improve_prod_"):
152
+ target = _parse_target_name(raw_action_key, "change_improve_prod_")
153
+ else:
154
+ continue
155
+ legal_actions.append(
156
+ LegalAction(
157
+ action_type="set_city_production",
158
+ label=f"Set city {city_id} production to {target}",
159
+ city_id=city_id,
160
+ target=target,
161
+ raw_action_key=raw_action_key,
162
+ )
163
+ )
164
+ refs[("set_city_production", city_id, None, target)] = ActionRef(
165
+ controller="city",
166
+ actor_id=city_id,
167
+ raw_action_key=raw_action_key,
168
+ )
169
+
170
+ tech_actions = _controller_actions(snapshot, "tech").get("cur_player", {})
171
+ for raw_action_key, enabled in sorted(tech_actions.items()):
172
+ if not enabled or not raw_action_key.startswith("research_tech_"):
173
+ continue
174
+ target = _parse_target_name(raw_action_key, "research_tech_")
175
+ legal_actions.append(
176
+ LegalAction(
177
+ action_type="set_research",
178
+ label=f"Research {target}",
179
+ target=target,
180
+ raw_action_key=raw_action_key,
181
+ )
182
+ )
183
+ refs[("set_research", None, None, target)] = ActionRef(
184
+ controller="tech",
185
+ actor_id="cur_player",
186
+ raw_action_key=raw_action_key,
187
+ )
188
+
189
+ legal_actions.sort(
190
+ key=lambda item: (
191
+ item.action_type,
192
+ item.unit_id or -1,
193
+ item.city_id or -1,
194
+ item.direction or -1,
195
+ item.target or "",
196
+ )
197
+ )
198
+ return legal_actions, refs
199
+
200
+
201
+ def _extract_unit_summaries(snapshot: RawSnapshot) -> list[UnitSummary]:
202
+ unit_actions = _controller_actions(snapshot, "unit")
203
+ units: list[UnitSummary] = []
204
+ for actor_id, unit in sorted(snapshot.state.get("unit", {}).items(), key=lambda item: int(item[0])):
205
+ action_map = unit_actions.get(str(actor_id), unit_actions.get(actor_id, {}))
206
+ move_directions = sorted(
207
+ int(raw_action_key.split("_", 1)[1])
208
+ for raw_action_key, enabled in action_map.items()
209
+ if enabled and raw_action_key.startswith("goto_")
210
+ )
211
+ units.append(
212
+ UnitSummary(
213
+ unit_id=int(actor_id),
214
+ unit_type=str(unit.get("type_rule_name", "Unknown")),
215
+ health=int(unit.get("health", 0) or 0),
216
+ moves_left=int(unit.get("moves_left", unit.get("movesleft", 0)) or 0),
217
+ home_city_id=(
218
+ int(unit.get("home_city"))
219
+ if unit.get("home_city") not in (None, -1, "")
220
+ else None
221
+ ),
222
+ veteran_level=int(unit.get("veteran", 0) or 0),
223
+ can_build_city=bool(action_map.get("build", False)),
224
+ move_directions=move_directions,
225
+ )
226
+ )
227
+ return units
228
+
229
+
230
+ def _extract_city_summaries(snapshot: RawSnapshot) -> list[CitySummary]:
231
+ city_actions = _controller_actions(snapshot, "city")
232
+ cities: list[CitySummary] = []
233
+ for actor_id, city in sorted(snapshot.state.get("city", {}).items(), key=lambda item: int(item[0])):
234
+ action_map = city_actions.get(str(actor_id), city_actions.get(actor_id, {}))
235
+ production_options = [
236
+ _parse_target_name(raw_action_key, "change_unit_prod_")
237
+ for raw_action_key, enabled in sorted(action_map.items())
238
+ if enabled and raw_action_key.startswith("change_unit_prod_")
239
+ ] + [
240
+ _parse_target_name(raw_action_key, "change_improve_prod_")
241
+ for raw_action_key, enabled in sorted(action_map.items())
242
+ if enabled and raw_action_key.startswith("change_improve_prod_")
243
+ ]
244
+ cities.append(
245
+ CitySummary(
246
+ city_id=int(actor_id),
247
+ size=int(city.get("size", 0) or 0),
248
+ prod_food=int(city.get("prod_food", 0) or 0),
249
+ prod_shield=int(city.get("prod_shield", 0) or 0),
250
+ prod_trade=int(city.get("prod_trade", 0) or 0),
251
+ surplus_food=int(city.get("surplus_food", 0) or 0),
252
+ surplus_shield=int(city.get("surplus_shield", 0) or 0),
253
+ surplus_trade=int(city.get("surplus_trade", 0) or 0),
254
+ production_kind=(
255
+ int(city.get("production_kind"))
256
+ if city.get("production_kind") is not None
257
+ else None
258
+ ),
259
+ production_value=(
260
+ int(city.get("production_value"))
261
+ if city.get("production_value") is not None
262
+ else None
263
+ ),
264
+ turns_to_complete=(
265
+ float(city.get("turns_to_prod_complete"))
266
+ if city.get("turns_to_prod_complete") is not None
267
+ else None
268
+ ),
269
+ production_options=production_options,
270
+ )
271
+ )
272
+ return cities
273
+
274
+
275
+ def _build_summary(
276
+ snapshot: RawSnapshot,
277
+ metrics: SnapshotMetrics,
278
+ units: list[UnitSummary],
279
+ cities: list[CitySummary],
280
+ legal_actions: list[LegalAction],
281
+ ) -> str:
282
+ player = snapshot.state.get("player", {})
283
+ lines = [
284
+ f"Turn {snapshot.turn}",
285
+ f"Score {metrics.score:.1f}",
286
+ f"Map: {metrics.known_tiles} known tiles, {metrics.visible_tiles} visible tiles",
287
+ f"Economy: {player.get('my_gold', 0)} gold, science rate {player.get('my_science', 0)}%",
288
+ f"Cities: {metrics.city_count}",
289
+ ]
290
+ for city in cities[:5]:
291
+ lines.append(
292
+ f"- City {city.city_id}: size {city.size}, food {city.prod_food}/{city.surplus_food:+d}, "
293
+ f"shields {city.prod_shield}/{city.surplus_shield:+d}, trade {city.prod_trade}/{city.surplus_trade:+d}"
294
+ )
295
+ lines.append(f"Units: {metrics.unit_count}")
296
+ for unit in units[:8]:
297
+ lines.append(
298
+ f"- Unit {unit.unit_id}: {unit.unit_type}, hp {unit.health}, moves_left {unit.moves_left}, "
299
+ f"build_city={str(unit.can_build_city).lower()}, move_dirs={unit.move_directions}"
300
+ )
301
+ lines.append(f"Techs researched: {metrics.techs_researched}")
302
+ lines.append(f"Legal actions exposed: {len(legal_actions)}")
303
+ return "\n".join(lines)
304
+
305
+
306
+ def prepare_observation(
307
+ snapshot: RawSnapshot,
308
+ *,
309
+ reward: float,
310
+ done: bool,
311
+ status: str,
312
+ metadata: dict[str, Any] | None = None,
313
+ ) -> PreparedObservation:
314
+ legal_actions, action_refs = _extract_legal_actions(snapshot)
315
+ metrics = extract_metrics(snapshot)
316
+ units = _extract_unit_summaries(snapshot)
317
+ cities = _extract_city_summaries(snapshot)
318
+ observation = FreecivObservation(
319
+ turn=snapshot.turn,
320
+ score=metrics.score,
321
+ known_tiles=metrics.known_tiles,
322
+ visible_tiles=metrics.visible_tiles,
323
+ city_count=metrics.city_count,
324
+ unit_count=metrics.unit_count,
325
+ techs_researched=metrics.techs_researched,
326
+ status=status,
327
+ summary=_build_summary(snapshot, metrics, units, cities, legal_actions),
328
+ units=units,
329
+ cities=cities,
330
+ legal_actions=legal_actions,
331
+ reward=reward,
332
+ done=done,
333
+ metadata=metadata or {},
334
+ )
335
+ return PreparedObservation(observation=observation, metrics=metrics, action_refs=action_refs)
freeciv_env/client.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from openenv.core.client_types import StepResult
4
+ from openenv.core.env_client import EnvClient
5
+
6
+ from freeciv_env.models import FreecivAction, FreecivObservation, FreecivState
7
+
8
+
9
+ class FreecivEnv(EnvClient[FreecivAction, FreecivObservation, FreecivState]):
10
+ def _step_payload(self, action: FreecivAction) -> dict:
11
+ return action.model_dump(exclude_none=True)
12
+
13
+ def _parse_result(self, payload: dict) -> StepResult[FreecivObservation]:
14
+ observation = FreecivObservation(**payload["observation"])
15
+ return StepResult(
16
+ observation=observation,
17
+ reward=payload.get("reward"),
18
+ done=payload.get("done", False),
19
+ )
20
+
21
+ def _parse_state(self, payload: dict) -> FreecivState:
22
+ return FreecivState(**payload)
freeciv_env/grpo.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Iterable
5
+
6
+ from freeciv_env.models import FreecivAction, FreecivObservation, LegalAction
7
+
8
+ SYSTEM_PROMPT = (
9
+ "You are choosing the next action for a Freeciv agent. "
10
+ "Return only the integer index of the best legal action. "
11
+ "Do not output words, punctuation, JSON, or explanations."
12
+ )
13
+
14
+ TASK_PROMPT = (
15
+ "Pick the legal action index that maximizes immediate reward. "
16
+ "Invalid actions are penalized. Shorter outputs are better."
17
+ )
18
+
19
+
20
+ def format_action_line(index: int, action: LegalAction) -> str:
21
+ return f"{index}: {action.label}"
22
+
23
+
24
+ def build_turn_prompt(observation: FreecivObservation, task_prompt: str = TASK_PROMPT) -> str:
25
+ action_lines = [format_action_line(index, action) for index, action in enumerate(observation.legal_actions)]
26
+ return (
27
+ f"{task_prompt}\n\n"
28
+ f"State:\n{observation.summary}\n\n"
29
+ f"Legal actions:\n" + "\n".join(action_lines) + "\n\n"
30
+ "Return exactly one integer index."
31
+ )
32
+
33
+
34
+ def parse_action_choice(completion_text: str, legal_actions: Iterable[LegalAction]) -> FreecivAction | None:
35
+ legal_actions = list(legal_actions)
36
+ match = re.search(r"-?\d+", completion_text)
37
+ if match is None:
38
+ return None
39
+ index = int(match.group(0))
40
+ if index < 0 or index >= len(legal_actions):
41
+ return None
42
+ action = legal_actions[index]
43
+ if action.action_type == "end_turn":
44
+ return FreecivAction(action_type="end_turn")
45
+ if action.action_type == "move_unit":
46
+ return FreecivAction(action_type="move_unit", unit_id=action.unit_id, direction=action.direction)
47
+ if action.action_type == "build_city":
48
+ return FreecivAction(action_type="build_city", unit_id=action.unit_id)
49
+ if action.action_type == "set_city_production":
50
+ return FreecivAction(action_type="set_city_production", city_id=action.city_id, target=action.target)
51
+ if action.action_type == "set_research":
52
+ return FreecivAction(action_type="set_research", target=action.target)
53
+ raise ValueError(f"unsupported action_type: {action.action_type}")
54
+
55
+
56
+ def action_priority(action: LegalAction) -> tuple[int, int]:
57
+ if action.action_type == "build_city":
58
+ return (500, 0)
59
+ if action.action_type == "set_research":
60
+ return (400, 0)
61
+ if action.action_type == "set_city_production":
62
+ bonus = 50 if (action.target or "") == "Settlers" else 0
63
+ return (300 + bonus, 0)
64
+ if action.action_type == "move_unit":
65
+ return (200, -(action.direction or 0))
66
+ if action.action_type == "end_turn":
67
+ return (0, 0)
68
+ return (-1000, 0)
69
+
70
+
71
+
72
+ def oracle_action_index(legal_actions: Iterable[LegalAction]) -> int:
73
+ legal_actions = list(legal_actions)
74
+ if not legal_actions:
75
+ raise ValueError("no legal actions available")
76
+ best_index = 0
77
+ best_priority = action_priority(legal_actions[0])
78
+ for index, action in enumerate(legal_actions[1:], start=1):
79
+ priority = action_priority(action)
80
+ if priority > best_priority:
81
+ best_index = index
82
+ best_priority = priority
83
+ return best_index
84
+
85
+
86
+
87
+ def reward_from_oracle(completions, best_index, **kwargs):
88
+ del kwargs
89
+ rewards = []
90
+ for completion, expected in zip(completions, best_index):
91
+ match = re.search(r"-?\d+", completion if isinstance(completion, str) else str(completion))
92
+ if match is None:
93
+ rewards.append(-0.25)
94
+ continue
95
+ chosen = int(match.group(0))
96
+ rewards.append(1.0 if chosen == int(expected) else 0.0)
97
+ return rewards
freeciv_env/models.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, Field, model_validator
6
+
7
+ from openenv.core.env_server.types import Action, Observation, State
8
+
9
+
10
+ class UnitSummary(BaseModel):
11
+ unit_id: int = Field(..., description="Freeciv unit id")
12
+ unit_type: str = Field(..., description="Ruleset unit type name")
13
+ health: int = Field(0, description="Current health")
14
+ moves_left: int = Field(0, description="Movement points remaining")
15
+ home_city_id: int | None = Field(None, description="Home city id, if any")
16
+ veteran_level: int = Field(0, description="Veteran level")
17
+ can_build_city: bool = Field(False, description="Whether the unit can found a city now")
18
+ move_directions: list[int] = Field(default_factory=list, description="Legal move direction indexes")
19
+
20
+
21
+ class CitySummary(BaseModel):
22
+ city_id: int = Field(..., description="Freeciv city id")
23
+ size: int = Field(..., description="Population size")
24
+ prod_food: int = Field(0, description="Gross food output")
25
+ prod_shield: int = Field(0, description="Gross shield output")
26
+ prod_trade: int = Field(0, description="Gross trade output")
27
+ surplus_food: int = Field(0, description="Net food surplus")
28
+ surplus_shield: int = Field(0, description="Net shield surplus")
29
+ surplus_trade: int = Field(0, description="Net trade surplus")
30
+ production_kind: int | None = Field(None, description="Current production kind enum from Freeciv")
31
+ production_value: int | None = Field(None, description="Current production value id from Freeciv")
32
+ turns_to_complete: float | None = Field(None, description="Turns until current production completes")
33
+ production_options: list[str] = Field(default_factory=list, description="Legal production targets")
34
+
35
+
36
+ class LegalAction(BaseModel):
37
+ action_type: Literal[
38
+ "end_turn",
39
+ "move_unit",
40
+ "build_city",
41
+ "set_city_production",
42
+ "set_research",
43
+ ]
44
+ label: str = Field(..., description="Human-readable action label")
45
+ unit_id: int | None = Field(None, description="Target unit id")
46
+ city_id: int | None = Field(None, description="Target city id")
47
+ direction: int | None = Field(None, description="Freeciv direction index 0..7")
48
+ target: str | None = Field(None, description="Production or tech target name")
49
+ raw_action_key: str | None = Field(None, description="Underlying freeciv-bot action key")
50
+
51
+
52
+ class FreecivAction(Action):
53
+ action_type: Literal[
54
+ "end_turn",
55
+ "move_unit",
56
+ "build_city",
57
+ "set_city_production",
58
+ "set_research",
59
+ ]
60
+ unit_id: int | None = None
61
+ city_id: int | None = None
62
+ direction: int | None = None
63
+ target: str | None = None
64
+
65
+ @model_validator(mode="after")
66
+ def validate_shape(self) -> "FreecivAction":
67
+ if self.action_type == "end_turn":
68
+ return self
69
+ if self.action_type == "move_unit":
70
+ if self.unit_id is None or self.direction is None:
71
+ raise ValueError("move_unit requires unit_id and direction")
72
+ return self
73
+ if self.action_type == "build_city":
74
+ if self.unit_id is None:
75
+ raise ValueError("build_city requires unit_id")
76
+ return self
77
+ if self.action_type == "set_city_production":
78
+ if self.city_id is None or not self.target:
79
+ raise ValueError("set_city_production requires city_id and target")
80
+ return self
81
+ if self.action_type == "set_research":
82
+ if not self.target:
83
+ raise ValueError("set_research requires target")
84
+ return self
85
+ raise ValueError(f"unsupported action_type: {self.action_type}")
86
+
87
+
88
+ class FreecivObservation(Observation):
89
+ turn: int = Field(..., description="Current game turn")
90
+ score: float = Field(..., description="Current player score")
91
+ known_tiles: int = Field(..., description="Tiles known to the player")
92
+ visible_tiles: int = Field(..., description="Tiles currently visible to the player")
93
+ city_count: int = Field(..., description="Number of owned cities")
94
+ unit_count: int = Field(..., description="Number of owned units")
95
+ techs_researched: int = Field(..., description="Number of researched techs")
96
+ status: str = Field("ok", description="High-level environment status")
97
+ summary: str = Field(..., description="Compact text summary for LLMs")
98
+ units: list[UnitSummary] = Field(default_factory=list, description="Compact unit summaries")
99
+ cities: list[CitySummary] = Field(default_factory=list, description="Compact city summaries")
100
+ legal_actions: list[LegalAction] = Field(default_factory=list, description="Legal actions exposed by the environment")
101
+ reward: float = Field(0.0, description="Reward from the last action")
102
+ done: bool = Field(False, description="Whether the episode is done")
103
+
104
+
105
+ class FreecivState(State):
106
+ turn: int = Field(0, description="Current game turn")
107
+ score: float = Field(0.0, description="Current player score")
108
+ known_tiles: int = Field(0, description="Known tiles")
109
+ visible_tiles: int = Field(0, description="Visible tiles")
110
+ city_count: int = Field(0, description="Owned city count")
111
+ unit_count: int = Field(0, description="Owned unit count")
112
+ techs_researched: int = Field(0, description="Researched tech count")
freeciv_env/runtime.py ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import threading
6
+ import time
7
+ from typing import Protocol
8
+ from urllib.parse import urlencode, urlparse
9
+ from urllib.request import Request, urlopen
10
+
11
+ from freeciv_env.adapter import ActionRef, RawSnapshot
12
+
13
+
14
+ class FreecivSession(Protocol):
15
+ def reset(self, seed: int | None = None) -> RawSnapshot: ...
16
+
17
+ def apply_action(self, action_ref: ActionRef) -> RawSnapshot: ...
18
+
19
+ def end_turn(self) -> RawSnapshot: ...
20
+
21
+ def close(self) -> None: ...
22
+
23
+
24
+ class _InteractiveBot:
25
+ def __init__(self, session: "LiveFreecivSession"):
26
+ from freecivbot.bot.base_bot import BaseBot
27
+
28
+ class InteractiveBotImpl(BaseBot):
29
+ def __init__(self, owner: "LiveFreecivSession"):
30
+ super().__init__()
31
+ self._owner = owner
32
+
33
+ def conduct_turn(self, pplayer, info_controls, end_turn_hook):
34
+ super().conduct_turn(pplayer, info_controls, end_turn_hook)
35
+ self._publish_snapshot()
36
+
37
+ def calculate_next_move(self):
38
+ if self._turn_active:
39
+ self._publish_snapshot()
40
+
41
+ def _publish_snapshot(self):
42
+ self._acquire_state()
43
+ self._owner._publish_snapshot(
44
+ RawSnapshot(
45
+ turn=self.turn,
46
+ state=self._turn_state,
47
+ actions=self._turn_opts,
48
+ )
49
+ )
50
+
51
+ self.impl = InteractiveBotImpl(session)
52
+
53
+
54
+ class _ConfiguredCivClient:
55
+ def __init__(self, bot, user_name: str, *, client_port: int, visual_monitor: bool = False):
56
+ from freecivbot.civclient import CivClient
57
+
58
+ class ConfiguredCivClientImpl(CivClient):
59
+ def init_control(self, ws_client):
60
+ self.ws_client = ws_client
61
+ self.init_controller()
62
+ if self.visual_monitor:
63
+ self.monitor.start_monitor()
64
+ login_message = {
65
+ "pid": 4,
66
+ "username": self.user_name,
67
+ "capability": "+Freeciv.Web.Devel-3.2",
68
+ "version_label": "-dev",
69
+ "major_version": 3,
70
+ "minor_version": 1,
71
+ "patch_version": 90,
72
+ "port": self.client_port,
73
+ "password": None,
74
+ "subject": None,
75
+ }
76
+ self.ws_client.send(login_message)
77
+
78
+ def handle_chat_msg(self, packet):
79
+ from freecivbot.utils.fc_events import E_UNDEFINED
80
+
81
+ message = packet["message"]
82
+ conn_id = packet["conn_id"]
83
+ event = packet["event"]
84
+
85
+ if message is None:
86
+ return
87
+ if event is None or event < 0 or event >= E_UNDEFINED:
88
+ print("Undefined message event type")
89
+ print(packet)
90
+ print("\r\n")
91
+ packet["event"] = event = E_UNDEFINED
92
+
93
+ if conn_id in self.clstate.connections:
94
+ message = "<b>" + self.clstate.connections[conn_id]["username"] + ":</b>" + message
95
+ else:
96
+ if "/metamessage" in message:
97
+ return
98
+ if "Metaserver message string" in message:
99
+ return
100
+
101
+ packet["message"] = message
102
+ print(packet)
103
+ print("\r\n")
104
+
105
+ if "You are logged in as" in message:
106
+ self.ws_client.send_message("/set minplayers 1")
107
+ self.prepare_game()
108
+
109
+ def handle_conn_info(self, packet):
110
+ from freecivbot.connectivity.client_state import C_S_PREPARING
111
+ from freecivbot.utils.freecivlog import freelog
112
+
113
+ pconn = self.clstate.find_conn_by_id(packet["id"])
114
+
115
+ if not packet["used"]:
116
+ if pconn is None:
117
+ freelog(f"Server removed unknown connection {packet['id']}")
118
+ return
119
+ self.clstate.client_remove_cli_conn(pconn)
120
+ pconn = None
121
+ else:
122
+ pplayer = self.player_ctrl.valid_player_by_number(packet["player_num"])
123
+ if pplayer is None:
124
+ return
125
+ packet["playing"] = pplayer
126
+
127
+ if self.clstate.has_id(packet["id"]):
128
+ self.clstate.init_state(packet)
129
+
130
+ self.clstate.conn_list_append(packet)
131
+
132
+ if self.clstate.has_id(packet["id"]) and self.clstate.cur_player() != packet["playing"]:
133
+ self.clstate.set_client_state(C_S_PREPARING)
134
+
135
+ self.impl = ConfiguredCivClientImpl(
136
+ bot,
137
+ user_name,
138
+ client_port=client_port,
139
+ visual_monitor=visual_monitor,
140
+ )
141
+
142
+
143
+ class _ConfiguredCivConnection:
144
+ def __init__(self, civ_client, base_url: str, *, owner: "LiveFreecivSession", wait_for_server: int = 120, retry_interval: int = 5):
145
+ from math import ceil
146
+
147
+ import websocket
148
+
149
+ self._websocket = websocket
150
+ self.client = civ_client
151
+ self.base_url = base_url
152
+ self._owner = owner
153
+ self._loop = None
154
+ self._owner._connection = self
155
+ self.civserverport = self._reserve_client_port(base_url, civ_client.client_port)
156
+ self.client.client_port = self.civserverport
157
+ self.proxyport = 1000 + self.civserverport
158
+ self._retry_interval = retry_interval
159
+ self._num_retries = int(ceil(wait_for_server / retry_interval))
160
+ self._cur_retry = 0
161
+ self._ws_url = self._build_ws_url(base_url)
162
+ self.network_init()
163
+
164
+ def _build_ws_url(self, base_url: str) -> str:
165
+ parsed = urlparse(base_url)
166
+ scheme = "wss" if parsed.scheme == "https" else "ws"
167
+ host = parsed.hostname or "localhost"
168
+ port = parsed.port
169
+ if port is None:
170
+ port = 443 if scheme == "wss" else 80
171
+ return f"{scheme}://{host}:{port}/civsocket/{self.proxyport}"
172
+
173
+ def _reserve_client_port(self, base_url: str, requested_port: int) -> int:
174
+ parsed = urlparse(base_url)
175
+ scheme = parsed.scheme or "http"
176
+ host = parsed.hostname or "localhost"
177
+ port = parsed.port
178
+ if port is None:
179
+ port = 443 if scheme == "https" else 80
180
+ query = urlencode({"civserverport": requested_port})
181
+ launcher_url = f"{scheme}://{host}:{port}/civclientlauncher?{query}"
182
+ request = Request(launcher_url, method="POST")
183
+ with urlopen(request, timeout=10) as response:
184
+ result = response.headers.get("result")
185
+ reserved_port = response.headers.get("port")
186
+ if result != "success" or reserved_port is None:
187
+ raise RuntimeError(f"failed to reserve freeciv client port via {launcher_url}")
188
+ return int(reserved_port)
189
+
190
+ def _retry(self):
191
+ self._cur_retry += 1
192
+ time.sleep(self._retry_interval)
193
+ return self._detect_server_up()
194
+
195
+ def _detect_server_up(self):
196
+ ws = self._websocket.WebSocket()
197
+ try:
198
+ ws.connect(self._ws_url, timeout=10)
199
+ return True
200
+ except Exception as err:
201
+ print("Connect not successful:", err, " retrying in %s seconds." % self._retry_interval)
202
+ if self._cur_retry < self._num_retries:
203
+ return self._retry()
204
+ return False
205
+ finally:
206
+ try:
207
+ ws.close()
208
+ except Exception:
209
+ pass
210
+
211
+ def network_init(self):
212
+ self._cur_retry = 0
213
+ print("Connecting to server at %s ..." % self.base_url)
214
+ if self._detect_server_up():
215
+ self.websocket_init()
216
+ else:
217
+ print("Connection could not be established!")
218
+
219
+ def websocket_init(self):
220
+ from tornado import ioloop
221
+
222
+ from freecivbot.connectivity.clinet import CivWSClient
223
+
224
+ asyncio.set_event_loop(asyncio.new_event_loop())
225
+ ioloop.IOLoop.clear_current()
226
+ self._loop = ioloop.IOLoop.current()
227
+
228
+ client = CivWSClient(self.client)
229
+
230
+ def send_json(data):
231
+ if not client._ws_connection:
232
+ raise RuntimeError("Web socket connection is closed.")
233
+ msg = json.dumps(data, separators=(",", ":"))
234
+ client._ws_connection.write_message(msg)
235
+
236
+ client.send = send_json
237
+ client.connect(self._ws_url)
238
+
239
+ try:
240
+ self._loop.start()
241
+ except KeyboardInterrupt:
242
+ client.close()
243
+
244
+ def submit(self, fn) -> None:
245
+ if self._loop is None:
246
+ raise RuntimeError("freeciv connection loop is not ready")
247
+ done = threading.Event()
248
+ error: BaseException | None = None
249
+
250
+ def run():
251
+ nonlocal error
252
+ try:
253
+ fn()
254
+ except BaseException as exc:
255
+ error = exc
256
+ finally:
257
+ done.set()
258
+
259
+ self._loop.add_callback(run)
260
+ if not done.wait(timeout=10):
261
+ raise TimeoutError("timed out dispatching action to freeciv loop")
262
+ if error is not None:
263
+ raise error
264
+
265
+ def close(self) -> None:
266
+ if self._loop is None:
267
+ return
268
+ self.submit(self.client.close)
269
+
270
+
271
+ class LiveFreecivSession:
272
+ def __init__(
273
+ self,
274
+ *,
275
+ username: str = "openenvbot",
276
+ client_port: int = 6000,
277
+ base_url: str = "http://localhost",
278
+ turn_timeout_s: float = 60.0,
279
+ ):
280
+ self.username = username
281
+ self.client_port = client_port
282
+ self.base_url = base_url
283
+ self.turn_timeout_s = turn_timeout_s
284
+
285
+ self._bot_wrapper: _InteractiveBot | None = None
286
+ self._client = None
287
+ self._connection: _ConfiguredCivConnection | None = None
288
+ self._thread: threading.Thread | None = None
289
+ self._ready = threading.Event()
290
+ self._snapshot_lock = threading.Lock()
291
+ self._snapshot: RawSnapshot | None = None
292
+ self._thread_error: BaseException | None = None
293
+ self._reset_counter = 0
294
+ self._session_seed = time.monotonic_ns() % 1_000_000
295
+
296
+ def reset(self, seed: int | None = None) -> RawSnapshot:
297
+ del seed
298
+ self.close()
299
+ self._reset_counter += 1
300
+ username = self._next_username()
301
+ client_port = self.client_port + ((self._session_seed + self._reset_counter - 1) % 3)
302
+
303
+ self._ready.clear()
304
+ self._thread_error = None
305
+ self._snapshot = None
306
+
307
+ self._bot_wrapper = _InteractiveBot(self)
308
+ self._client = _ConfiguredCivClient(
309
+ self._bot_wrapper.impl,
310
+ username,
311
+ client_port=client_port,
312
+ visual_monitor=False,
313
+ ).impl
314
+
315
+ def run() -> None:
316
+ try:
317
+ _ConfiguredCivConnection(self._client, self.base_url, owner=self)
318
+ except BaseException as exc: # pragma: no cover - surfaced in waiters
319
+ self._thread_error = exc
320
+ self._ready.set()
321
+
322
+ self._thread = threading.Thread(target=run, name="freeciv-live-session", daemon=True)
323
+ self._thread.start()
324
+ return self._wait_for_snapshot("reset")
325
+
326
+ def apply_action(self, action_ref: ActionRef) -> RawSnapshot:
327
+ snapshot = self._require_snapshot()
328
+ action_list = snapshot.actions[action_ref.controller]
329
+ valid_actions = action_list.get_actions(action_ref.actor_id, valid_only=True)
330
+ action = None if valid_actions is None else valid_actions.get(action_ref.raw_action_key)
331
+ if action is None:
332
+ raise ValueError(
333
+ f"action {action_ref.raw_action_key} is no longer valid for {action_ref.controller}:{action_ref.actor_id}"
334
+ )
335
+ self._ready.clear()
336
+ connection = self._require_connection()
337
+ connection.submit(lambda: action_list.trigger_validated_action(action))
338
+ return self._wait_for_snapshot(action_ref.raw_action_key)
339
+
340
+ def end_turn(self) -> RawSnapshot:
341
+ if self._bot_wrapper is None:
342
+ raise RuntimeError("session has not been reset")
343
+ self._ready.clear()
344
+ connection = self._require_connection()
345
+ connection.submit(self._bot_wrapper.impl.end_turn)
346
+ return self._wait_for_snapshot("end_turn")
347
+
348
+ def close(self) -> None:
349
+ if self._connection is not None:
350
+ try:
351
+ self._connection.close()
352
+ except Exception:
353
+ pass
354
+ elif self._client is not None:
355
+ try:
356
+ self._client.close()
357
+ except Exception:
358
+ pass
359
+ if self._thread is not None and self._thread.is_alive():
360
+ self._thread.join(timeout=5)
361
+ self._bot_wrapper = None
362
+ self._client = None
363
+ self._connection = None
364
+ self._thread = None
365
+ self._snapshot = None
366
+ self._thread_error = None
367
+ self._ready.clear()
368
+
369
+ def _publish_snapshot(self, snapshot: RawSnapshot) -> None:
370
+ with self._snapshot_lock:
371
+ self._snapshot = snapshot
372
+ self._ready.set()
373
+
374
+ def _next_username(self) -> str:
375
+ suffix = str(self._session_seed + self._reset_counter)
376
+ prefix_len = max(1, 31 - len(suffix))
377
+ return f"{self.username[:prefix_len]}{suffix}"
378
+
379
+ def _require_connection(self) -> _ConfiguredCivConnection:
380
+ if self._connection is None:
381
+ raise RuntimeError("freeciv connection is not ready")
382
+ return self._connection
383
+
384
+ def _require_snapshot(self) -> RawSnapshot:
385
+ with self._snapshot_lock:
386
+ if self._snapshot is None:
387
+ raise RuntimeError("no live snapshot is available")
388
+ return self._snapshot
389
+
390
+ def _wait_for_snapshot(self, reason: str) -> RawSnapshot:
391
+ deadline = time.monotonic() + self.turn_timeout_s
392
+ while time.monotonic() < deadline:
393
+ if self._thread_error is not None:
394
+ raise RuntimeError(f"freeciv session failed during {reason}") from self._thread_error
395
+ if self._ready.wait(timeout=0.1):
396
+ if self._thread_error is not None:
397
+ raise RuntimeError(f"freeciv session failed during {reason}") from self._thread_error
398
+ snapshot = self._require_snapshot()
399
+ if snapshot is not None:
400
+ return snapshot
401
+ raise TimeoutError(f"timed out waiting for freeciv snapshot during {reason}")
freeciv_env/server/Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ARG BASE_IMAGE=openenv-base:latest
2
+ FROM ${BASE_IMAGE} AS builder
3
+
4
+ WORKDIR /app/env
5
+ COPY . /app/env
6
+
7
+ RUN --mount=type=cache,target=/root/.cache/uv \
8
+ if [ -f uv.lock ]; then \
9
+ uv sync --frozen --no-editable; \
10
+ else \
11
+ uv sync --no-editable; \
12
+ fi
13
+
14
+ FROM ${BASE_IMAGE}
15
+
16
+ WORKDIR /app/env
17
+ COPY --from=builder /app/env /app/env
18
+ COPY --from=builder /app/env/.venv /app/env/.venv
19
+
20
+ ENV PATH="/app/env/.venv/bin:$PATH"
21
+ ENV PYTHONPATH="/app/env:$PYTHONPATH"
22
+
23
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
24
+ CMD curl -f http://localhost:8000/health || exit 1
25
+
26
+ CMD ["sh", "-c", "cd /app/env && uvicorn freeciv_env.server.app:app --host 0.0.0.0 --port 8000 --ws-ping-interval 300 --ws-ping-timeout 300"]
freeciv_env/server/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from freeciv_env.server.freeciv_environment import FreecivEnvironment
2
+
3
+ __all__ = ["FreecivEnvironment"]
freeciv_env/server/app.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from openenv.core.env_server import create_app
6
+
7
+ from freeciv_env.models import FreecivAction, FreecivObservation
8
+ from freeciv_env.runtime import LiveFreecivSession
9
+ from freeciv_env.server.freeciv_environment import FreecivEnvironment
10
+
11
+
12
+ def create_live_session() -> LiveFreecivSession:
13
+ return LiveFreecivSession(
14
+ username=os.getenv("FREECIV_USERNAME", "openenvbot"),
15
+ client_port=int(os.getenv("FREECIV_CLIENT_PORT", "6000")),
16
+ base_url=os.getenv("FREECIV_SERVER_URL", "http://localhost"),
17
+ turn_timeout_s=float(os.getenv("FREECIV_TURN_TIMEOUT_S", "60")),
18
+ )
19
+
20
+
21
+ def create_freeciv_app(*, session_factory=create_live_session, max_turns: int | None = None):
22
+ if max_turns is None:
23
+ max_turns = int(os.getenv("FREECIV_MAX_TURNS", "50"))
24
+ return create_app(
25
+ lambda: FreecivEnvironment(session_factory=session_factory, max_turns=max_turns),
26
+ FreecivAction,
27
+ FreecivObservation,
28
+ env_name="freeciv_env",
29
+ )
30
+
31
+
32
+ app = create_freeciv_app()
33
+
34
+
35
+ def main() -> None:
36
+ import uvicorn
37
+
38
+ uvicorn.run(app, host="0.0.0.0", port=8000, ws_ping_interval=300, ws_ping_timeout=300)
39
+
40
+
41
+ if __name__ == "__main__":
42
+ main()
freeciv_env/server/freeciv_environment.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable
4
+ from uuid import uuid4
5
+
6
+ from openenv.core.env_server.interfaces import Environment
7
+
8
+ from freeciv_env.adapter import (
9
+ ActionLookupKey,
10
+ ActionRef,
11
+ PreparedObservation,
12
+ RawSnapshot,
13
+ SnapshotMetrics,
14
+ action_lookup_key,
15
+ prepare_observation,
16
+ )
17
+ from freeciv_env.models import FreecivAction, FreecivObservation, FreecivState
18
+ from freeciv_env.runtime import FreecivSession
19
+
20
+
21
+ class FreecivEnvironment(Environment[FreecivAction, FreecivObservation, FreecivState]):
22
+ SUPPORTS_CONCURRENT_SESSIONS = False
23
+
24
+ def __init__(self, session_factory: Callable[[], FreecivSession], max_turns: int = 50):
25
+ super().__init__()
26
+ self._session_factory = session_factory
27
+ self.max_turns = max_turns
28
+ self._session: FreecivSession | None = None
29
+ self._snapshot: RawSnapshot | None = None
30
+ self._metrics: SnapshotMetrics | None = None
31
+ self._action_refs: dict[ActionLookupKey, ActionRef] = {}
32
+ self._state = FreecivState(episode_id=str(uuid4()), step_count=0)
33
+
34
+ def reset(
35
+ self,
36
+ seed: int | None = None,
37
+ episode_id: str | None = None,
38
+ **kwargs,
39
+ ) -> FreecivObservation:
40
+ del kwargs
41
+ self.close()
42
+ self._session = self._session_factory()
43
+ snapshot = self._session.reset(seed=seed)
44
+ prepared = prepare_observation(
45
+ snapshot,
46
+ reward=0.0,
47
+ done=self._is_done(snapshot),
48
+ status="ready",
49
+ metadata={},
50
+ )
51
+ self._commit(snapshot, prepared, episode_id=episode_id or str(uuid4()))
52
+ return prepared.observation
53
+
54
+ def step(
55
+ self,
56
+ action: FreecivAction,
57
+ timeout_s: float | None = None,
58
+ **kwargs,
59
+ ) -> FreecivObservation:
60
+ del timeout_s, kwargs
61
+ if self._session is None or self._snapshot is None or self._metrics is None:
62
+ raise RuntimeError("environment must be reset before step")
63
+
64
+ self._state.step_count += 1
65
+ if action.action_type == "end_turn":
66
+ next_snapshot = self._session.end_turn()
67
+ reward = self._reward_for_transition(action, self._metrics, next_snapshot)
68
+ prepared = prepare_observation(
69
+ next_snapshot,
70
+ reward=reward,
71
+ done=self._is_done(next_snapshot),
72
+ status="ok",
73
+ metadata={},
74
+ )
75
+ self._commit(next_snapshot, prepared, episode_id=self._state.episode_id)
76
+ return prepared.observation
77
+
78
+ ref = self._action_refs.get(action_lookup_key(action))
79
+ if ref is None:
80
+ prepared = prepare_observation(
81
+ self._snapshot,
82
+ reward=-0.25,
83
+ done=self._is_done(self._snapshot),
84
+ status="invalid_action",
85
+ metadata={"error": "action is not currently legal"},
86
+ )
87
+ self._commit(self._snapshot, prepared, episode_id=self._state.episode_id, replace_snapshot=False)
88
+ return prepared.observation
89
+
90
+ next_snapshot = self._session.apply_action(ref)
91
+ reward = self._reward_for_transition(action, self._metrics, next_snapshot)
92
+ prepared = prepare_observation(
93
+ next_snapshot,
94
+ reward=reward,
95
+ done=self._is_done(next_snapshot),
96
+ status="ok",
97
+ metadata={},
98
+ )
99
+ self._commit(next_snapshot, prepared, episode_id=self._state.episode_id)
100
+ return prepared.observation
101
+
102
+ @property
103
+ def state(self) -> FreecivState:
104
+ return self._state
105
+
106
+ def close(self) -> None:
107
+ if self._session is not None:
108
+ self._session.close()
109
+ self._session = None
110
+ self._snapshot = None
111
+ self._metrics = None
112
+ self._action_refs = {}
113
+
114
+ def _commit(
115
+ self,
116
+ snapshot: RawSnapshot,
117
+ prepared: PreparedObservation,
118
+ *,
119
+ episode_id: str,
120
+ replace_snapshot: bool = True,
121
+ ) -> None:
122
+ if replace_snapshot:
123
+ self._snapshot = snapshot
124
+ self._metrics = prepared.metrics
125
+ self._action_refs = prepared.action_refs
126
+ self._state = FreecivState(
127
+ episode_id=episode_id,
128
+ step_count=self._state.step_count,
129
+ turn=prepared.observation.turn,
130
+ score=prepared.observation.score,
131
+ known_tiles=prepared.observation.known_tiles,
132
+ visible_tiles=prepared.observation.visible_tiles,
133
+ city_count=prepared.observation.city_count,
134
+ unit_count=prepared.observation.unit_count,
135
+ techs_researched=prepared.observation.techs_researched,
136
+ )
137
+
138
+ def _reward_for_transition(
139
+ self,
140
+ action: FreecivAction,
141
+ previous: SnapshotMetrics,
142
+ next_snapshot: RawSnapshot,
143
+ ) -> float:
144
+ from freeciv_env.adapter import extract_metrics
145
+
146
+ current = extract_metrics(next_snapshot)
147
+ reward = {
148
+ "end_turn": 0.0,
149
+ "move_unit": 0.01,
150
+ "build_city": 0.10,
151
+ "set_city_production": 0.05,
152
+ "set_research": 0.05,
153
+ }[action.action_type]
154
+ reward += max(current.score - previous.score, 0.0) * 0.02
155
+ reward += max(current.known_tiles - previous.known_tiles, 0) * 0.01
156
+ reward += max(current.city_count - previous.city_count, 0) * 0.50
157
+ reward += max(current.techs_researched - previous.techs_researched, 0) * 0.25
158
+ return float(reward)
159
+
160
+ def _is_done(self, snapshot: RawSnapshot) -> bool:
161
+ player = snapshot.state.get("player", {})
162
+ alive = bool(player.get("my_is_alive", True))
163
+ return (not alive) or snapshot.turn >= self.max_turns
freeciv_rl_training_curve.png ADDED
hackathon.md ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## **OpenEnv Hackathon Participant Guide**
2
+
3
+ Welcome to the [OpenEnv Hackathon](https://cerebralvalley.ai/e/open-env-hackathon), hacker! 👋 We’re thrilled to have you on board.
4
+
5
+ This guide is your all-in-one resource for the event, including schedule, rules, technical resources, problem statements, judging information, and more. Please read this carefully; most answers can be found here.
6
+
7
+ ## **1. Join the [PyTorch Discord Server](https://discord.gg/VBcf6VtfY6)**
8
+
9
+ - You’ll be given a Hackathon Participant role by an admin, which will give you access to the hackathon-specific channels.
10
+
11
+ - Here, you’ll be able to interact with hackers and sponsors, introduce yourselves, and form teams (for a maximum team size of **3**).
12
+
13
+ - If you don't receive your role within **24 hours of joining,** please ping @CV.
14
+
15
+ - Please submit your Discord username below so we can grant you the role
16
+
17
+ [linkEmbed]
18
+
19
+ ## **2. Location**
20
+
21
+ **|** Shack15 (1 Ferry Building, Suite 201, San Francisco CA. 94111)
22
+
23
+ - **Venue Access:** Shack15 is on the 2nd floor of the Ferry Building. Go up the Ferry Building elevator to the second floor, and turn left. Here you will see the main entrance to Shack15. 
24
+
25
+ - **Parking:** Parking near the Ferry Building is extremely limited. Consider parking farther out and taking Uber, Lyft, or Public Transportation. 
26
+
27
+ [youtube]
28
+
29
+ ## **3. WiFi Information**
30
+
31
+ - **Username:** SHACK15_Members
32
+
33
+ - **Password:** M3mb3r$4L!f3
34
+
35
+ ## **4. Hackathon Schedule**
36
+
37
+ **Saturday, March 7 (Outline)**
38
+
39
+ - **9:00 AM:** Doors Open •󠁏 Breakfast Served •󠁏 Team Formation
40
+
41
+ - **10:00 AM – 11:30AM**: Kick-off presentations with Meta, Hugging Face, UC Berkeley, CoreWeave, OpenPipe, Unsloth AI, Fleet AI, Mercor, Scaler AI Labs, Snorkel AI, Patronus AI, Halluminate and Scale AI
42
+
43
+ - **11:30 AM:** Hacking Begins
44
+
45
+ - **1:00 PM:** Lunch Served
46
+
47
+ - **6:00 PM:** Dinner Served
48
+
49
+ - **10:00 PM:** Doors Close •󠁏 Re-entry not permitted
50
+
51
+ **Sunday, March 8 (Outline)**
52
+
53
+ - **9:00AM:** Doors Open •󠁏 Breakfast Served
54
+
55
+ - **1:00PM:** Hacking stops •󠁏 Submissions Due
56
+
57
+ - **1:15PM:** First Round Judging Begins
58
+
59
+ - **2:00PM:** Lunch Served
60
+
61
+ - **3:00PM:** Final Round Judging Begins
62
+
63
+ - **4:00PM:** Winners Announced and Closing
64
+
65
+ - **5:00PM:** Doors Close
66
+
67
+ All presentation slides can be found here
68
+
69
+ [linkEmbed]
70
+
71
+ ## **5. Hackathon and Submission Rules**
72
+
73
+ To keep things fair and aligned with our goals, all teams must follow these rules:
74
+
75
+ - **Open Source:** Please ensure your repository is public.
76
+
77
+ - **New Work Only:** All projects must be started from scratch during the hackathon with no previous work.
78
+
79
+ - **Team Size:** Teams may have up to **3** members.
80
+
81
+ - **Banned Projects:** Projects will be disqualified if they: violate legal, ethical, or platform policies, use code, data, or assets you do not have the rights to.
82
+
83
+ - Your project **must** use OpenEnv (stable release 0.2.1) deployed on HF spaces
84
+
85
+ - You must show a minimal training script for your environment using Unsloth or HF TRL in Colab.
86
+
87
+ - You must upload a **one minute** demo video to YouTube talking about your submission.
88
+
89
+ ## **6. Hackathon Problem Statements**
90
+
91
+ Your project must address at least **one of the five** required problem statements.
92
+
93
+ - Some problem statements include **optional partner-sponsored sub-problem statements**, which are additional focus areas related to the main theme.
94
+
95
+ - Your project may align with **multiple partner sub-problem statements**, but you can only be **judged for a maximum of two**. Please **select up to two** when submitting.
96
+
97
+ - Projects that match these partner sub-problem statements are eligible for **extra partner prizes**, judged separately from the main track winners.
98
+
99
+ - Each partner sub-problem statement carries a prize of **$10,000 USD**.
100
+
101
+ **Statement 1: Multi-Agent Interactions**
102
+
103
+ Environments for this theme involve cooperation, competition, negotiation, and coalition formation. Learning from these environments will enable agents to model the beliefs and incentives of others in partially observable settings. This drives theory-of-mind reasoning and emergent strategic behavior.
104
+
105
+ - **Expected Outcome:** an environment that can be used to train multi-agent task handling in a LLM
106
+
107
+ - **Example Environments:** Market simulations, compute-allocation negotiations, collaborative puzzle worlds, mixed cooperative/competitive strategy games.
108
+
109
+ - **Partner Sub-Themes:**
110
+
111
+ - **Fleet AI:** Scalable Oversight: Environments that train oversight agents to monitor, analyze, and explain the behavior of other AI agents operating in complex, multi-agent settings.
112
+ - **Halluminate:** Multi-Actor Environments: Build a realistic environment where an agent interacts with and manages multiple actors (agents) to discover and achieve the task
113
+
114
+ **Statement 2: (Super) Long-Horizon Planning & Instruction Following**
115
+
116
+ You will build environments that require deep, multi-step reasoning with sparse or delayed rewards. After using these environments, the goal is to enable agents to decompose goals, track state over extended trajectories, and recover from early mistakes. The aim is to push beyond shallow next-token reasoning toward structured planning and durable internal representations. 
117
+
118
+ - **Expected Outcome:** an environment that can capture and improve LLM behaviour on challenging long horizon tasks that need long running sessions beyond context memory limits. 
119
+
120
+ - **Example Environments:** Research-planning simulators, large-scale codebase refactoring tasks, strategic resource management worlds, long-horizon logistics optimization, extremely complicated long-horizon instruction following (e.g., 300 instructions scattered around).
121
+
122
+ - **Partner Sub-Themes:**
123
+
124
+ - **Mercor:** Make an environment with capped/uncapped rewards where frontier model rewards scale with token output.
125
+
126
+ - **Scale AI:** Environments for long horizon workflows for non-code use cases within a business setting: focusing on either Sales, Project management, or HR & IT.
127
+
128
+ **Statement 3: World Modeling**
129
+
130
+ - **Statement 3.1: Professional Tasks:** Here you will develop environments that require real interaction with tools, APIs, or dynamic systems where the model is expected to do real hard work instead of exploiting short-cuts to arrive at the desired outcome. Learning from these environments will enable agents to maintain consistent internal state, update beliefs based on outcomes, and orchestrate multi-step workflows. The goal is to strengthen causal reasoning and persistent world models.
131
+
132
+ - **Expected Outcome:** an environment capturing nuances of a defined partially observable world and improve LLM interaction with it
133
+
134
+ - **Example Environments:** Dynamic browser/API ecosystems, enterprise applications, scientific workflow loops (papers → code → experiments), economic simulations with feedback, tool-discovery benchmarks.
135
+
136
+ - **Partner Sub-Theme:**
137
+
138
+ - **Scaler AI Labs:** Multi-App RL Environment for Enterprise Workflows: Create RL environments to demonstrate complex workflows, business rule nuances etc in a large enterprise
139
+
140
+ - **Statement 3.2: Personalized Tasks:** Here we will develop an environment that offers real personalized task handling, imagine replying to personal messages or handling dinner conflicts due to work conflicts, replying to tough emails. Think any personal assistant tasks.
141
+
142
+ - **Expected Outcome:** An environment that gives the model a realistic simulation of handling personal tasks, conflicts and managing them as delegations
143
+
144
+ - **Example Environments:** Executive Assistant Meeting Planner, Dinner and drive planning, email and message replying, etc
145
+
146
+ - **Partner Sub-Theme:**
147
+
148
+ - **Patronus AI:** Consumer Workflows with Schema Drift: Multi-step consumer workflow environments where the underlying data schemas, API contracts, and t&cs/policies/rules change.
149
+
150
+ **Statement 4: Self-Improvement**
151
+
152
+ The focus here is to create environments where agents can learn to generate new challenges, escalate difficulty, and improve through self-play or adaptive curricula. Rather than optimizing fixed tasks, the goal is for agents to learn to drive their own capability growth. The objective is recursive skill amplification.
153
+
154
+ - **Expected Outcome:** an environment for improving self-play of a LLM over a defined set of tasks
155
+
156
+ - **Example Environments:** Self-play negotiation arenas, auto-generated math/proof tasks, evolving coding competitions, adaptive RL curricula.
157
+
158
+ - **Partner Sub-Theme:**
159
+
160
+ - **Snorkel AI:** Simulated Experts-in-the-Loop: Environment that simulates interactions with real subject-matter experts, with changing requirements / preferences.
161
+
162
+ **Statement 5: Wild Card - Impress Us!**
163
+
164
+ We do not want to limit your focus if your idea doesn’t fit the boxes above, we want and WILL reward out of box tasks, please be creative but remember to add submissions that meaningfully add value to LLM training on a certain task.
165
+
166
+ More details about each theme can be found here:
167
+
168
+ [linkEmbed]
169
+
170
+ ## **7. CV Hackathon Winners**
171
+
172
+ [linkEmbed]
173
+
174
+ ## **8. OpenEnv Provided Resources**
175
+
176
+ **Please read through the entire slideshow here. This includes:**
177
+
178
+ - OpenEnv Fundamentals, Architecture
179
+ - Local Dev, Docker, and HF Spaces Deployment
180
+ - OpenEnv in Practice
181
+ - Training (TRL & Unsloth)
182
+ - How-to-Access-Infrastructure (including GPU Request Form)
183
+
184
+ [linkEmbed]
185
+
186
+ ## **9. Partner Provided Resources**
187
+
188
+ - **Unsloth AI Resources**
189
+ - RL notebooks: <https://unsloth.ai/docs/get-started/unsloth-notebooks#grpo-reasoning-rl>
190
+ - All notebooks: <https://unsloth.ai/docs/get-started/unsloth-notebooks>
191
+ - GitHub notebook index: <https://github.com/unslothai/notebooks/tree/main/nb>
192
+ - H100 / OpenEnv recommendation: use the BF16 gpt-oss 20B OpenEnv notebook for faster H100 runs: <https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/OpenEnv_gpt_oss_(20B)_Reinforcement_Learning_2048_Game_BF16.ipynb>
193
+ - For that notebook, reduce `max_steps` to `300` to make the process faster.
194
+ - If GRPO is too slow, prefer smaller-model notebooks with `fast_inference = True`, for example:
195
+ - <https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen3_(4B)-GRPO.ipynb>
196
+ - <https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Advanced_Llama3_2_(3B)_GRPO_LoRA.ipynb>
197
+ - You will need to edit those notebooks to include OpenEnv calls.
198
+ - If vLLM GRPO runs fail, try using a fresh virtualenv:
199
+
200
+ ```bash
201
+ python -m venv unsloth_env
202
+ source unsloth_env/bin/activate
203
+ pip install --upgrade pip && pip install uv
204
+ uv pip install unsloth vllm --torch-backend=auto
205
+ ```
206
+
207
+ - If Unsloth is already installed, update it for the latest GRPO bugfixes:
208
+
209
+ ```bash
210
+ pip install --upgrade --no-cache-dir --no-deps unsloth unsloth_zoo
211
+ ```
212
+
213
+ - **Mercor Resources**
214
+ - Dataset: <https://huggingface.co/datasets/mercor/apex-agents>
215
+ - Archipelago repo to run the eval: <https://github.com/Mercor-Intelligence/archipelago>
216
+ - APEX-Agents paper: <https://arxiv.org/abs/2601.14242>
217
+ - **Hugging Face Resources**
218
+ - **$30** in Compute and Inference Credits
219
+ - To claim your credits, set up a HF account here: <https://huggingface.co/join>
220
+ - Then, follow this link: <https://huggingface.co/openenv-community>
221
+ - You will be granted **$30** of compute and inference credits!
222
+ - **Northflank Resources**
223
+ - Each team gets an H100
224
+ - Northflank instructions
225
+
226
+ [linkEmbed]
227
+ - Join the NorthFlank discord channel for any questions
228
+ - Please fill out this form:
229
+
230
+ [linkEmbed]
231
+
232
+
233
+ - **Cursor Resources**
234
+ - **$50** in Cursor Credits, **apply below**
235
+
236
+ [linkEmbed]
237
+
238
+ ## **10. Judging & Submissions**
239
+
240
+ Judges will be taking place on **Sunday, March 8**. These judges are evaluating your **technical demos** in the following categories. *Show us what you have built* to solve our problem statements. Please **do not** show us a presentation. We'll be checking to ensure your project was built **entirely during the event**; no previous work is allowed. 
241
+
242
+ **|** **Teams should submit [here](https://cerebralvalley.ai/e/openenv-hackathon-sf/hackathon/submit) when they have completed hacking.** In the submission form, you will have to upload a **one minute** demo video on YouTube talking about your submission. You must also show a minimal training script for your environment using Unsloth or HF TRL in Colab.
243
+
244
+ **Please ensure your project uses** use OpenEnv (stable release 0.2.1) deployed on HF spaces.
245
+
246
+ [linkEmbed]
247
+
248
+ **Judging Criteria**
249
+
250
+ - **Environment Innovation (40%) -** Is the environment novel, creative, or challenging? Does it meaningfully test the agent’s behavior?
251
+ - **Storytelling (30%) -** Does the team clearly explain the problem, environment, and agent behavior? Is the demo engaging and easy to follow?
252
+ - **Training Script Showing Improvement in Rewards (20%) -** Does the demo provide observable evidence of training progress (reward curves, metrics, or before/after behavior)? 
253
+ - **Reward and Training Pipeline Setup (10%) -** Is the reward logic coherent, and does the pipeline produce meaningful improvement in the agent’s inference (how it acts in the environment)?
254
+
255
+ **Judging Process**
256
+
257
+ **|** Judging proceeds in two rounds:
258
+
259
+ - Hackers will be assigned groups of judges; \~3 minutes to pitch followed by 1-2 minutes of Q/A
260
+
261
+ - The top **six** teams in ranking will get to demo on stage to a panel of judges; \~3 minutes to pitch followed by 2-3 minutes for Q/A.
262
+
263
+ ## **11. Prizes**
264
+
265
+ - **1st Place:** $15,000 USD Cash
266
+
267
+ - **2nd Place:** $9,000 USD Cash
268
+
269
+ - **3rd Place:** $6,000 USD Cash
270
+
271
+ ## **❓If you have any questions, please email [wania@cerebralvalley.ai](mailto:wania@cerebralvalley.ai) or message on Discord.**
models.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from freeciv_env.models import *
notes.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ multi-agent (track 1) - freeciv
2
+ long context (track 2) - freeciv
3
+ self improving? (track 4) - mechinterp
openenv.yaml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ name: freeciv_env
2
+ description: OpenEnv wrapper around freeciv-bot for long-horizon strategy play.
3
+ version: 0.1.0
4
+ entrypoint: freeciv_env.server.app:app
pyproject.toml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "freeciv-env"
3
+ version = "0.1.0"
4
+ description = "OpenEnv environment for Freeciv via freeciv-bot"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "openenv-core[core]==0.2.1",
9
+ "freecivbot @ git+https://github.com/chris1869/freeciv-bot.git",
10
+ ]
11
+
12
+ [project.scripts]
13
+ server = "server.app:main"
14
+
15
+ [project.optional-dependencies]
16
+ dev = [
17
+ "pytest>=8.4.1",
18
+ "requests>=2.32.5",
19
+ "uvicorn>=0.35.0",
20
+ ]
21
+ train = [
22
+ "accelerate>=1.10.0",
23
+ "bitsandbytes>=0.47.0",
24
+ "datasets>=4.0.0",
25
+ "trl>=0.24.0",
26
+ "unsloth>=2026.3.4",
27
+ ]
28
+
29
+ [build-system]
30
+ requires = ["setuptools>=80", "wheel"]
31
+ build-backend = "setuptools.build_meta"
32
+
33
+ [tool.setuptools]
34
+ packages = ["freeciv_env", "freeciv_env.server", "server"]
35
+
36
+ [tool.pytest.ini_options]
37
+ pythonpath = ["."]
38
+ testpaths = ["tests"]
39
+ markers = [
40
+ "integration: requires a live freeciv-web runtime",
41
+ ]
qwen35_live_long_trainer_state.json ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "best_global_step": null,
3
+ "best_metric": null,
4
+ "best_model_checkpoint": null,
5
+ "epoch": 0.625,
6
+ "eval_steps": 500,
7
+ "global_step": 10,
8
+ "is_hyper_param_search": false,
9
+ "is_local_process_zero": true,
10
+ "is_world_process_zero": true,
11
+ "log_history": [
12
+ {
13
+ "clip_ratio/high_max": 0.0,
14
+ "clip_ratio/high_mean": 0.0,
15
+ "clip_ratio/low_mean": 0.0,
16
+ "clip_ratio/low_min": 0.0,
17
+ "clip_ratio/region_mean": 0.0,
18
+ "completion_length": 2.375,
19
+ "completions/clipped_ratio": 0.0,
20
+ "completions/max_length": 3.0,
21
+ "completions/max_terminated_length": 3.0,
22
+ "completions/mean_length": 2.375,
23
+ "completions/mean_terminated_length": 2.375,
24
+ "completions/min_length": 2.0,
25
+ "completions/min_terminated_length": 2.0,
26
+ "epoch": 0.0625,
27
+ "frac_reward_zero_std": 0.5,
28
+ "grad_norm": 5.300995349884033,
29
+ "kl": 0.0,
30
+ "learning_rate": 0.0,
31
+ "loss": 0.01562187448143959,
32
+ "num_tokens": 8343.0,
33
+ "reward": 0.125,
34
+ "reward_std": 0.25,
35
+ "rewards/reward_from_oracle/mean": 0.125,
36
+ "rewards/reward_from_oracle/std": 0.3535533845424652,
37
+ "step": 1
38
+ },
39
+ {
40
+ "clip_ratio/high_max": 0.0,
41
+ "clip_ratio/high_mean": 0.0,
42
+ "clip_ratio/low_mean": 0.0,
43
+ "clip_ratio/low_min": 0.0,
44
+ "clip_ratio/region_mean": 0.0,
45
+ "completion_length": 2.375,
46
+ "completions/clipped_ratio": 0.0,
47
+ "completions/max_length": 3.0,
48
+ "completions/max_terminated_length": 3.0,
49
+ "completions/mean_length": 2.375,
50
+ "completions/mean_terminated_length": 2.375,
51
+ "completions/min_length": 2.0,
52
+ "completions/min_terminated_length": 2.0,
53
+ "epoch": 0.125,
54
+ "frac_reward_zero_std": 0.0,
55
+ "grad_norm": 9.095938682556152,
56
+ "kl": 0.0,
57
+ "learning_rate": 5e-06,
58
+ "loss": 0.029151180759072304,
59
+ "num_tokens": 16682.0,
60
+ "reward": 0.375,
61
+ "reward_std": 0.5386751294136047,
62
+ "rewards/reward_from_oracle/mean": 0.375,
63
+ "rewards/reward_from_oracle/std": 0.5175492167472839,
64
+ "step": 2
65
+ },
66
+ {
67
+ "clip_ratio/high_max": 0.0,
68
+ "clip_ratio/high_mean": 0.0,
69
+ "clip_ratio/low_mean": 0.0,
70
+ "clip_ratio/low_min": 0.0,
71
+ "clip_ratio/region_mean": 0.0,
72
+ "completion_length": 2.375,
73
+ "completions/clipped_ratio": 0.0,
74
+ "completions/max_length": 3.0,
75
+ "completions/max_terminated_length": 3.0,
76
+ "completions/mean_length": 2.375,
77
+ "completions/mean_terminated_length": 2.375,
78
+ "completions/min_length": 2.0,
79
+ "completions/min_terminated_length": 2.0,
80
+ "epoch": 0.1875,
81
+ "frac_reward_zero_std": 0.0,
82
+ "grad_norm": 8.75147533416748,
83
+ "kl": 0.0,
84
+ "learning_rate": 4.849231551964771e-06,
85
+ "loss": 0.023432811722159386,
86
+ "num_tokens": 25025.0,
87
+ "reward": 0.25,
88
+ "reward_std": 0.5,
89
+ "rewards/reward_from_oracle/mean": 0.25,
90
+ "rewards/reward_from_oracle/std": 0.4629100561141968,
91
+ "step": 3
92
+ },
93
+ {
94
+ "clip_ratio/high_max": 0.0,
95
+ "clip_ratio/high_mean": 0.0,
96
+ "clip_ratio/low_mean": 0.0,
97
+ "clip_ratio/low_min": 0.0,
98
+ "clip_ratio/region_mean": 0.0,
99
+ "completion_length": 2.125,
100
+ "completions/clipped_ratio": 0.0,
101
+ "completions/max_length": 3.0,
102
+ "completions/max_terminated_length": 3.0,
103
+ "completions/mean_length": 2.125,
104
+ "completions/mean_terminated_length": 2.125,
105
+ "completions/min_length": 2.0,
106
+ "completions/min_terminated_length": 2.0,
107
+ "epoch": 0.25,
108
+ "frac_reward_zero_std": 0.0,
109
+ "grad_norm": 10.478106498718262,
110
+ "kl": 0.0,
111
+ "learning_rate": 4.415111107797445e-06,
112
+ "loss": 0.013529304414987564,
113
+ "num_tokens": 33362.0,
114
+ "reward": 0.5,
115
+ "reward_std": 0.5773502588272095,
116
+ "rewards/reward_from_oracle/mean": 0.5,
117
+ "rewards/reward_from_oracle/std": 0.5345224738121033,
118
+ "step": 4
119
+ },
120
+ {
121
+ "clip_ratio/high_max": 0.0,
122
+ "clip_ratio/high_mean": 0.0,
123
+ "clip_ratio/low_mean": 0.0,
124
+ "clip_ratio/low_min": 0.0,
125
+ "clip_ratio/region_mean": 0.0,
126
+ "completion_length": 2.125,
127
+ "completions/clipped_ratio": 0.0,
128
+ "completions/max_length": 3.0,
129
+ "completions/max_terminated_length": 3.0,
130
+ "completions/mean_length": 2.125,
131
+ "completions/mean_terminated_length": 2.125,
132
+ "completions/min_length": 2.0,
133
+ "completions/min_terminated_length": 2.0,
134
+ "epoch": 0.3125,
135
+ "frac_reward_zero_std": 0.0,
136
+ "grad_norm": 8.125267028808594,
137
+ "kl": 0.0,
138
+ "learning_rate": 3.7500000000000005e-06,
139
+ "loss": 0.013529304414987564,
140
+ "num_tokens": 41707.0,
141
+ "reward": 0.625,
142
+ "reward_std": 0.5386751294136047,
143
+ "rewards/reward_from_oracle/mean": 0.625,
144
+ "rewards/reward_from_oracle/std": 0.5175492167472839,
145
+ "step": 5
146
+ },
147
+ {
148
+ "clip_ratio/high_max": 0.0,
149
+ "clip_ratio/high_mean": 0.0,
150
+ "clip_ratio/low_mean": 0.0,
151
+ "clip_ratio/low_min": 0.0,
152
+ "clip_ratio/region_mean": 0.0,
153
+ "completion_length": 2.0,
154
+ "completions/clipped_ratio": 0.0,
155
+ "completions/max_length": 2.0,
156
+ "completions/max_terminated_length": 2.0,
157
+ "completions/mean_length": 2.0,
158
+ "completions/mean_terminated_length": 2.0,
159
+ "completions/min_length": 2.0,
160
+ "completions/min_terminated_length": 2.0,
161
+ "epoch": 0.375,
162
+ "frac_reward_zero_std": 0.5,
163
+ "grad_norm": 3.183867931365967,
164
+ "kl": 0.0,
165
+ "learning_rate": 2.9341204441673267e-06,
166
+ "loss": 0.0,
167
+ "num_tokens": 50047.0,
168
+ "reward": 0.875,
169
+ "reward_std": 0.25,
170
+ "rewards/reward_from_oracle/mean": 0.875,
171
+ "rewards/reward_from_oracle/std": 0.3535533845424652,
172
+ "step": 6
173
+ },
174
+ {
175
+ "clip_ratio/high_max": 0.0,
176
+ "clip_ratio/high_mean": 0.0,
177
+ "clip_ratio/low_mean": 0.0,
178
+ "clip_ratio/low_min": 0.0,
179
+ "clip_ratio/region_mean": 0.0,
180
+ "completion_length": 2.0,
181
+ "completions/clipped_ratio": 0.0,
182
+ "completions/max_length": 2.0,
183
+ "completions/max_terminated_length": 2.0,
184
+ "completions/mean_length": 2.0,
185
+ "completions/mean_terminated_length": 2.0,
186
+ "completions/min_length": 2.0,
187
+ "completions/min_terminated_length": 2.0,
188
+ "epoch": 0.4375,
189
+ "frac_reward_zero_std": 0.0,
190
+ "grad_norm": 7.007436275482178,
191
+ "kl": 0.0,
192
+ "learning_rate": 2.0658795558326745e-06,
193
+ "loss": 0.0,
194
+ "num_tokens": 58391.0,
195
+ "reward": 0.75,
196
+ "reward_std": 0.5,
197
+ "rewards/reward_from_oracle/mean": 0.75,
198
+ "rewards/reward_from_oracle/std": 0.4629100561141968,
199
+ "step": 7
200
+ },
201
+ {
202
+ "clip_ratio/high_max": 0.0,
203
+ "clip_ratio/high_mean": 0.0,
204
+ "clip_ratio/low_mean": 0.0,
205
+ "clip_ratio/low_min": 0.0,
206
+ "clip_ratio/region_mean": 0.0,
207
+ "completion_length": 2.0,
208
+ "completions/clipped_ratio": 0.0,
209
+ "completions/max_length": 2.0,
210
+ "completions/max_terminated_length": 2.0,
211
+ "completions/mean_length": 2.0,
212
+ "completions/mean_terminated_length": 2.0,
213
+ "completions/min_length": 2.0,
214
+ "completions/min_terminated_length": 2.0,
215
+ "epoch": 0.5,
216
+ "frac_reward_zero_std": 0.5,
217
+ "grad_norm": 3.759775161743164,
218
+ "kl": 0.0,
219
+ "learning_rate": 1.2500000000000007e-06,
220
+ "loss": 1.862645149230957e-09,
221
+ "num_tokens": 66727.0,
222
+ "reward": 0.875,
223
+ "reward_std": 0.25,
224
+ "rewards/reward_from_oracle/mean": 0.875,
225
+ "rewards/reward_from_oracle/std": 0.3535533845424652,
226
+ "step": 8
227
+ },
228
+ {
229
+ "clip_ratio/high_max": 0.0,
230
+ "clip_ratio/high_mean": 0.0,
231
+ "clip_ratio/low_mean": 0.0,
232
+ "clip_ratio/low_min": 0.0,
233
+ "clip_ratio/region_mean": 0.0,
234
+ "completion_length": 2.0,
235
+ "completions/clipped_ratio": 0.0,
236
+ "completions/max_length": 2.0,
237
+ "completions/max_terminated_length": 2.0,
238
+ "completions/mean_length": 2.0,
239
+ "completions/mean_terminated_length": 2.0,
240
+ "completions/min_length": 2.0,
241
+ "completions/min_terminated_length": 2.0,
242
+ "epoch": 0.5625,
243
+ "frac_reward_zero_std": 0.0,
244
+ "grad_norm": 7.748785495758057,
245
+ "kl": 0.0,
246
+ "learning_rate": 5.848888922025553e-07,
247
+ "loss": 1.862645149230957e-09,
248
+ "num_tokens": 75063.0,
249
+ "reward": 0.75,
250
+ "reward_std": 0.5,
251
+ "rewards/reward_from_oracle/mean": 0.75,
252
+ "rewards/reward_from_oracle/std": 0.4629100561141968,
253
+ "step": 9
254
+ },
255
+ {
256
+ "clip_ratio/high_max": 0.0,
257
+ "clip_ratio/high_mean": 0.0,
258
+ "clip_ratio/low_mean": 0.0,
259
+ "clip_ratio/low_min": 0.0,
260
+ "clip_ratio/region_mean": 0.0,
261
+ "completion_length": 2.0,
262
+ "completions/clipped_ratio": 0.0,
263
+ "completions/max_length": 2.0,
264
+ "completions/max_terminated_length": 2.0,
265
+ "completions/mean_length": 2.0,
266
+ "completions/mean_terminated_length": 2.0,
267
+ "completions/min_length": 2.0,
268
+ "completions/min_terminated_length": 2.0,
269
+ "epoch": 0.625,
270
+ "frac_reward_zero_std": 1.0,
271
+ "grad_norm": 0.0,
272
+ "kl": 0.0,
273
+ "learning_rate": 1.507684480352292e-07,
274
+ "loss": 0.0,
275
+ "num_tokens": 83403.0,
276
+ "reward": 1.0,
277
+ "reward_std": 0.0,
278
+ "rewards/reward_from_oracle/mean": 1.0,
279
+ "rewards/reward_from_oracle/std": 0.0,
280
+ "step": 10
281
+ }
282
+ ],
283
+ "logging_steps": 1,
284
+ "max_steps": 10,
285
+ "num_input_tokens_seen": 83403,
286
+ "num_train_epochs": 1,
287
+ "save_steps": 10,
288
+ "stateful_callbacks": {
289
+ "TrainerControl": {
290
+ "args": {
291
+ "should_epoch_stop": false,
292
+ "should_evaluate": false,
293
+ "should_log": false,
294
+ "should_save": true,
295
+ "should_training_stop": true
296
+ },
297
+ "attributes": {}
298
+ }
299
+ },
300
+ "total_flos": 0.0,
301
+ "train_batch_size": 8,
302
+ "trial_name": null,
303
+ "trial_params": null
304
+ }
scripts/train_grpo_fast.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import os
5
+
6
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
7
+ os.environ.setdefault("UNSLOTH_RETURN_LOGITS", "1")
8
+ os.environ.setdefault("UNSLOTH_DISABLE_AUTO_UPDATES", "1")
9
+
10
+ from unsloth import FastLanguageModel
11
+ from datasets import Dataset
12
+ from trl import GRPOConfig, GRPOTrainer
13
+
14
+ from freeciv_env.adapter import prepare_observation
15
+ from freeciv_env.grpo import SYSTEM_PROMPT, build_turn_prompt, oracle_action_index, reward_from_oracle
16
+ from freeciv_env.runtime import LiveFreecivSession
17
+
18
+
19
+ def parse_args():
20
+ parser = argparse.ArgumentParser()
21
+ parser.add_argument("--env-url", default="http://127.0.0.1")
22
+ parser.add_argument("--model-id", default="Qwen/Qwen3.5-0.8B")
23
+ parser.add_argument("--dataset-size", type=int, default=512)
24
+ parser.add_argument("--max-steps", type=int, default=50)
25
+ parser.add_argument("--batch-size", type=int, default=16)
26
+ parser.add_argument("--num-generations", type=int, default=4)
27
+ parser.add_argument("--episode-horizon", type=int, default=4)
28
+ parser.add_argument("--max-prompt-length", type=int, default=768)
29
+ parser.add_argument("--max-completion-length", type=int, default=8)
30
+ parser.add_argument("--learning-rate", type=float, default=5e-6)
31
+ parser.add_argument("--lora-rank", type=int, default=16)
32
+ parser.add_argument("--output-dir", default="outputs/qwen35_08b_grpo")
33
+ parser.add_argument("--save-steps", type=int, default=50)
34
+ return parser.parse_args()
35
+
36
+
37
+
38
+ def collect_dataset(env_url: str, dataset_size: int, episode_horizon: int) -> Dataset:
39
+ rows = {"prompt": [], "best_index": []}
40
+ while len(rows["prompt"]) < dataset_size:
41
+ session = LiveFreecivSession(base_url=env_url, turn_timeout_s=120)
42
+ try:
43
+ snapshot = session.reset()
44
+ for turn_index in range(episode_horizon):
45
+ observation = prepare_observation(
46
+ snapshot,
47
+ reward=0.0,
48
+ done=False,
49
+ status="running",
50
+ ).observation
51
+ best_index = oracle_action_index(observation.legal_actions)
52
+ rows["prompt"].append(build_turn_prompt(observation))
53
+ rows["best_index"].append(best_index)
54
+ if len(rows["prompt"]) >= dataset_size or turn_index + 1 >= episode_horizon:
55
+ break
56
+ snapshot = session.end_turn()
57
+ finally:
58
+ session.close()
59
+ return Dataset.from_dict(rows)
60
+
61
+
62
+
63
+ def load_model(model_id: str, max_seq_length: int, lora_rank: int):
64
+ model, tokenizer = FastLanguageModel.from_pretrained(
65
+ model_name=model_id,
66
+ max_seq_length=max_seq_length,
67
+ load_in_4bit=False,
68
+ load_in_16bit=True,
69
+ full_finetuning=False,
70
+ fast_inference=False,
71
+ )
72
+ model = FastLanguageModel.get_peft_model(
73
+ model,
74
+ r=lora_rank,
75
+ target_modules=[
76
+ "q_proj",
77
+ "k_proj",
78
+ "v_proj",
79
+ "o_proj",
80
+ "gate_proj",
81
+ "up_proj",
82
+ "down_proj",
83
+ ],
84
+ lora_alpha=lora_rank * 2,
85
+ lora_dropout=0,
86
+ bias="none",
87
+ use_gradient_checkpointing=False,
88
+ random_state=3407,
89
+ max_seq_length=max_seq_length,
90
+ )
91
+ return model, tokenizer
92
+
93
+
94
+
95
+ def apply_chat_template(dataset: Dataset, tokenizer) -> Dataset:
96
+ def format_row(row):
97
+ messages = [
98
+ {"role": "system", "content": SYSTEM_PROMPT},
99
+ {"role": "user", "content": row["prompt"]},
100
+ ]
101
+ return {
102
+ "prompt": tokenizer.apply_chat_template(
103
+ messages,
104
+ tokenize=False,
105
+ add_generation_prompt=True,
106
+ enable_thinking=False,
107
+ )
108
+ }
109
+
110
+ return dataset.map(format_row)
111
+
112
+
113
+
114
+ def main() -> None:
115
+ args = parse_args()
116
+ max_seq_length = args.max_prompt_length + args.max_completion_length
117
+ dataset = collect_dataset(args.env_url, args.dataset_size, args.episode_horizon)
118
+ model, tokenizer = load_model(args.model_id, max_seq_length, args.lora_rank)
119
+ dataset = apply_chat_template(dataset, tokenizer)
120
+
121
+ training_args = GRPOConfig(
122
+ learning_rate=args.learning_rate,
123
+ weight_decay=0.01,
124
+ warmup_ratio=0.05,
125
+ lr_scheduler_type="cosine",
126
+ optim="adamw_torch_fused",
127
+ logging_steps=1,
128
+ log_completions=False,
129
+ per_device_train_batch_size=args.batch_size,
130
+ gradient_accumulation_steps=1,
131
+ num_generations=args.num_generations,
132
+ max_prompt_length=args.max_prompt_length,
133
+ max_completion_length=args.max_completion_length,
134
+ max_steps=args.max_steps,
135
+ save_steps=args.save_steps,
136
+ max_grad_norm=0.3,
137
+ bf16=True,
138
+ report_to="none",
139
+ beta=0.0,
140
+ loss_type="dr_grpo",
141
+ temperature=0.7,
142
+ top_p=0.8,
143
+ top_k=20,
144
+ output_dir=args.output_dir,
145
+ )
146
+
147
+ trainer = GRPOTrainer(
148
+ model=model,
149
+ processing_class=tokenizer,
150
+ reward_funcs=reward_from_oracle,
151
+ train_dataset=dataset,
152
+ args=training_args,
153
+ )
154
+ trainer.train()
155
+ model.save_pretrained(f"{args.output_dir}/lora")
156
+ tokenizer.save_pretrained(f"{args.output_dir}/lora")
157
+
158
+
159
+ if __name__ == "__main__":
160
+ main()
server/__init__.py ADDED
File without changes
server/app.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from freeciv_env.server.app import app as app
2
+ from freeciv_env.server.app import main as _main
3
+
4
+
5
+ def main() -> None:
6
+ _main()
7
+
8
+
9
+ if __name__ == "__main__":
10
+ main()
tests/fake_server.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from freeciv_env.server.app import create_freeciv_app
2
+ from tests.fakes import FakeFreecivSession
3
+
4
+
5
+ app = create_freeciv_app(session_factory=FakeFreecivSession, max_turns=5)
tests/fakes.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+
5
+ from freeciv_env.adapter import ActionRef, RawSnapshot
6
+
7
+
8
+ class FakeFreecivSession:
9
+ def __init__(self):
10
+ self.current = None
11
+
12
+ def reset(self, seed: int | None = None) -> RawSnapshot:
13
+ del seed
14
+ self.current = _initial_snapshot()
15
+ return deepcopy(self.current)
16
+
17
+ def apply_action(self, action_ref: ActionRef) -> RawSnapshot:
18
+ if self.current is None:
19
+ raise RuntimeError("session was not reset")
20
+ raw_action_key = action_ref.raw_action_key
21
+ if raw_action_key == "goto_0":
22
+ self.current = _moved_snapshot(self.current.turn)
23
+ elif raw_action_key == "build":
24
+ self.current = _built_snapshot(self.current.turn)
25
+ elif raw_action_key == "change_unit_prod_Settlers_0":
26
+ self.current = _production_snapshot(self.current.turn)
27
+ elif raw_action_key == "research_tech_Pottery_63":
28
+ self.current = _research_snapshot(self.current.turn)
29
+ else:
30
+ raise ValueError(f"unsupported fake action: {raw_action_key}")
31
+ return deepcopy(self.current)
32
+
33
+ def end_turn(self) -> RawSnapshot:
34
+ if self.current is None:
35
+ raise RuntimeError("session was not reset")
36
+ self.current = _advanced_turn_snapshot(self.current)
37
+ return deepcopy(self.current)
38
+
39
+ def close(self) -> None:
40
+ self.current = None
41
+
42
+
43
+ def _base_state(*, score: float, techs: int, status: list[list[int]], cities: dict, units: dict) -> dict:
44
+ return {
45
+ "player": {
46
+ "my_score": score,
47
+ "my_gold": 20,
48
+ "my_science": 60,
49
+ "my_techs_researched": techs,
50
+ "my_is_alive": True,
51
+ },
52
+ "map": {"status": status},
53
+ "city": cities,
54
+ "unit": units,
55
+ "tech": {},
56
+ }
57
+
58
+
59
+ def _base_actions(*, can_build: bool, can_move: bool, include_city_prod: bool, include_research: bool) -> dict:
60
+ unit_actions = {}
61
+ if can_move:
62
+ unit_actions["goto_0"] = True
63
+ if can_build:
64
+ unit_actions["build"] = True
65
+ city_actions = {"change_unit_prod_Settlers_0": True} if include_city_prod else {}
66
+ tech_actions = {"research_tech_Pottery_63": True} if include_research else {}
67
+ return {
68
+ "unit": {"201": unit_actions},
69
+ "city": {"101": city_actions},
70
+ "tech": {"cur_player": tech_actions},
71
+ }
72
+
73
+
74
+ def _initial_snapshot(turn: int = 1) -> RawSnapshot:
75
+ return RawSnapshot(
76
+ turn=turn,
77
+ state=_base_state(
78
+ score=10.0,
79
+ techs=0,
80
+ status=[[2, 2, 1], [1, 0, 0]],
81
+ cities={
82
+ "101": {
83
+ "id": 101,
84
+ "size": 1,
85
+ "prod_food": 4,
86
+ "prod_shield": 2,
87
+ "prod_trade": 1,
88
+ "surplus_food": 2,
89
+ "surplus_shield": 1,
90
+ "surplus_trade": 1,
91
+ "production_kind": 6,
92
+ "production_value": 0,
93
+ "turns_to_prod_complete": 2.0,
94
+ }
95
+ },
96
+ units={
97
+ "201": {
98
+ "health": 10,
99
+ "moves_left": 1,
100
+ "home_city": 101,
101
+ "type_rule_name": "Settlers",
102
+ "veteran": 0,
103
+ }
104
+ },
105
+ ),
106
+ actions=_base_actions(can_build=True, can_move=True, include_city_prod=True, include_research=True),
107
+ )
108
+
109
+
110
+ def _moved_snapshot(turn: int) -> RawSnapshot:
111
+ return RawSnapshot(
112
+ turn=turn,
113
+ state=_base_state(
114
+ score=11.0,
115
+ techs=0,
116
+ status=[[2, 2, 2], [1, 1, 0]],
117
+ cities={
118
+ "101": {
119
+ "id": 101,
120
+ "size": 1,
121
+ "prod_food": 4,
122
+ "prod_shield": 2,
123
+ "prod_trade": 1,
124
+ "surplus_food": 2,
125
+ "surplus_shield": 1,
126
+ "surplus_trade": 1,
127
+ "production_kind": 6,
128
+ "production_value": 0,
129
+ "turns_to_prod_complete": 2.0,
130
+ }
131
+ },
132
+ units={
133
+ "201": {
134
+ "health": 10,
135
+ "moves_left": 0,
136
+ "home_city": 101,
137
+ "type_rule_name": "Settlers",
138
+ "veteran": 0,
139
+ }
140
+ },
141
+ ),
142
+ actions=_base_actions(can_build=False, can_move=False, include_city_prod=True, include_research=True),
143
+ )
144
+
145
+
146
+ def _built_snapshot(turn: int) -> RawSnapshot:
147
+ return RawSnapshot(
148
+ turn=turn,
149
+ state=_base_state(
150
+ score=14.0,
151
+ techs=0,
152
+ status=[[2, 2, 1], [1, 0, 0]],
153
+ cities={
154
+ "101": {
155
+ "id": 101,
156
+ "size": 1,
157
+ "prod_food": 4,
158
+ "prod_shield": 2,
159
+ "prod_trade": 1,
160
+ "surplus_food": 2,
161
+ "surplus_shield": 1,
162
+ "surplus_trade": 1,
163
+ "production_kind": 6,
164
+ "production_value": 0,
165
+ "turns_to_prod_complete": 2.0,
166
+ },
167
+ "102": {
168
+ "id": 102,
169
+ "size": 1,
170
+ "prod_food": 2,
171
+ "prod_shield": 1,
172
+ "prod_trade": 1,
173
+ "surplus_food": 1,
174
+ "surplus_shield": 1,
175
+ "surplus_trade": 1,
176
+ "production_kind": 6,
177
+ "production_value": 3,
178
+ "turns_to_prod_complete": 4.0,
179
+ },
180
+ },
181
+ units={},
182
+ ),
183
+ actions={
184
+ "unit": {},
185
+ "city": {
186
+ "101": {"change_unit_prod_Settlers_0": True},
187
+ "102": {"change_unit_prod_Settlers_0": True},
188
+ },
189
+ "tech": {"cur_player": {"research_tech_Pottery_63": True}},
190
+ },
191
+ )
192
+
193
+
194
+ def _production_snapshot(turn: int) -> RawSnapshot:
195
+ return RawSnapshot(
196
+ turn=turn,
197
+ state=_base_state(
198
+ score=10.0,
199
+ techs=0,
200
+ status=[[2, 2, 1], [1, 0, 0]],
201
+ cities={
202
+ "101": {
203
+ "id": 101,
204
+ "size": 1,
205
+ "prod_food": 4,
206
+ "prod_shield": 2,
207
+ "prod_trade": 1,
208
+ "surplus_food": 2,
209
+ "surplus_shield": 1,
210
+ "surplus_trade": 1,
211
+ "production_kind": 6,
212
+ "production_value": 0,
213
+ "turns_to_prod_complete": 1.0,
214
+ }
215
+ },
216
+ units={
217
+ "201": {
218
+ "health": 10,
219
+ "moves_left": 1,
220
+ "home_city": 101,
221
+ "type_rule_name": "Settlers",
222
+ "veteran": 0,
223
+ }
224
+ },
225
+ ),
226
+ actions=_base_actions(can_build=True, can_move=True, include_city_prod=True, include_research=True),
227
+ )
228
+
229
+
230
+ def _research_snapshot(turn: int) -> RawSnapshot:
231
+ return RawSnapshot(
232
+ turn=turn,
233
+ state=_base_state(
234
+ score=10.0,
235
+ techs=1,
236
+ status=[[2, 2, 1], [1, 0, 0]],
237
+ cities={
238
+ "101": {
239
+ "id": 101,
240
+ "size": 1,
241
+ "prod_food": 4,
242
+ "prod_shield": 2,
243
+ "prod_trade": 1,
244
+ "surplus_food": 2,
245
+ "surplus_shield": 1,
246
+ "surplus_trade": 1,
247
+ "production_kind": 6,
248
+ "production_value": 0,
249
+ "turns_to_prod_complete": 2.0,
250
+ }
251
+ },
252
+ units={
253
+ "201": {
254
+ "health": 10,
255
+ "moves_left": 1,
256
+ "home_city": 101,
257
+ "type_rule_name": "Settlers",
258
+ "veteran": 0,
259
+ }
260
+ },
261
+ ),
262
+ actions={
263
+ "unit": {"201": {"goto_0": True, "build": True}},
264
+ "city": {"101": {"change_unit_prod_Settlers_0": True}},
265
+ "tech": {"cur_player": {}},
266
+ },
267
+ )
268
+
269
+
270
+ def _advanced_turn_snapshot(current: RawSnapshot) -> RawSnapshot:
271
+ state = deepcopy(current.state)
272
+ state["player"]["my_score"] = float(state["player"].get("my_score", 0.0)) + 2.0
273
+ state["map"]["status"] = [[2, 2, 2], [2, 1, 0]]
274
+ if "201" in state.get("unit", {}):
275
+ state["unit"]["201"]["moves_left"] = 1
276
+ actions = _base_actions(can_build=True, can_move=True, include_city_prod=True, include_research=True)
277
+ return RawSnapshot(turn=current.turn + 1, state=state, actions=actions)
tests/fixtures/turn_actions.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "city": {
3
+ "141": {
4
+ "change_improve_prod_Barracks_3": true,
5
+ "change_improve_prod_City Walls_7": true,
6
+ "change_improve_prod_Coinage_67": true,
7
+ "change_improve_prod_Great Wall_47": true,
8
+ "change_improve_prod_Pyramids_60": true,
9
+ "change_unit_prod_Horsemen_15": true,
10
+ "change_unit_prod_Settlers_0": true,
11
+ "change_unit_prod_Warriors_3": true
12
+ },
13
+ "158": {
14
+ "change_improve_prod_Barracks_3": true,
15
+ "change_improve_prod_City Walls_7": true,
16
+ "change_improve_prod_Coinage_67": true,
17
+ "change_improve_prod_Great Wall_47": true,
18
+ "change_improve_prod_Palace_21": true,
19
+ "change_improve_prod_Pyramids_60": true,
20
+ "change_unit_prod_Horsemen_15": true,
21
+ "change_unit_prod_Settlers_0": true,
22
+ "change_unit_prod_Warriors_3": true
23
+ }
24
+ },
25
+ "tech": {
26
+ "cur_player": {
27
+ "research_tech_Alphabet_2": true,
28
+ "research_tech_Bronze Working_9": true,
29
+ "research_tech_Ceremonial Burial_10": true,
30
+ "research_tech_Pottery_63": true,
31
+ "research_tech_The Wheel_81": true,
32
+ "research_tech_Warrior Code_86": true
33
+ }
34
+ },
35
+ "unit": {
36
+ "110": {
37
+ "autosettlers": true,
38
+ "disband": true,
39
+ "goto_0": true,
40
+ "goto_1": true,
41
+ "goto_2": true,
42
+ "goto_3": true,
43
+ "goto_4": true,
44
+ "goto_5": true,
45
+ "goto_6": true,
46
+ "goto_7": true
47
+ },
48
+ "111": {
49
+ "autosettlers": true,
50
+ "disband": true,
51
+ "goto_0": true,
52
+ "goto_1": true,
53
+ "goto_2": true,
54
+ "goto_3": true,
55
+ "goto_4": true,
56
+ "goto_5": true,
57
+ "goto_6": true,
58
+ "goto_7": true
59
+ },
60
+ "112": {
61
+ "disband": true,
62
+ "explore": true,
63
+ "fortify": true,
64
+ "goto_0": true,
65
+ "goto_1": true,
66
+ "goto_2": true,
67
+ "goto_3": true,
68
+ "goto_4": true,
69
+ "goto_5": true,
70
+ "goto_6": true,
71
+ "goto_7": true
72
+ }
73
+ }
74
+ }
tests/fixtures/turn_state.json ADDED
@@ -0,0 +1,5899 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "city": {
3
+ "141": {
4
+ "id": 141,
5
+ "prod_food": 5,
6
+ "prod_shield": 10,
7
+ "prod_trade": 2,
8
+ "production_kind": 6,
9
+ "production_value": 3,
10
+ "size": 2,
11
+ "surplus_food": 1,
12
+ "surplus_shield": 6,
13
+ "surplus_trade": 2,
14
+ "turns_to_prod_complete": 1.0
15
+ },
16
+ "158": {
17
+ "id": 158,
18
+ "prod_food": 4,
19
+ "prod_shield": 3,
20
+ "prod_trade": 1,
21
+ "production_kind": 6,
22
+ "production_value": 3,
23
+ "size": 1,
24
+ "surplus_food": 2,
25
+ "surplus_shield": 3,
26
+ "surplus_trade": 1,
27
+ "turns_to_prod_complete": 1.0
28
+ }
29
+ },
30
+ "map": {
31
+ "status": [
32
+ [
33
+ 0.0,
34
+ 0.0,
35
+ 0.0,
36
+ 0.0,
37
+ 0.0,
38
+ 0.0,
39
+ 0.0,
40
+ 0.0,
41
+ 0.0,
42
+ 0.0,
43
+ 0.0,
44
+ 0.0,
45
+ 0.0,
46
+ 0.0,
47
+ 0.0,
48
+ 0.0,
49
+ 0.0,
50
+ 0.0,
51
+ 0.0,
52
+ 0.0,
53
+ 0.0,
54
+ 0.0,
55
+ 0.0,
56
+ 0.0,
57
+ 0.0,
58
+ 0.0,
59
+ 0.0,
60
+ 0.0,
61
+ 0.0,
62
+ 0.0,
63
+ 0.0,
64
+ 0.0,
65
+ 0.0,
66
+ 0.0,
67
+ 0.0,
68
+ 0.0,
69
+ 0.0,
70
+ 0.0,
71
+ 0.0,
72
+ 0.0,
73
+ 0.0,
74
+ 0.0,
75
+ 0.0,
76
+ 0.0,
77
+ 0.0,
78
+ 0.0,
79
+ 0.0,
80
+ 0.0,
81
+ 0.0,
82
+ 0.0,
83
+ 0.0,
84
+ 0.0,
85
+ 0.0,
86
+ 0.0,
87
+ 0.0,
88
+ 0.0
89
+ ],
90
+ [
91
+ 0.0,
92
+ 0.0,
93
+ 0.0,
94
+ 0.0,
95
+ 0.0,
96
+ 0.0,
97
+ 0.0,
98
+ 0.0,
99
+ 0.0,
100
+ 0.0,
101
+ 0.0,
102
+ 0.0,
103
+ 0.0,
104
+ 0.0,
105
+ 0.0,
106
+ 0.0,
107
+ 0.0,
108
+ 0.0,
109
+ 0.0,
110
+ 0.0,
111
+ 0.0,
112
+ 0.0,
113
+ 0.0,
114
+ 0.0,
115
+ 0.0,
116
+ 0.0,
117
+ 0.0,
118
+ 0.0,
119
+ 0.0,
120
+ 0.0,
121
+ 0.0,
122
+ 0.0,
123
+ 0.0,
124
+ 0.0,
125
+ 0.0,
126
+ 0.0,
127
+ 0.0,
128
+ 0.0,
129
+ 0.0,
130
+ 0.0,
131
+ 0.0,
132
+ 0.0,
133
+ 0.0,
134
+ 0.0,
135
+ 0.0,
136
+ 0.0,
137
+ 0.0,
138
+ 0.0,
139
+ 0.0,
140
+ 0.0,
141
+ 0.0,
142
+ 0.0,
143
+ 0.0,
144
+ 0.0,
145
+ 0.0,
146
+ 0.0
147
+ ],
148
+ [
149
+ 0.0,
150
+ 0.0,
151
+ 0.0,
152
+ 0.0,
153
+ 0.0,
154
+ 0.0,
155
+ 0.0,
156
+ 0.0,
157
+ 0.0,
158
+ 0.0,
159
+ 0.0,
160
+ 0.0,
161
+ 0.0,
162
+ 0.0,
163
+ 0.0,
164
+ 0.0,
165
+ 0.0,
166
+ 0.0,
167
+ 0.0,
168
+ 0.0,
169
+ 0.0,
170
+ 0.0,
171
+ 0.0,
172
+ 0.0,
173
+ 0.0,
174
+ 0.0,
175
+ 0.0,
176
+ 0.0,
177
+ 0.0,
178
+ 0.0,
179
+ 0.0,
180
+ 0.0,
181
+ 0.0,
182
+ 0.0,
183
+ 0.0,
184
+ 0.0,
185
+ 0.0,
186
+ 0.0,
187
+ 0.0,
188
+ 0.0,
189
+ 0.0,
190
+ 0.0,
191
+ 0.0,
192
+ 0.0,
193
+ 0.0,
194
+ 0.0,
195
+ 0.0,
196
+ 0.0,
197
+ 0.0,
198
+ 0.0,
199
+ 0.0,
200
+ 0.0,
201
+ 0.0,
202
+ 0.0,
203
+ 0.0,
204
+ 0.0
205
+ ],
206
+ [
207
+ 0.0,
208
+ 0.0,
209
+ 0.0,
210
+ 0.0,
211
+ 0.0,
212
+ 0.0,
213
+ 0.0,
214
+ 0.0,
215
+ 0.0,
216
+ 0.0,
217
+ 0.0,
218
+ 0.0,
219
+ 0.0,
220
+ 0.0,
221
+ 0.0,
222
+ 0.0,
223
+ 0.0,
224
+ 0.0,
225
+ 0.0,
226
+ 0.0,
227
+ 0.0,
228
+ 0.0,
229
+ 0.0,
230
+ 0.0,
231
+ 0.0,
232
+ 0.0,
233
+ 0.0,
234
+ 0.0,
235
+ 0.0,
236
+ 0.0,
237
+ 0.0,
238
+ 0.0,
239
+ 0.0,
240
+ 0.0,
241
+ 0.0,
242
+ 0.0,
243
+ 0.0,
244
+ 0.0,
245
+ 0.0,
246
+ 0.0,
247
+ 0.0,
248
+ 0.0,
249
+ 0.0,
250
+ 0.0,
251
+ 0.0,
252
+ 0.0,
253
+ 0.0,
254
+ 0.0,
255
+ 0.0,
256
+ 0.0,
257
+ 0.0,
258
+ 0.0,
259
+ 0.0,
260
+ 0.0,
261
+ 0.0,
262
+ 0.0
263
+ ],
264
+ [
265
+ 0.0,
266
+ 0.0,
267
+ 0.0,
268
+ 0.0,
269
+ 0.0,
270
+ 0.0,
271
+ 0.0,
272
+ 0.0,
273
+ 0.0,
274
+ 0.0,
275
+ 0.0,
276
+ 0.0,
277
+ 0.0,
278
+ 0.0,
279
+ 0.0,
280
+ 0.0,
281
+ 0.0,
282
+ 0.0,
283
+ 0.0,
284
+ 0.0,
285
+ 0.0,
286
+ 0.0,
287
+ 0.0,
288
+ 0.0,
289
+ 0.0,
290
+ 0.0,
291
+ 0.0,
292
+ 0.0,
293
+ 0.0,
294
+ 0.0,
295
+ 0.0,
296
+ 0.0,
297
+ 0.0,
298
+ 0.0,
299
+ 0.0,
300
+ 0.0,
301
+ 0.0,
302
+ 0.0,
303
+ 0.0,
304
+ 0.0,
305
+ 0.0,
306
+ 0.0,
307
+ 0.0,
308
+ 0.0,
309
+ 0.0,
310
+ 0.0,
311
+ 0.0,
312
+ 0.0,
313
+ 0.0,
314
+ 0.0,
315
+ 0.0,
316
+ 0.0,
317
+ 0.0,
318
+ 0.0,
319
+ 0.0,
320
+ 0.0
321
+ ],
322
+ [
323
+ 0.0,
324
+ 0.0,
325
+ 0.0,
326
+ 0.0,
327
+ 0.0,
328
+ 0.0,
329
+ 0.0,
330
+ 0.0,
331
+ 0.0,
332
+ 0.0,
333
+ 0.0,
334
+ 0.0,
335
+ 0.0,
336
+ 0.0,
337
+ 0.0,
338
+ 0.0,
339
+ 0.0,
340
+ 0.0,
341
+ 0.0,
342
+ 0.0,
343
+ 0.0,
344
+ 0.0,
345
+ 0.0,
346
+ 0.0,
347
+ 0.0,
348
+ 0.0,
349
+ 0.0,
350
+ 0.0,
351
+ 0.0,
352
+ 0.0,
353
+ 0.0,
354
+ 0.0,
355
+ 0.0,
356
+ 0.0,
357
+ 0.0,
358
+ 0.0,
359
+ 0.0,
360
+ 0.0,
361
+ 0.0,
362
+ 0.0,
363
+ 0.0,
364
+ 0.0,
365
+ 0.0,
366
+ 0.0,
367
+ 0.0,
368
+ 0.0,
369
+ 0.0,
370
+ 0.0,
371
+ 0.0,
372
+ 0.0,
373
+ 0.0,
374
+ 0.0,
375
+ 0.0,
376
+ 0.0,
377
+ 0.0,
378
+ 0.0
379
+ ],
380
+ [
381
+ 0.0,
382
+ 0.0,
383
+ 0.0,
384
+ 0.0,
385
+ 0.0,
386
+ 0.0,
387
+ 0.0,
388
+ 0.0,
389
+ 0.0,
390
+ 0.0,
391
+ 0.0,
392
+ 0.0,
393
+ 0.0,
394
+ 0.0,
395
+ 0.0,
396
+ 0.0,
397
+ 0.0,
398
+ 0.0,
399
+ 0.0,
400
+ 0.0,
401
+ 0.0,
402
+ 0.0,
403
+ 0.0,
404
+ 0.0,
405
+ 0.0,
406
+ 0.0,
407
+ 0.0,
408
+ 0.0,
409
+ 0.0,
410
+ 0.0,
411
+ 0.0,
412
+ 0.0,
413
+ 0.0,
414
+ 0.0,
415
+ 0.0,
416
+ 0.0,
417
+ 0.0,
418
+ 0.0,
419
+ 0.0,
420
+ 0.0,
421
+ 0.0,
422
+ 0.0,
423
+ 0.0,
424
+ 0.0,
425
+ 0.0,
426
+ 0.0,
427
+ 0.0,
428
+ 0.0,
429
+ 0.0,
430
+ 0.0,
431
+ 0.0,
432
+ 0.0,
433
+ 0.0,
434
+ 0.0,
435
+ 0.0,
436
+ 0.0
437
+ ],
438
+ [
439
+ 0.0,
440
+ 0.0,
441
+ 0.0,
442
+ 0.0,
443
+ 0.0,
444
+ 0.0,
445
+ 0.0,
446
+ 0.0,
447
+ 0.0,
448
+ 0.0,
449
+ 0.0,
450
+ 0.0,
451
+ 0.0,
452
+ 0.0,
453
+ 0.0,
454
+ 0.0,
455
+ 0.0,
456
+ 0.0,
457
+ 0.0,
458
+ 0.0,
459
+ 0.0,
460
+ 0.0,
461
+ 0.0,
462
+ 0.0,
463
+ 0.0,
464
+ 0.0,
465
+ 0.0,
466
+ 0.0,
467
+ 0.0,
468
+ 0.0,
469
+ 0.0,
470
+ 0.0,
471
+ 0.0,
472
+ 0.0,
473
+ 0.0,
474
+ 0.0,
475
+ 0.0,
476
+ 0.0,
477
+ 0.0,
478
+ 0.0,
479
+ 0.0,
480
+ 0.0,
481
+ 0.0,
482
+ 0.0,
483
+ 0.0,
484
+ 0.0,
485
+ 0.0,
486
+ 0.0,
487
+ 0.0,
488
+ 0.0,
489
+ 0.0,
490
+ 0.0,
491
+ 0.0,
492
+ 0.0,
493
+ 0.0,
494
+ 0.0
495
+ ],
496
+ [
497
+ 0.0,
498
+ 0.0,
499
+ 0.0,
500
+ 0.0,
501
+ 0.0,
502
+ 0.0,
503
+ 0.0,
504
+ 0.0,
505
+ 0.0,
506
+ 0.0,
507
+ 0.0,
508
+ 0.0,
509
+ 0.0,
510
+ 0.0,
511
+ 0.0,
512
+ 0.0,
513
+ 0.0,
514
+ 0.0,
515
+ 0.0,
516
+ 0.0,
517
+ 0.0,
518
+ 0.0,
519
+ 0.0,
520
+ 0.0,
521
+ 0.0,
522
+ 0.0,
523
+ 0.0,
524
+ 0.0,
525
+ 0.0,
526
+ 0.0,
527
+ 0.0,
528
+ 0.0,
529
+ 0.0,
530
+ 0.0,
531
+ 0.0,
532
+ 0.0,
533
+ 0.0,
534
+ 0.0,
535
+ 0.0,
536
+ 0.0,
537
+ 0.0,
538
+ 0.0,
539
+ 0.0,
540
+ 0.0,
541
+ 0.0,
542
+ 0.0,
543
+ 0.0,
544
+ 0.0,
545
+ 0.0,
546
+ 0.0,
547
+ 0.0,
548
+ 0.0,
549
+ 0.0,
550
+ 0.0,
551
+ 0.0,
552
+ 0.0
553
+ ],
554
+ [
555
+ 0.0,
556
+ 0.0,
557
+ 0.0,
558
+ 0.0,
559
+ 0.0,
560
+ 0.0,
561
+ 0.0,
562
+ 0.0,
563
+ 0.0,
564
+ 0.0,
565
+ 0.0,
566
+ 0.0,
567
+ 0.0,
568
+ 0.0,
569
+ 0.0,
570
+ 0.0,
571
+ 0.0,
572
+ 0.0,
573
+ 0.0,
574
+ 0.0,
575
+ 0.0,
576
+ 0.0,
577
+ 0.0,
578
+ 0.0,
579
+ 0.0,
580
+ 0.0,
581
+ 0.0,
582
+ 0.0,
583
+ 0.0,
584
+ 0.0,
585
+ 0.0,
586
+ 0.0,
587
+ 0.0,
588
+ 0.0,
589
+ 0.0,
590
+ 0.0,
591
+ 0.0,
592
+ 0.0,
593
+ 0.0,
594
+ 0.0,
595
+ 0.0,
596
+ 0.0,
597
+ 0.0,
598
+ 0.0,
599
+ 0.0,
600
+ 0.0,
601
+ 0.0,
602
+ 0.0,
603
+ 0.0,
604
+ 0.0,
605
+ 0.0,
606
+ 0.0,
607
+ 0.0,
608
+ 0.0,
609
+ 0.0,
610
+ 0.0
611
+ ],
612
+ [
613
+ 0.0,
614
+ 0.0,
615
+ 0.0,
616
+ 0.0,
617
+ 0.0,
618
+ 0.0,
619
+ 0.0,
620
+ 0.0,
621
+ 0.0,
622
+ 0.0,
623
+ 0.0,
624
+ 0.0,
625
+ 0.0,
626
+ 0.0,
627
+ 0.0,
628
+ 0.0,
629
+ 0.0,
630
+ 0.0,
631
+ 0.0,
632
+ 0.0,
633
+ 0.0,
634
+ 0.0,
635
+ 0.0,
636
+ 0.0,
637
+ 0.0,
638
+ 0.0,
639
+ 0.0,
640
+ 0.0,
641
+ 0.0,
642
+ 0.0,
643
+ 0.0,
644
+ 0.0,
645
+ 0.0,
646
+ 0.0,
647
+ 0.0,
648
+ 0.0,
649
+ 0.0,
650
+ 0.0,
651
+ 0.0,
652
+ 0.0,
653
+ 0.0,
654
+ 0.0,
655
+ 0.0,
656
+ 0.0,
657
+ 0.0,
658
+ 0.0,
659
+ 0.0,
660
+ 0.0,
661
+ 0.0,
662
+ 0.0,
663
+ 0.0,
664
+ 0.0,
665
+ 0.0,
666
+ 0.0,
667
+ 0.0,
668
+ 0.0
669
+ ],
670
+ [
671
+ 0.0,
672
+ 0.0,
673
+ 0.0,
674
+ 0.0,
675
+ 0.0,
676
+ 0.0,
677
+ 0.0,
678
+ 0.0,
679
+ 0.0,
680
+ 0.0,
681
+ 0.0,
682
+ 0.0,
683
+ 0.0,
684
+ 0.0,
685
+ 0.0,
686
+ 0.0,
687
+ 0.0,
688
+ 0.0,
689
+ 0.0,
690
+ 0.0,
691
+ 0.0,
692
+ 0.0,
693
+ 0.0,
694
+ 0.0,
695
+ 0.0,
696
+ 0.0,
697
+ 0.0,
698
+ 0.0,
699
+ 0.0,
700
+ 0.0,
701
+ 0.0,
702
+ 0.0,
703
+ 0.0,
704
+ 0.0,
705
+ 0.0,
706
+ 0.0,
707
+ 0.0,
708
+ 0.0,
709
+ 0.0,
710
+ 0.0,
711
+ 0.0,
712
+ 0.0,
713
+ 0.0,
714
+ 0.0,
715
+ 0.0,
716
+ 0.0,
717
+ 0.0,
718
+ 0.0,
719
+ 0.0,
720
+ 0.0,
721
+ 0.0,
722
+ 0.0,
723
+ 0.0,
724
+ 0.0,
725
+ 0.0,
726
+ 0.0
727
+ ],
728
+ [
729
+ 0.0,
730
+ 0.0,
731
+ 0.0,
732
+ 0.0,
733
+ 0.0,
734
+ 0.0,
735
+ 0.0,
736
+ 0.0,
737
+ 0.0,
738
+ 0.0,
739
+ 0.0,
740
+ 0.0,
741
+ 0.0,
742
+ 0.0,
743
+ 0.0,
744
+ 0.0,
745
+ 0.0,
746
+ 0.0,
747
+ 0.0,
748
+ 0.0,
749
+ 0.0,
750
+ 0.0,
751
+ 0.0,
752
+ 0.0,
753
+ 0.0,
754
+ 0.0,
755
+ 0.0,
756
+ 0.0,
757
+ 0.0,
758
+ 0.0,
759
+ 0.0,
760
+ 0.0,
761
+ 0.0,
762
+ 0.0,
763
+ 0.0,
764
+ 0.0,
765
+ 0.0,
766
+ 0.0,
767
+ 0.0,
768
+ 0.0,
769
+ 0.0,
770
+ 0.0,
771
+ 0.0,
772
+ 0.0,
773
+ 0.0,
774
+ 0.0,
775
+ 0.0,
776
+ 0.0,
777
+ 0.0,
778
+ 0.0,
779
+ 0.0,
780
+ 0.0,
781
+ 0.0,
782
+ 0.0,
783
+ 0.0,
784
+ 0.0
785
+ ],
786
+ [
787
+ 0.0,
788
+ 0.0,
789
+ 0.0,
790
+ 0.0,
791
+ 0.0,
792
+ 0.0,
793
+ 0.0,
794
+ 0.0,
795
+ 0.0,
796
+ 0.0,
797
+ 0.0,
798
+ 0.0,
799
+ 0.0,
800
+ 0.0,
801
+ 0.0,
802
+ 0.0,
803
+ 0.0,
804
+ 0.0,
805
+ 0.0,
806
+ 0.0,
807
+ 0.0,
808
+ 0.0,
809
+ 0.0,
810
+ 0.0,
811
+ 0.0,
812
+ 0.0,
813
+ 0.0,
814
+ 0.0,
815
+ 0.0,
816
+ 0.0,
817
+ 0.0,
818
+ 0.0,
819
+ 0.0,
820
+ 0.0,
821
+ 0.0,
822
+ 0.0,
823
+ 0.0,
824
+ 0.0,
825
+ 0.0,
826
+ 0.0,
827
+ 0.0,
828
+ 0.0,
829
+ 0.0,
830
+ 0.0,
831
+ 0.0,
832
+ 0.0,
833
+ 0.0,
834
+ 0.0,
835
+ 0.0,
836
+ 0.0,
837
+ 0.0,
838
+ 0.0,
839
+ 0.0,
840
+ 0.0,
841
+ 0.0,
842
+ 0.0
843
+ ],
844
+ [
845
+ 0.0,
846
+ 0.0,
847
+ 0.0,
848
+ 0.0,
849
+ 0.0,
850
+ 0.0,
851
+ 0.0,
852
+ 0.0,
853
+ 0.0,
854
+ 0.0,
855
+ 0.0,
856
+ 0.0,
857
+ 0.0,
858
+ 0.0,
859
+ 0.0,
860
+ 0.0,
861
+ 0.0,
862
+ 0.0,
863
+ 0.0,
864
+ 0.0,
865
+ 0.0,
866
+ 0.0,
867
+ 0.0,
868
+ 0.0,
869
+ 0.0,
870
+ 0.0,
871
+ 0.0,
872
+ 0.0,
873
+ 0.0,
874
+ 0.0,
875
+ 0.0,
876
+ 0.0,
877
+ 0.0,
878
+ 0.0,
879
+ 0.0,
880
+ 0.0,
881
+ 0.0,
882
+ 0.0,
883
+ 0.0,
884
+ 0.0,
885
+ 0.0,
886
+ 0.0,
887
+ 0.0,
888
+ 0.0,
889
+ 0.0,
890
+ 0.0,
891
+ 0.0,
892
+ 0.0,
893
+ 0.0,
894
+ 0.0,
895
+ 0.0,
896
+ 0.0,
897
+ 0.0,
898
+ 0.0,
899
+ 0.0,
900
+ 0.0
901
+ ],
902
+ [
903
+ 0.0,
904
+ 0.0,
905
+ 0.0,
906
+ 0.0,
907
+ 0.0,
908
+ 0.0,
909
+ 0.0,
910
+ 0.0,
911
+ 0.0,
912
+ 0.0,
913
+ 0.0,
914
+ 0.0,
915
+ 0.0,
916
+ 0.0,
917
+ 0.0,
918
+ 0.0,
919
+ 0.0,
920
+ 0.0,
921
+ 0.0,
922
+ 0.0,
923
+ 0.0,
924
+ 0.0,
925
+ 0.0,
926
+ 0.0,
927
+ 0.0,
928
+ 0.0,
929
+ 0.0,
930
+ 0.0,
931
+ 0.0,
932
+ 0.0,
933
+ 0.0,
934
+ 0.0,
935
+ 0.0,
936
+ 0.0,
937
+ 0.0,
938
+ 0.0,
939
+ 0.0,
940
+ 0.0,
941
+ 0.0,
942
+ 0.0,
943
+ 0.0,
944
+ 0.0,
945
+ 0.0,
946
+ 0.0,
947
+ 0.0,
948
+ 0.0,
949
+ 0.0,
950
+ 0.0,
951
+ 0.0,
952
+ 0.0,
953
+ 0.0,
954
+ 0.0,
955
+ 0.0,
956
+ 0.0,
957
+ 0.0,
958
+ 0.0
959
+ ],
960
+ [
961
+ 0.0,
962
+ 0.0,
963
+ 0.0,
964
+ 0.0,
965
+ 0.0,
966
+ 0.0,
967
+ 0.0,
968
+ 0.0,
969
+ 0.0,
970
+ 0.0,
971
+ 0.0,
972
+ 0.0,
973
+ 0.0,
974
+ 0.0,
975
+ 0.0,
976
+ 0.0,
977
+ 0.0,
978
+ 0.0,
979
+ 0.0,
980
+ 0.0,
981
+ 0.0,
982
+ 0.0,
983
+ 0.0,
984
+ 0.0,
985
+ 0.0,
986
+ 0.0,
987
+ 0.0,
988
+ 0.0,
989
+ 0.0,
990
+ 0.0,
991
+ 0.0,
992
+ 0.0,
993
+ 0.0,
994
+ 0.0,
995
+ 0.0,
996
+ 0.0,
997
+ 0.0,
998
+ 0.0,
999
+ 0.0,
1000
+ 0.0,
1001
+ 0.0,
1002
+ 0.0,
1003
+ 0.0,
1004
+ 0.0,
1005
+ 0.0,
1006
+ 0.0,
1007
+ 0.0,
1008
+ 0.0,
1009
+ 0.0,
1010
+ 0.0,
1011
+ 0.0,
1012
+ 0.0,
1013
+ 0.0,
1014
+ 0.0,
1015
+ 0.0,
1016
+ 0.0
1017
+ ],
1018
+ [
1019
+ 0.0,
1020
+ 0.0,
1021
+ 0.0,
1022
+ 0.0,
1023
+ 0.0,
1024
+ 0.0,
1025
+ 0.0,
1026
+ 0.0,
1027
+ 0.0,
1028
+ 0.0,
1029
+ 0.0,
1030
+ 0.0,
1031
+ 0.0,
1032
+ 0.0,
1033
+ 0.0,
1034
+ 0.0,
1035
+ 0.0,
1036
+ 0.0,
1037
+ 0.0,
1038
+ 0.0,
1039
+ 0.0,
1040
+ 0.0,
1041
+ 0.0,
1042
+ 0.0,
1043
+ 0.0,
1044
+ 0.0,
1045
+ 0.0,
1046
+ 0.0,
1047
+ 0.0,
1048
+ 0.0,
1049
+ 0.0,
1050
+ 0.0,
1051
+ 0.0,
1052
+ 0.0,
1053
+ 0.0,
1054
+ 0.0,
1055
+ 0.0,
1056
+ 0.0,
1057
+ 0.0,
1058
+ 0.0,
1059
+ 0.0,
1060
+ 0.0,
1061
+ 0.0,
1062
+ 0.0,
1063
+ 0.0,
1064
+ 0.0,
1065
+ 0.0,
1066
+ 0.0,
1067
+ 0.0,
1068
+ 0.0,
1069
+ 0.0,
1070
+ 0.0,
1071
+ 0.0,
1072
+ 0.0,
1073
+ 0.0,
1074
+ 0.0
1075
+ ],
1076
+ [
1077
+ 0.0,
1078
+ 0.0,
1079
+ 0.0,
1080
+ 0.0,
1081
+ 0.0,
1082
+ 0.0,
1083
+ 0.0,
1084
+ 0.0,
1085
+ 0.0,
1086
+ 0.0,
1087
+ 0.0,
1088
+ 0.0,
1089
+ 0.0,
1090
+ 0.0,
1091
+ 0.0,
1092
+ 0.0,
1093
+ 0.0,
1094
+ 0.0,
1095
+ 0.0,
1096
+ 0.0,
1097
+ 0.0,
1098
+ 0.0,
1099
+ 0.0,
1100
+ 0.0,
1101
+ 0.0,
1102
+ 0.0,
1103
+ 0.0,
1104
+ 0.0,
1105
+ 0.0,
1106
+ 0.0,
1107
+ 0.0,
1108
+ 0.0,
1109
+ 0.0,
1110
+ 0.0,
1111
+ 0.0,
1112
+ 0.0,
1113
+ 0.0,
1114
+ 0.0,
1115
+ 0.0,
1116
+ 0.0,
1117
+ 0.0,
1118
+ 0.0,
1119
+ 0.0,
1120
+ 0.0,
1121
+ 0.0,
1122
+ 0.0,
1123
+ 0.0,
1124
+ 0.0,
1125
+ 0.0,
1126
+ 0.0,
1127
+ 0.0,
1128
+ 0.0,
1129
+ 0.0,
1130
+ 0.0,
1131
+ 0.0,
1132
+ 0.0
1133
+ ],
1134
+ [
1135
+ 0.0,
1136
+ 0.0,
1137
+ 0.0,
1138
+ 0.0,
1139
+ 0.0,
1140
+ 0.0,
1141
+ 0.0,
1142
+ 0.0,
1143
+ 0.0,
1144
+ 0.0,
1145
+ 0.0,
1146
+ 0.0,
1147
+ 0.0,
1148
+ 0.0,
1149
+ 0.0,
1150
+ 0.0,
1151
+ 0.0,
1152
+ 0.0,
1153
+ 0.0,
1154
+ 0.0,
1155
+ 0.0,
1156
+ 0.0,
1157
+ 0.0,
1158
+ 0.0,
1159
+ 0.0,
1160
+ 0.0,
1161
+ 0.0,
1162
+ 0.0,
1163
+ 0.0,
1164
+ 0.0,
1165
+ 0.0,
1166
+ 0.0,
1167
+ 0.0,
1168
+ 0.0,
1169
+ 0.0,
1170
+ 0.0,
1171
+ 0.0,
1172
+ 0.0,
1173
+ 0.0,
1174
+ 0.0,
1175
+ 0.0,
1176
+ 0.0,
1177
+ 0.0,
1178
+ 0.0,
1179
+ 0.0,
1180
+ 0.0,
1181
+ 0.0,
1182
+ 0.0,
1183
+ 0.0,
1184
+ 0.0,
1185
+ 0.0,
1186
+ 0.0,
1187
+ 0.0,
1188
+ 0.0,
1189
+ 0.0,
1190
+ 0.0
1191
+ ],
1192
+ [
1193
+ 0.0,
1194
+ 0.0,
1195
+ 0.0,
1196
+ 0.0,
1197
+ 0.0,
1198
+ 0.0,
1199
+ 0.0,
1200
+ 0.0,
1201
+ 0.0,
1202
+ 0.0,
1203
+ 0.0,
1204
+ 0.0,
1205
+ 0.0,
1206
+ 0.0,
1207
+ 0.0,
1208
+ 0.0,
1209
+ 0.0,
1210
+ 0.0,
1211
+ 0.0,
1212
+ 0.0,
1213
+ 0.0,
1214
+ 0.0,
1215
+ 0.0,
1216
+ 0.0,
1217
+ 0.0,
1218
+ 0.0,
1219
+ 0.0,
1220
+ 0.0,
1221
+ 0.0,
1222
+ 0.0,
1223
+ 0.0,
1224
+ 0.0,
1225
+ 0.0,
1226
+ 0.0,
1227
+ 0.0,
1228
+ 0.0,
1229
+ 0.0,
1230
+ 0.0,
1231
+ 0.0,
1232
+ 0.0,
1233
+ 0.0,
1234
+ 0.0,
1235
+ 0.0,
1236
+ 0.0,
1237
+ 0.0,
1238
+ 0.0,
1239
+ 0.0,
1240
+ 0.0,
1241
+ 0.0,
1242
+ 0.0,
1243
+ 0.0,
1244
+ 0.0,
1245
+ 0.0,
1246
+ 0.0,
1247
+ 0.0,
1248
+ 0.0
1249
+ ],
1250
+ [
1251
+ 0.0,
1252
+ 0.0,
1253
+ 0.0,
1254
+ 0.0,
1255
+ 0.0,
1256
+ 0.0,
1257
+ 0.0,
1258
+ 0.0,
1259
+ 0.0,
1260
+ 0.0,
1261
+ 0.0,
1262
+ 0.0,
1263
+ 0.0,
1264
+ 0.0,
1265
+ 0.0,
1266
+ 0.0,
1267
+ 0.0,
1268
+ 0.0,
1269
+ 0.0,
1270
+ 0.0,
1271
+ 0.0,
1272
+ 0.0,
1273
+ 0.0,
1274
+ 0.0,
1275
+ 0.0,
1276
+ 0.0,
1277
+ 0.0,
1278
+ 0.0,
1279
+ 0.0,
1280
+ 0.0,
1281
+ 0.0,
1282
+ 0.0,
1283
+ 0.0,
1284
+ 0.0,
1285
+ 0.0,
1286
+ 0.0,
1287
+ 0.0,
1288
+ 0.0,
1289
+ 0.0,
1290
+ 0.0,
1291
+ 0.0,
1292
+ 0.0,
1293
+ 0.0,
1294
+ 0.0,
1295
+ 0.0,
1296
+ 0.0,
1297
+ 0.0,
1298
+ 0.0,
1299
+ 0.0,
1300
+ 0.0,
1301
+ 0.0,
1302
+ 0.0,
1303
+ 0.0,
1304
+ 0.0,
1305
+ 0.0,
1306
+ 0.0
1307
+ ],
1308
+ [
1309
+ 0.0,
1310
+ 0.0,
1311
+ 0.0,
1312
+ 0.0,
1313
+ 0.0,
1314
+ 0.0,
1315
+ 0.0,
1316
+ 0.0,
1317
+ 0.0,
1318
+ 0.0,
1319
+ 0.0,
1320
+ 0.0,
1321
+ 0.0,
1322
+ 0.0,
1323
+ 0.0,
1324
+ 0.0,
1325
+ 0.0,
1326
+ 0.0,
1327
+ 0.0,
1328
+ 0.0,
1329
+ 0.0,
1330
+ 0.0,
1331
+ 0.0,
1332
+ 0.0,
1333
+ 0.0,
1334
+ 0.0,
1335
+ 0.0,
1336
+ 0.0,
1337
+ 0.0,
1338
+ 0.0,
1339
+ 0.0,
1340
+ 0.0,
1341
+ 0.0,
1342
+ 0.0,
1343
+ 0.0,
1344
+ 0.0,
1345
+ 0.0,
1346
+ 0.0,
1347
+ 0.0,
1348
+ 0.0,
1349
+ 0.0,
1350
+ 0.0,
1351
+ 0.0,
1352
+ 0.0,
1353
+ 0.0,
1354
+ 0.0,
1355
+ 0.0,
1356
+ 0.0,
1357
+ 0.0,
1358
+ 0.0,
1359
+ 0.0,
1360
+ 0.0,
1361
+ 0.0,
1362
+ 0.0,
1363
+ 0.0,
1364
+ 0.0
1365
+ ],
1366
+ [
1367
+ 0.0,
1368
+ 0.0,
1369
+ 0.0,
1370
+ 0.0,
1371
+ 0.0,
1372
+ 0.0,
1373
+ 0.0,
1374
+ 0.0,
1375
+ 0.0,
1376
+ 0.0,
1377
+ 0.0,
1378
+ 0.0,
1379
+ 0.0,
1380
+ 0.0,
1381
+ 0.0,
1382
+ 0.0,
1383
+ 0.0,
1384
+ 0.0,
1385
+ 0.0,
1386
+ 0.0,
1387
+ 0.0,
1388
+ 0.0,
1389
+ 0.0,
1390
+ 0.0,
1391
+ 0.0,
1392
+ 0.0,
1393
+ 0.0,
1394
+ 0.0,
1395
+ 0.0,
1396
+ 0.0,
1397
+ 0.0,
1398
+ 0.0,
1399
+ 0.0,
1400
+ 0.0,
1401
+ 0.0,
1402
+ 0.0,
1403
+ 0.0,
1404
+ 0.0,
1405
+ 0.0,
1406
+ 0.0,
1407
+ 0.0,
1408
+ 0.0,
1409
+ 0.0,
1410
+ 0.0,
1411
+ 0.0,
1412
+ 0.0,
1413
+ 0.0,
1414
+ 0.0,
1415
+ 0.0,
1416
+ 0.0,
1417
+ 0.0,
1418
+ 0.0,
1419
+ 0.0,
1420
+ 0.0,
1421
+ 0.0,
1422
+ 0.0
1423
+ ],
1424
+ [
1425
+ 0.0,
1426
+ 0.0,
1427
+ 0.0,
1428
+ 0.0,
1429
+ 0.0,
1430
+ 0.0,
1431
+ 0.0,
1432
+ 0.0,
1433
+ 0.0,
1434
+ 0.0,
1435
+ 0.0,
1436
+ 0.0,
1437
+ 0.0,
1438
+ 0.0,
1439
+ 0.0,
1440
+ 0.0,
1441
+ 0.0,
1442
+ 0.0,
1443
+ 0.0,
1444
+ 0.0,
1445
+ 0.0,
1446
+ 0.0,
1447
+ 0.0,
1448
+ 0.0,
1449
+ 0.0,
1450
+ 0.0,
1451
+ 0.0,
1452
+ 0.0,
1453
+ 0.0,
1454
+ 0.0,
1455
+ 0.0,
1456
+ 0.0,
1457
+ 0.0,
1458
+ 0.0,
1459
+ 0.0,
1460
+ 0.0,
1461
+ 0.0,
1462
+ 0.0,
1463
+ 0.0,
1464
+ 0.0,
1465
+ 0.0,
1466
+ 0.0,
1467
+ 0.0,
1468
+ 0.0,
1469
+ 0.0,
1470
+ 0.0,
1471
+ 0.0,
1472
+ 0.0,
1473
+ 0.0,
1474
+ 0.0,
1475
+ 0.0,
1476
+ 0.0,
1477
+ 0.0,
1478
+ 0.0,
1479
+ 0.0,
1480
+ 0.0
1481
+ ],
1482
+ [
1483
+ 0.0,
1484
+ 0.0,
1485
+ 0.0,
1486
+ 0.0,
1487
+ 0.0,
1488
+ 0.0,
1489
+ 0.0,
1490
+ 0.0,
1491
+ 0.0,
1492
+ 0.0,
1493
+ 0.0,
1494
+ 0.0,
1495
+ 0.0,
1496
+ 0.0,
1497
+ 0.0,
1498
+ 0.0,
1499
+ 0.0,
1500
+ 0.0,
1501
+ 0.0,
1502
+ 0.0,
1503
+ 0.0,
1504
+ 0.0,
1505
+ 0.0,
1506
+ 0.0,
1507
+ 0.0,
1508
+ 0.0,
1509
+ 0.0,
1510
+ 0.0,
1511
+ 0.0,
1512
+ 0.0,
1513
+ 0.0,
1514
+ 0.0,
1515
+ 0.0,
1516
+ 0.0,
1517
+ 0.0,
1518
+ 0.0,
1519
+ 0.0,
1520
+ 0.0,
1521
+ 0.0,
1522
+ 0.0,
1523
+ 0.0,
1524
+ 0.0,
1525
+ 0.0,
1526
+ 0.0,
1527
+ 0.0,
1528
+ 0.0,
1529
+ 0.0,
1530
+ 0.0,
1531
+ 0.0,
1532
+ 0.0,
1533
+ 0.0,
1534
+ 0.0,
1535
+ 0.0,
1536
+ 0.0,
1537
+ 0.0,
1538
+ 0.0
1539
+ ],
1540
+ [
1541
+ 0.0,
1542
+ 0.0,
1543
+ 0.0,
1544
+ 0.0,
1545
+ 0.0,
1546
+ 0.0,
1547
+ 0.0,
1548
+ 0.0,
1549
+ 0.0,
1550
+ 0.0,
1551
+ 0.0,
1552
+ 0.0,
1553
+ 0.0,
1554
+ 0.0,
1555
+ 0.0,
1556
+ 0.0,
1557
+ 0.0,
1558
+ 0.0,
1559
+ 0.0,
1560
+ 0.0,
1561
+ 0.0,
1562
+ 0.0,
1563
+ 0.0,
1564
+ 0.0,
1565
+ 0.0,
1566
+ 0.0,
1567
+ 0.0,
1568
+ 0.0,
1569
+ 0.0,
1570
+ 0.0,
1571
+ 0.0,
1572
+ 0.0,
1573
+ 0.0,
1574
+ 0.0,
1575
+ 0.0,
1576
+ 0.0,
1577
+ 0.0,
1578
+ 0.0,
1579
+ 0.0,
1580
+ 0.0,
1581
+ 0.0,
1582
+ 0.0,
1583
+ 0.0,
1584
+ 0.0,
1585
+ 0.0,
1586
+ 0.0,
1587
+ 0.0,
1588
+ 0.0,
1589
+ 0.0,
1590
+ 0.0,
1591
+ 0.0,
1592
+ 0.0,
1593
+ 0.0,
1594
+ 0.0,
1595
+ 0.0,
1596
+ 0.0
1597
+ ],
1598
+ [
1599
+ 0.0,
1600
+ 0.0,
1601
+ 0.0,
1602
+ 0.0,
1603
+ 0.0,
1604
+ 0.0,
1605
+ 0.0,
1606
+ 0.0,
1607
+ 0.0,
1608
+ 0.0,
1609
+ 0.0,
1610
+ 0.0,
1611
+ 0.0,
1612
+ 0.0,
1613
+ 0.0,
1614
+ 0.0,
1615
+ 0.0,
1616
+ 0.0,
1617
+ 0.0,
1618
+ 0.0,
1619
+ 0.0,
1620
+ 0.0,
1621
+ 0.0,
1622
+ 0.0,
1623
+ 0.0,
1624
+ 0.0,
1625
+ 0.0,
1626
+ 0.0,
1627
+ 0.0,
1628
+ 0.0,
1629
+ 0.0,
1630
+ 0.0,
1631
+ 0.0,
1632
+ 0.0,
1633
+ 0.0,
1634
+ 0.0,
1635
+ 0.0,
1636
+ 0.0,
1637
+ 0.0,
1638
+ 0.0,
1639
+ 0.0,
1640
+ 0.0,
1641
+ 0.0,
1642
+ 0.0,
1643
+ 0.0,
1644
+ 0.0,
1645
+ 0.0,
1646
+ 0.0,
1647
+ 0.0,
1648
+ 0.0,
1649
+ 0.0,
1650
+ 0.0,
1651
+ 0.0,
1652
+ 0.0,
1653
+ 0.0,
1654
+ 0.0
1655
+ ],
1656
+ [
1657
+ 0.0,
1658
+ 0.0,
1659
+ 0.0,
1660
+ 0.0,
1661
+ 0.0,
1662
+ 0.0,
1663
+ 0.0,
1664
+ 0.0,
1665
+ 0.0,
1666
+ 0.0,
1667
+ 0.0,
1668
+ 0.0,
1669
+ 0.0,
1670
+ 0.0,
1671
+ 0.0,
1672
+ 0.0,
1673
+ 0.0,
1674
+ 0.0,
1675
+ 0.0,
1676
+ 0.0,
1677
+ 0.0,
1678
+ 0.0,
1679
+ 0.0,
1680
+ 0.0,
1681
+ 0.0,
1682
+ 0.0,
1683
+ 0.0,
1684
+ 0.0,
1685
+ 0.0,
1686
+ 0.0,
1687
+ 0.0,
1688
+ 0.0,
1689
+ 0.0,
1690
+ 0.0,
1691
+ 0.0,
1692
+ 0.0,
1693
+ 0.0,
1694
+ 0.0,
1695
+ 0.0,
1696
+ 0.0,
1697
+ 0.0,
1698
+ 0.0,
1699
+ 0.0,
1700
+ 0.0,
1701
+ 0.0,
1702
+ 0.0,
1703
+ 0.0,
1704
+ 0.0,
1705
+ 0.0,
1706
+ 0.0,
1707
+ 0.0,
1708
+ 0.0,
1709
+ 0.0,
1710
+ 0.0,
1711
+ 0.0,
1712
+ 0.0
1713
+ ],
1714
+ [
1715
+ 0.0,
1716
+ 0.0,
1717
+ 0.0,
1718
+ 0.0,
1719
+ 0.0,
1720
+ 0.0,
1721
+ 0.0,
1722
+ 0.0,
1723
+ 0.0,
1724
+ 0.0,
1725
+ 0.0,
1726
+ 0.0,
1727
+ 0.0,
1728
+ 0.0,
1729
+ 0.0,
1730
+ 0.0,
1731
+ 0.0,
1732
+ 0.0,
1733
+ 0.0,
1734
+ 0.0,
1735
+ 0.0,
1736
+ 0.0,
1737
+ 0.0,
1738
+ 0.0,
1739
+ 0.0,
1740
+ 0.0,
1741
+ 0.0,
1742
+ 0.0,
1743
+ 0.0,
1744
+ 0.0,
1745
+ 0.0,
1746
+ 0.0,
1747
+ 0.0,
1748
+ 0.0,
1749
+ 0.0,
1750
+ 0.0,
1751
+ 0.0,
1752
+ 0.0,
1753
+ 0.0,
1754
+ 0.0,
1755
+ 0.0,
1756
+ 0.0,
1757
+ 0.0,
1758
+ 0.0,
1759
+ 0.0,
1760
+ 0.0,
1761
+ 0.0,
1762
+ 0.0,
1763
+ 0.0,
1764
+ 0.0,
1765
+ 0.0,
1766
+ 0.0,
1767
+ 0.0,
1768
+ 0.0,
1769
+ 0.0,
1770
+ 0.0
1771
+ ],
1772
+ [
1773
+ 0.0,
1774
+ 0.0,
1775
+ 0.0,
1776
+ 0.0,
1777
+ 0.0,
1778
+ 0.0,
1779
+ 0.0,
1780
+ 0.0,
1781
+ 0.0,
1782
+ 0.0,
1783
+ 0.0,
1784
+ 0.0,
1785
+ 0.0,
1786
+ 0.0,
1787
+ 0.0,
1788
+ 0.0,
1789
+ 0.0,
1790
+ 0.0,
1791
+ 0.0,
1792
+ 0.0,
1793
+ 0.0,
1794
+ 0.0,
1795
+ 0.0,
1796
+ 0.0,
1797
+ 0.0,
1798
+ 0.0,
1799
+ 0.0,
1800
+ 0.0,
1801
+ 0.0,
1802
+ 0.0,
1803
+ 0.0,
1804
+ 0.0,
1805
+ 0.0,
1806
+ 0.0,
1807
+ 0.0,
1808
+ 0.0,
1809
+ 0.0,
1810
+ 0.0,
1811
+ 0.0,
1812
+ 0.0,
1813
+ 0.0,
1814
+ 0.0,
1815
+ 0.0,
1816
+ 0.0,
1817
+ 0.0,
1818
+ 0.0,
1819
+ 0.0,
1820
+ 0.0,
1821
+ 0.0,
1822
+ 0.0,
1823
+ 0.0,
1824
+ 0.0,
1825
+ 0.0,
1826
+ 0.0,
1827
+ 0.0,
1828
+ 0.0
1829
+ ],
1830
+ [
1831
+ 0.0,
1832
+ 0.0,
1833
+ 0.0,
1834
+ 0.0,
1835
+ 0.0,
1836
+ 0.0,
1837
+ 0.0,
1838
+ 0.0,
1839
+ 0.0,
1840
+ 0.0,
1841
+ 0.0,
1842
+ 0.0,
1843
+ 0.0,
1844
+ 0.0,
1845
+ 0.0,
1846
+ 0.0,
1847
+ 0.0,
1848
+ 0.0,
1849
+ 0.0,
1850
+ 0.0,
1851
+ 0.0,
1852
+ 0.0,
1853
+ 0.0,
1854
+ 0.0,
1855
+ 0.0,
1856
+ 0.0,
1857
+ 0.0,
1858
+ 0.0,
1859
+ 0.0,
1860
+ 0.0,
1861
+ 0.0,
1862
+ 0.0,
1863
+ 0.0,
1864
+ 0.0,
1865
+ 0.0,
1866
+ 0.0,
1867
+ 0.0,
1868
+ 0.0,
1869
+ 0.0,
1870
+ 0.0,
1871
+ 0.0,
1872
+ 0.0,
1873
+ 0.0,
1874
+ 0.0,
1875
+ 0.0,
1876
+ 0.0,
1877
+ 0.0,
1878
+ 0.0,
1879
+ 0.0,
1880
+ 0.0,
1881
+ 0.0,
1882
+ 0.0,
1883
+ 0.0,
1884
+ 0.0,
1885
+ 0.0,
1886
+ 0.0
1887
+ ],
1888
+ [
1889
+ 0.0,
1890
+ 0.0,
1891
+ 0.0,
1892
+ 0.0,
1893
+ 0.0,
1894
+ 0.0,
1895
+ 0.0,
1896
+ 0.0,
1897
+ 0.0,
1898
+ 0.0,
1899
+ 0.0,
1900
+ 0.0,
1901
+ 0.0,
1902
+ 0.0,
1903
+ 0.0,
1904
+ 0.0,
1905
+ 0.0,
1906
+ 0.0,
1907
+ 0.0,
1908
+ 0.0,
1909
+ 0.0,
1910
+ 0.0,
1911
+ 0.0,
1912
+ 0.0,
1913
+ 0.0,
1914
+ 0.0,
1915
+ 0.0,
1916
+ 0.0,
1917
+ 0.0,
1918
+ 0.0,
1919
+ 0.0,
1920
+ 0.0,
1921
+ 0.0,
1922
+ 0.0,
1923
+ 0.0,
1924
+ 0.0,
1925
+ 0.0,
1926
+ 0.0,
1927
+ 0.0,
1928
+ 0.0,
1929
+ 0.0,
1930
+ 0.0,
1931
+ 0.0,
1932
+ 0.0,
1933
+ 0.0,
1934
+ 0.0,
1935
+ 0.0,
1936
+ 0.0,
1937
+ 0.0,
1938
+ 0.0,
1939
+ 0.0,
1940
+ 0.0,
1941
+ 0.0,
1942
+ 0.0,
1943
+ 0.0,
1944
+ 0.0
1945
+ ],
1946
+ [
1947
+ 0.0,
1948
+ 0.0,
1949
+ 0.0,
1950
+ 0.0,
1951
+ 0.0,
1952
+ 0.0,
1953
+ 0.0,
1954
+ 0.0,
1955
+ 0.0,
1956
+ 0.0,
1957
+ 0.0,
1958
+ 0.0,
1959
+ 0.0,
1960
+ 0.0,
1961
+ 0.0,
1962
+ 0.0,
1963
+ 0.0,
1964
+ 0.0,
1965
+ 0.0,
1966
+ 0.0,
1967
+ 0.0,
1968
+ 0.0,
1969
+ 0.0,
1970
+ 0.0,
1971
+ 0.0,
1972
+ 0.0,
1973
+ 0.0,
1974
+ 0.0,
1975
+ 0.0,
1976
+ 0.0,
1977
+ 0.0,
1978
+ 0.0,
1979
+ 0.0,
1980
+ 0.0,
1981
+ 0.0,
1982
+ 0.0,
1983
+ 0.0,
1984
+ 0.0,
1985
+ 0.0,
1986
+ 0.0,
1987
+ 0.0,
1988
+ 0.0,
1989
+ 0.0,
1990
+ 0.0,
1991
+ 0.0,
1992
+ 0.0,
1993
+ 0.0,
1994
+ 0.0,
1995
+ 0.0,
1996
+ 0.0,
1997
+ 0.0,
1998
+ 0.0,
1999
+ 0.0,
2000
+ 0.0,
2001
+ 0.0,
2002
+ 0.0
2003
+ ],
2004
+ [
2005
+ 0.0,
2006
+ 0.0,
2007
+ 0.0,
2008
+ 0.0,
2009
+ 0.0,
2010
+ 0.0,
2011
+ 0.0,
2012
+ 0.0,
2013
+ 0.0,
2014
+ 0.0,
2015
+ 0.0,
2016
+ 0.0,
2017
+ 0.0,
2018
+ 0.0,
2019
+ 0.0,
2020
+ 0.0,
2021
+ 0.0,
2022
+ 0.0,
2023
+ 0.0,
2024
+ 0.0,
2025
+ 0.0,
2026
+ 0.0,
2027
+ 0.0,
2028
+ 0.0,
2029
+ 0.0,
2030
+ 0.0,
2031
+ 0.0,
2032
+ 0.0,
2033
+ 0.0,
2034
+ 0.0,
2035
+ 0.0,
2036
+ 0.0,
2037
+ 0.0,
2038
+ 0.0,
2039
+ 0.0,
2040
+ 0.0,
2041
+ 0.0,
2042
+ 0.0,
2043
+ 0.0,
2044
+ 0.0,
2045
+ 0.0,
2046
+ 0.0,
2047
+ 0.0,
2048
+ 0.0,
2049
+ 0.0,
2050
+ 0.0,
2051
+ 0.0,
2052
+ 0.0,
2053
+ 0.0,
2054
+ 0.0,
2055
+ 0.0,
2056
+ 0.0,
2057
+ 0.0,
2058
+ 0.0,
2059
+ 0.0,
2060
+ 0.0
2061
+ ],
2062
+ [
2063
+ 0.0,
2064
+ 0.0,
2065
+ 0.0,
2066
+ 0.0,
2067
+ 0.0,
2068
+ 0.0,
2069
+ 0.0,
2070
+ 0.0,
2071
+ 0.0,
2072
+ 0.0,
2073
+ 0.0,
2074
+ 0.0,
2075
+ 0.0,
2076
+ 0.0,
2077
+ 0.0,
2078
+ 0.0,
2079
+ 0.0,
2080
+ 0.0,
2081
+ 0.0,
2082
+ 0.0,
2083
+ 0.0,
2084
+ 0.0,
2085
+ 0.0,
2086
+ 0.0,
2087
+ 0.0,
2088
+ 0.0,
2089
+ 0.0,
2090
+ 0.0,
2091
+ 0.0,
2092
+ 0.0,
2093
+ 0.0,
2094
+ 0.0,
2095
+ 0.0,
2096
+ 0.0,
2097
+ 0.0,
2098
+ 0.0,
2099
+ 0.0,
2100
+ 0.0,
2101
+ 0.0,
2102
+ 0.0,
2103
+ 0.0,
2104
+ 0.0,
2105
+ 0.0,
2106
+ 0.0,
2107
+ 0.0,
2108
+ 0.0,
2109
+ 0.0,
2110
+ 0.0,
2111
+ 0.0,
2112
+ 0.0,
2113
+ 0.0,
2114
+ 0.0,
2115
+ 0.0,
2116
+ 0.0,
2117
+ 0.0,
2118
+ 0.0
2119
+ ],
2120
+ [
2121
+ 0.0,
2122
+ 0.0,
2123
+ 0.0,
2124
+ 0.0,
2125
+ 0.0,
2126
+ 0.0,
2127
+ 0.0,
2128
+ 0.0,
2129
+ 0.0,
2130
+ 0.0,
2131
+ 0.0,
2132
+ 0.0,
2133
+ 0.0,
2134
+ 0.0,
2135
+ 0.0,
2136
+ 0.0,
2137
+ 0.0,
2138
+ 0.0,
2139
+ 0.0,
2140
+ 0.0,
2141
+ 0.0,
2142
+ 0.0,
2143
+ 0.0,
2144
+ 0.0,
2145
+ 0.0,
2146
+ 0.0,
2147
+ 0.0,
2148
+ 0.0,
2149
+ 0.0,
2150
+ 0.0,
2151
+ 0.0,
2152
+ 0.0,
2153
+ 0.0,
2154
+ 0.0,
2155
+ 0.0,
2156
+ 0.0,
2157
+ 0.0,
2158
+ 0.0,
2159
+ 0.0,
2160
+ 0.0,
2161
+ 0.0,
2162
+ 0.0,
2163
+ 0.0,
2164
+ 0.0,
2165
+ 0.0,
2166
+ 0.0,
2167
+ 0.0,
2168
+ 0.0,
2169
+ 0.0,
2170
+ 0.0,
2171
+ 0.0,
2172
+ 0.0,
2173
+ 0.0,
2174
+ 0.0,
2175
+ 0.0,
2176
+ 0.0
2177
+ ],
2178
+ [
2179
+ 0.0,
2180
+ 0.0,
2181
+ 0.0,
2182
+ 0.0,
2183
+ 0.0,
2184
+ 0.0,
2185
+ 0.0,
2186
+ 0.0,
2187
+ 0.0,
2188
+ 0.0,
2189
+ 0.0,
2190
+ 0.0,
2191
+ 0.0,
2192
+ 0.0,
2193
+ 0.0,
2194
+ 0.0,
2195
+ 0.0,
2196
+ 0.0,
2197
+ 0.0,
2198
+ 0.0,
2199
+ 0.0,
2200
+ 0.0,
2201
+ 0.0,
2202
+ 0.0,
2203
+ 0.0,
2204
+ 0.0,
2205
+ 0.0,
2206
+ 0.0,
2207
+ 0.0,
2208
+ 0.0,
2209
+ 0.0,
2210
+ 0.0,
2211
+ 0.0,
2212
+ 0.0,
2213
+ 0.0,
2214
+ 0.0,
2215
+ 0.0,
2216
+ 0.0,
2217
+ 0.0,
2218
+ 0.0,
2219
+ 0.0,
2220
+ 0.0,
2221
+ 0.0,
2222
+ 0.0,
2223
+ 0.0,
2224
+ 0.0,
2225
+ 0.0,
2226
+ 0.0,
2227
+ 0.0,
2228
+ 0.0,
2229
+ 0.0,
2230
+ 0.0,
2231
+ 0.0,
2232
+ 0.0,
2233
+ 0.0,
2234
+ 0.0
2235
+ ],
2236
+ [
2237
+ 0.0,
2238
+ 0.0,
2239
+ 0.0,
2240
+ 0.0,
2241
+ 0.0,
2242
+ 0.0,
2243
+ 0.0,
2244
+ 0.0,
2245
+ 0.0,
2246
+ 0.0,
2247
+ 0.0,
2248
+ 0.0,
2249
+ 0.0,
2250
+ 0.0,
2251
+ 0.0,
2252
+ 0.0,
2253
+ 0.0,
2254
+ 0.0,
2255
+ 0.0,
2256
+ 0.0,
2257
+ 0.0,
2258
+ 0.0,
2259
+ 0.0,
2260
+ 0.0,
2261
+ 0.0,
2262
+ 0.0,
2263
+ 0.0,
2264
+ 0.0,
2265
+ 0.0,
2266
+ 0.0,
2267
+ 0.0,
2268
+ 0.0,
2269
+ 0.0,
2270
+ 0.0,
2271
+ 0.0,
2272
+ 0.0,
2273
+ 0.0,
2274
+ 0.0,
2275
+ 0.0,
2276
+ 0.0,
2277
+ 0.0,
2278
+ 0.0,
2279
+ 0.0,
2280
+ 0.0,
2281
+ 0.0,
2282
+ 0.0,
2283
+ 0.0,
2284
+ 0.0,
2285
+ 0.0,
2286
+ 0.0,
2287
+ 0.0,
2288
+ 0.0,
2289
+ 0.0,
2290
+ 0.0,
2291
+ 0.0,
2292
+ 0.0
2293
+ ],
2294
+ [
2295
+ 0.0,
2296
+ 0.0,
2297
+ 0.0,
2298
+ 0.0,
2299
+ 0.0,
2300
+ 0.0,
2301
+ 0.0,
2302
+ 0.0,
2303
+ 0.0,
2304
+ 0.0,
2305
+ 0.0,
2306
+ 0.0,
2307
+ 0.0,
2308
+ 0.0,
2309
+ 0.0,
2310
+ 0.0,
2311
+ 0.0,
2312
+ 0.0,
2313
+ 0.0,
2314
+ 0.0,
2315
+ 0.0,
2316
+ 0.0,
2317
+ 0.0,
2318
+ 0.0,
2319
+ 0.0,
2320
+ 0.0,
2321
+ 0.0,
2322
+ 0.0,
2323
+ 0.0,
2324
+ 0.0,
2325
+ 0.0,
2326
+ 0.0,
2327
+ 0.0,
2328
+ 0.0,
2329
+ 0.0,
2330
+ 0.0,
2331
+ 0.0,
2332
+ 0.0,
2333
+ 0.0,
2334
+ 0.0,
2335
+ 0.0,
2336
+ 0.0,
2337
+ 0.0,
2338
+ 0.0,
2339
+ 0.0,
2340
+ 0.0,
2341
+ 0.0,
2342
+ 0.0,
2343
+ 0.0,
2344
+ 0.0,
2345
+ 0.0,
2346
+ 0.0,
2347
+ 0.0,
2348
+ 0.0,
2349
+ 0.0,
2350
+ 0.0
2351
+ ],
2352
+ [
2353
+ 0.0,
2354
+ 0.0,
2355
+ 0.0,
2356
+ 0.0,
2357
+ 0.0,
2358
+ 0.0,
2359
+ 0.0,
2360
+ 0.0,
2361
+ 0.0,
2362
+ 0.0,
2363
+ 0.0,
2364
+ 0.0,
2365
+ 0.0,
2366
+ 0.0,
2367
+ 0.0,
2368
+ 0.0,
2369
+ 0.0,
2370
+ 0.0,
2371
+ 0.0,
2372
+ 0.0,
2373
+ 0.0,
2374
+ 0.0,
2375
+ 0.0,
2376
+ 0.0,
2377
+ 0.0,
2378
+ 0.0,
2379
+ 0.0,
2380
+ 0.0,
2381
+ 0.0,
2382
+ 0.0,
2383
+ 0.0,
2384
+ 0.0,
2385
+ 0.0,
2386
+ 0.0,
2387
+ 0.0,
2388
+ 0.0,
2389
+ 0.0,
2390
+ 0.0,
2391
+ 0.0,
2392
+ 0.0,
2393
+ 0.0,
2394
+ 0.0,
2395
+ 0.0,
2396
+ 0.0,
2397
+ 0.0,
2398
+ 0.0,
2399
+ 0.0,
2400
+ 0.0,
2401
+ 0.0,
2402
+ 0.0,
2403
+ 0.0,
2404
+ 0.0,
2405
+ 0.0,
2406
+ 0.0,
2407
+ 0.0,
2408
+ 0.0
2409
+ ],
2410
+ [
2411
+ 0.0,
2412
+ 0.0,
2413
+ 0.0,
2414
+ 0.0,
2415
+ 0.0,
2416
+ 0.0,
2417
+ 0.0,
2418
+ 0.0,
2419
+ 0.0,
2420
+ 0.0,
2421
+ 0.0,
2422
+ 0.0,
2423
+ 0.0,
2424
+ 0.0,
2425
+ 0.0,
2426
+ 0.0,
2427
+ 0.0,
2428
+ 0.0,
2429
+ 0.0,
2430
+ 0.0,
2431
+ 0.0,
2432
+ 0.0,
2433
+ 0.0,
2434
+ 0.0,
2435
+ 0.0,
2436
+ 0.0,
2437
+ 0.0,
2438
+ 0.0,
2439
+ 0.0,
2440
+ 0.0,
2441
+ 0.0,
2442
+ 0.0,
2443
+ 0.0,
2444
+ 0.0,
2445
+ 0.0,
2446
+ 0.0,
2447
+ 0.0,
2448
+ 0.0,
2449
+ 0.0,
2450
+ 0.0,
2451
+ 0.0,
2452
+ 0.0,
2453
+ 0.0,
2454
+ 0.0,
2455
+ 0.0,
2456
+ 0.0,
2457
+ 0.0,
2458
+ 0.0,
2459
+ 0.0,
2460
+ 0.0,
2461
+ 0.0,
2462
+ 0.0,
2463
+ 0.0,
2464
+ 0.0,
2465
+ 0.0,
2466
+ 0.0
2467
+ ],
2468
+ [
2469
+ 0.0,
2470
+ 0.0,
2471
+ 0.0,
2472
+ 0.0,
2473
+ 0.0,
2474
+ 0.0,
2475
+ 0.0,
2476
+ 0.0,
2477
+ 0.0,
2478
+ 0.0,
2479
+ 0.0,
2480
+ 0.0,
2481
+ 0.0,
2482
+ 0.0,
2483
+ 0.0,
2484
+ 0.0,
2485
+ 0.0,
2486
+ 0.0,
2487
+ 0.0,
2488
+ 0.0,
2489
+ 0.0,
2490
+ 0.0,
2491
+ 0.0,
2492
+ 0.0,
2493
+ 0.0,
2494
+ 0.0,
2495
+ 0.0,
2496
+ 0.0,
2497
+ 0.0,
2498
+ 0.0,
2499
+ 0.0,
2500
+ 0.0,
2501
+ 0.0,
2502
+ 0.0,
2503
+ 0.0,
2504
+ 0.0,
2505
+ 0.0,
2506
+ 0.0,
2507
+ 0.0,
2508
+ 0.0,
2509
+ 0.0,
2510
+ 0.0,
2511
+ 0.0,
2512
+ 0.0,
2513
+ 0.0,
2514
+ 0.0,
2515
+ 0.0,
2516
+ 0.0,
2517
+ 0.0,
2518
+ 0.0,
2519
+ 0.0,
2520
+ 0.0,
2521
+ 0.0,
2522
+ 0.0,
2523
+ 0.0,
2524
+ 0.0
2525
+ ],
2526
+ [
2527
+ 0.0,
2528
+ 0.0,
2529
+ 0.0,
2530
+ 0.0,
2531
+ 0.0,
2532
+ 0.0,
2533
+ 0.0,
2534
+ 0.0,
2535
+ 0.0,
2536
+ 0.0,
2537
+ 0.0,
2538
+ 0.0,
2539
+ 0.0,
2540
+ 0.0,
2541
+ 0.0,
2542
+ 0.0,
2543
+ 0.0,
2544
+ 0.0,
2545
+ 0.0,
2546
+ 0.0,
2547
+ 0.0,
2548
+ 0.0,
2549
+ 0.0,
2550
+ 0.0,
2551
+ 0.0,
2552
+ 0.0,
2553
+ 0.0,
2554
+ 0.0,
2555
+ 0.0,
2556
+ 0.0,
2557
+ 0.0,
2558
+ 0.0,
2559
+ 0.0,
2560
+ 0.0,
2561
+ 0.0,
2562
+ 0.0,
2563
+ 0.0,
2564
+ 0.0,
2565
+ 0.0,
2566
+ 0.0,
2567
+ 0.0,
2568
+ 0.0,
2569
+ 0.0,
2570
+ 0.0,
2571
+ 0.0,
2572
+ 0.0,
2573
+ 0.0,
2574
+ 0.0,
2575
+ 0.0,
2576
+ 0.0,
2577
+ 0.0,
2578
+ 0.0,
2579
+ 0.0,
2580
+ 0.0,
2581
+ 0.0,
2582
+ 0.0
2583
+ ],
2584
+ [
2585
+ 0.0,
2586
+ 0.0,
2587
+ 0.0,
2588
+ 0.0,
2589
+ 0.0,
2590
+ 0.0,
2591
+ 0.0,
2592
+ 0.0,
2593
+ 0.0,
2594
+ 0.0,
2595
+ 0.0,
2596
+ 0.0,
2597
+ 0.0,
2598
+ 0.0,
2599
+ 0.0,
2600
+ 0.0,
2601
+ 0.0,
2602
+ 0.0,
2603
+ 0.0,
2604
+ 0.0,
2605
+ 0.0,
2606
+ 0.0,
2607
+ 0.0,
2608
+ 0.0,
2609
+ 0.0,
2610
+ 0.0,
2611
+ 0.0,
2612
+ 0.0,
2613
+ 0.0,
2614
+ 0.0,
2615
+ 0.0,
2616
+ 0.0,
2617
+ 0.0,
2618
+ 0.0,
2619
+ 0.0,
2620
+ 0.0,
2621
+ 0.0,
2622
+ 0.0,
2623
+ 0.0,
2624
+ 0.0,
2625
+ 0.0,
2626
+ 0.0,
2627
+ 0.0,
2628
+ 0.0,
2629
+ 0.0,
2630
+ 0.0,
2631
+ 0.0,
2632
+ 0.0,
2633
+ 0.0,
2634
+ 0.0,
2635
+ 0.0,
2636
+ 0.0,
2637
+ 0.0,
2638
+ 0.0,
2639
+ 0.0,
2640
+ 0.0
2641
+ ],
2642
+ [
2643
+ 0.0,
2644
+ 0.0,
2645
+ 0.0,
2646
+ 0.0,
2647
+ 0.0,
2648
+ 0.0,
2649
+ 0.0,
2650
+ 0.0,
2651
+ 0.0,
2652
+ 0.0,
2653
+ 0.0,
2654
+ 0.0,
2655
+ 0.0,
2656
+ 0.0,
2657
+ 0.0,
2658
+ 0.0,
2659
+ 0.0,
2660
+ 0.0,
2661
+ 0.0,
2662
+ 0.0,
2663
+ 0.0,
2664
+ 0.0,
2665
+ 0.0,
2666
+ 0.0,
2667
+ 0.0,
2668
+ 0.0,
2669
+ 0.0,
2670
+ 0.0,
2671
+ 0.0,
2672
+ 0.0,
2673
+ 0.0,
2674
+ 0.0,
2675
+ 0.0,
2676
+ 0.0,
2677
+ 0.0,
2678
+ 0.0,
2679
+ 0.0,
2680
+ 0.0,
2681
+ 0.0,
2682
+ 0.0,
2683
+ 0.0,
2684
+ 0.0,
2685
+ 0.0,
2686
+ 0.0,
2687
+ 0.0,
2688
+ 0.0,
2689
+ 0.0,
2690
+ 0.0,
2691
+ 0.0,
2692
+ 0.0,
2693
+ 0.0,
2694
+ 0.0,
2695
+ 0.0,
2696
+ 0.0,
2697
+ 0.0,
2698
+ 0.0
2699
+ ],
2700
+ [
2701
+ 0.0,
2702
+ 0.0,
2703
+ 0.0,
2704
+ 0.0,
2705
+ 0.0,
2706
+ 0.0,
2707
+ 0.0,
2708
+ 0.0,
2709
+ 0.0,
2710
+ 0.0,
2711
+ 0.0,
2712
+ 0.0,
2713
+ 0.0,
2714
+ 0.0,
2715
+ 0.0,
2716
+ 0.0,
2717
+ 0.0,
2718
+ 0.0,
2719
+ 0.0,
2720
+ 0.0,
2721
+ 0.0,
2722
+ 0.0,
2723
+ 0.0,
2724
+ 0.0,
2725
+ 0.0,
2726
+ 0.0,
2727
+ 0.0,
2728
+ 0.0,
2729
+ 0.0,
2730
+ 0.0,
2731
+ 0.0,
2732
+ 0.0,
2733
+ 0.0,
2734
+ 0.0,
2735
+ 0.0,
2736
+ 0.0,
2737
+ 0.0,
2738
+ 0.0,
2739
+ 0.0,
2740
+ 0.0,
2741
+ 0.0,
2742
+ 0.0,
2743
+ 0.0,
2744
+ 0.0,
2745
+ 0.0,
2746
+ 0.0,
2747
+ 0.0,
2748
+ 0.0,
2749
+ 0.0,
2750
+ 0.0,
2751
+ 0.0,
2752
+ 0.0,
2753
+ 0.0,
2754
+ 0.0,
2755
+ 0.0,
2756
+ 0.0
2757
+ ],
2758
+ [
2759
+ 0.0,
2760
+ 0.0,
2761
+ 0.0,
2762
+ 0.0,
2763
+ 0.0,
2764
+ 0.0,
2765
+ 0.0,
2766
+ 0.0,
2767
+ 0.0,
2768
+ 0.0,
2769
+ 0.0,
2770
+ 0.0,
2771
+ 0.0,
2772
+ 0.0,
2773
+ 0.0,
2774
+ 0.0,
2775
+ 0.0,
2776
+ 0.0,
2777
+ 0.0,
2778
+ 0.0,
2779
+ 0.0,
2780
+ 0.0,
2781
+ 0.0,
2782
+ 0.0,
2783
+ 0.0,
2784
+ 0.0,
2785
+ 0.0,
2786
+ 0.0,
2787
+ 0.0,
2788
+ 0.0,
2789
+ 0.0,
2790
+ 0.0,
2791
+ 0.0,
2792
+ 0.0,
2793
+ 0.0,
2794
+ 0.0,
2795
+ 0.0,
2796
+ 0.0,
2797
+ 0.0,
2798
+ 0.0,
2799
+ 0.0,
2800
+ 0.0,
2801
+ 0.0,
2802
+ 0.0,
2803
+ 0.0,
2804
+ 0.0,
2805
+ 0.0,
2806
+ 0.0,
2807
+ 0.0,
2808
+ 0.0,
2809
+ 0.0,
2810
+ 0.0,
2811
+ 0.0,
2812
+ 0.0,
2813
+ 0.0,
2814
+ 0.0
2815
+ ],
2816
+ [
2817
+ 0.0,
2818
+ 0.0,
2819
+ 0.0,
2820
+ 0.0,
2821
+ 0.0,
2822
+ 0.0,
2823
+ 0.0,
2824
+ 0.0,
2825
+ 0.0,
2826
+ 0.0,
2827
+ 0.0,
2828
+ 0.0,
2829
+ 0.0,
2830
+ 0.0,
2831
+ 0.0,
2832
+ 0.0,
2833
+ 0.0,
2834
+ 0.0,
2835
+ 0.0,
2836
+ 0.0,
2837
+ 0.0,
2838
+ 0.0,
2839
+ 0.0,
2840
+ 0.0,
2841
+ 0.0,
2842
+ 0.0,
2843
+ 0.0,
2844
+ 0.0,
2845
+ 0.0,
2846
+ 0.0,
2847
+ 0.0,
2848
+ 0.0,
2849
+ 0.0,
2850
+ 0.0,
2851
+ 0.0,
2852
+ 0.0,
2853
+ 0.0,
2854
+ 0.0,
2855
+ 0.0,
2856
+ 0.0,
2857
+ 0.0,
2858
+ 0.0,
2859
+ 0.0,
2860
+ 0.0,
2861
+ 0.0,
2862
+ 0.0,
2863
+ 0.0,
2864
+ 0.0,
2865
+ 0.0,
2866
+ 0.0,
2867
+ 0.0,
2868
+ 0.0,
2869
+ 0.0,
2870
+ 0.0,
2871
+ 0.0,
2872
+ 0.0
2873
+ ],
2874
+ [
2875
+ 0.0,
2876
+ 0.0,
2877
+ 0.0,
2878
+ 0.0,
2879
+ 0.0,
2880
+ 0.0,
2881
+ 0.0,
2882
+ 0.0,
2883
+ 0.0,
2884
+ 0.0,
2885
+ 0.0,
2886
+ 0.0,
2887
+ 0.0,
2888
+ 0.0,
2889
+ 0.0,
2890
+ 0.0,
2891
+ 0.0,
2892
+ 0.0,
2893
+ 0.0,
2894
+ 0.0,
2895
+ 0.0,
2896
+ 0.0,
2897
+ 0.0,
2898
+ 0.0,
2899
+ 0.0,
2900
+ 0.0,
2901
+ 0.0,
2902
+ 0.0,
2903
+ 0.0,
2904
+ 0.0,
2905
+ 0.0,
2906
+ 0.0,
2907
+ 0.0,
2908
+ 0.0,
2909
+ 0.0,
2910
+ 0.0,
2911
+ 0.0,
2912
+ 0.0,
2913
+ 0.0,
2914
+ 0.0,
2915
+ 0.0,
2916
+ 0.0,
2917
+ 0.0,
2918
+ 0.0,
2919
+ 0.0,
2920
+ 0.0,
2921
+ 0.0,
2922
+ 0.0,
2923
+ 0.0,
2924
+ 0.0,
2925
+ 0.0,
2926
+ 0.0,
2927
+ 0.0,
2928
+ 0.0,
2929
+ 0.0,
2930
+ 0.0
2931
+ ],
2932
+ [
2933
+ 0.0,
2934
+ 0.0,
2935
+ 0.0,
2936
+ 0.0,
2937
+ 0.0,
2938
+ 0.0,
2939
+ 0.0,
2940
+ 0.0,
2941
+ 0.0,
2942
+ 0.0,
2943
+ 0.0,
2944
+ 0.0,
2945
+ 0.0,
2946
+ 0.0,
2947
+ 0.0,
2948
+ 0.0,
2949
+ 0.0,
2950
+ 0.0,
2951
+ 0.0,
2952
+ 0.0,
2953
+ 0.0,
2954
+ 0.0,
2955
+ 0.0,
2956
+ 0.0,
2957
+ 0.0,
2958
+ 0.0,
2959
+ 0.0,
2960
+ 0.0,
2961
+ 0.0,
2962
+ 0.0,
2963
+ 0.0,
2964
+ 0.0,
2965
+ 0.0,
2966
+ 0.0,
2967
+ 0.0,
2968
+ 0.0,
2969
+ 0.0,
2970
+ 0.0,
2971
+ 0.0,
2972
+ 0.0,
2973
+ 0.0,
2974
+ 0.0,
2975
+ 0.0,
2976
+ 0.0,
2977
+ 0.0,
2978
+ 0.0,
2979
+ 0.0,
2980
+ 0.0,
2981
+ 0.0,
2982
+ 0.0,
2983
+ 0.0,
2984
+ 0.0,
2985
+ 0.0,
2986
+ 0.0,
2987
+ 0.0,
2988
+ 0.0
2989
+ ],
2990
+ [
2991
+ 0.0,
2992
+ 0.0,
2993
+ 0.0,
2994
+ 0.0,
2995
+ 0.0,
2996
+ 0.0,
2997
+ 0.0,
2998
+ 0.0,
2999
+ 0.0,
3000
+ 0.0,
3001
+ 0.0,
3002
+ 0.0,
3003
+ 0.0,
3004
+ 0.0,
3005
+ 0.0,
3006
+ 0.0,
3007
+ 0.0,
3008
+ 0.0,
3009
+ 0.0,
3010
+ 0.0,
3011
+ 0.0,
3012
+ 0.0,
3013
+ 0.0,
3014
+ 0.0,
3015
+ 0.0,
3016
+ 0.0,
3017
+ 0.0,
3018
+ 0.0,
3019
+ 0.0,
3020
+ 0.0,
3021
+ 0.0,
3022
+ 0.0,
3023
+ 0.0,
3024
+ 0.0,
3025
+ 0.0,
3026
+ 0.0,
3027
+ 0.0,
3028
+ 0.0,
3029
+ 0.0,
3030
+ 0.0,
3031
+ 0.0,
3032
+ 0.0,
3033
+ 0.0,
3034
+ 0.0,
3035
+ 0.0,
3036
+ 0.0,
3037
+ 0.0,
3038
+ 0.0,
3039
+ 0.0,
3040
+ 0.0,
3041
+ 0.0,
3042
+ 0.0,
3043
+ 0.0,
3044
+ 0.0,
3045
+ 0.0,
3046
+ 0.0
3047
+ ],
3048
+ [
3049
+ 0.0,
3050
+ 0.0,
3051
+ 0.0,
3052
+ 0.0,
3053
+ 0.0,
3054
+ 0.0,
3055
+ 0.0,
3056
+ 0.0,
3057
+ 0.0,
3058
+ 0.0,
3059
+ 0.0,
3060
+ 0.0,
3061
+ 0.0,
3062
+ 0.0,
3063
+ 0.0,
3064
+ 0.0,
3065
+ 0.0,
3066
+ 0.0,
3067
+ 0.0,
3068
+ 0.0,
3069
+ 0.0,
3070
+ 0.0,
3071
+ 0.0,
3072
+ 0.0,
3073
+ 0.0,
3074
+ 0.0,
3075
+ 0.0,
3076
+ 0.0,
3077
+ 0.0,
3078
+ 0.0,
3079
+ 0.0,
3080
+ 0.0,
3081
+ 0.0,
3082
+ 0.0,
3083
+ 0.0,
3084
+ 0.0,
3085
+ 0.0,
3086
+ 0.0,
3087
+ 0.0,
3088
+ 0.0,
3089
+ 0.0,
3090
+ 0.0,
3091
+ 0.0,
3092
+ 0.0,
3093
+ 0.0,
3094
+ 0.0,
3095
+ 0.0,
3096
+ 0.0,
3097
+ 0.0,
3098
+ 0.0,
3099
+ 0.0,
3100
+ 0.0,
3101
+ 0.0,
3102
+ 0.0,
3103
+ 0.0,
3104
+ 0.0
3105
+ ],
3106
+ [
3107
+ 0.0,
3108
+ 0.0,
3109
+ 0.0,
3110
+ 0.0,
3111
+ 0.0,
3112
+ 0.0,
3113
+ 0.0,
3114
+ 0.0,
3115
+ 0.0,
3116
+ 0.0,
3117
+ 0.0,
3118
+ 0.0,
3119
+ 0.0,
3120
+ 0.0,
3121
+ 0.0,
3122
+ 0.0,
3123
+ 0.0,
3124
+ 0.0,
3125
+ 0.0,
3126
+ 0.0,
3127
+ 0.0,
3128
+ 0.0,
3129
+ 0.0,
3130
+ 0.0,
3131
+ 0.0,
3132
+ 0.0,
3133
+ 0.0,
3134
+ 0.0,
3135
+ 0.0,
3136
+ 0.0,
3137
+ 0.0,
3138
+ 0.0,
3139
+ 0.0,
3140
+ 0.0,
3141
+ 0.0,
3142
+ 0.0,
3143
+ 0.0,
3144
+ 0.0,
3145
+ 0.0,
3146
+ 0.0,
3147
+ 0.0,
3148
+ 0.0,
3149
+ 0.0,
3150
+ 0.0,
3151
+ 0.0,
3152
+ 0.0,
3153
+ 0.0,
3154
+ 0.0,
3155
+ 0.0,
3156
+ 0.0,
3157
+ 0.0,
3158
+ 0.0,
3159
+ 0.0,
3160
+ 0.0,
3161
+ 0.0,
3162
+ 0.0
3163
+ ],
3164
+ [
3165
+ 0.0,
3166
+ 0.0,
3167
+ 0.0,
3168
+ 0.0,
3169
+ 0.0,
3170
+ 0.0,
3171
+ 0.0,
3172
+ 0.0,
3173
+ 0.0,
3174
+ 0.0,
3175
+ 0.0,
3176
+ 0.0,
3177
+ 0.0,
3178
+ 0.0,
3179
+ 0.0,
3180
+ 0.0,
3181
+ 0.0,
3182
+ 0.0,
3183
+ 0.0,
3184
+ 0.0,
3185
+ 0.0,
3186
+ 0.0,
3187
+ 0.0,
3188
+ 0.0,
3189
+ 0.0,
3190
+ 0.0,
3191
+ 0.0,
3192
+ 0.0,
3193
+ 0.0,
3194
+ 0.0,
3195
+ 0.0,
3196
+ 0.0,
3197
+ 0.0,
3198
+ 0.0,
3199
+ 0.0,
3200
+ 0.0,
3201
+ 0.0,
3202
+ 0.0,
3203
+ 0.0,
3204
+ 0.0,
3205
+ 0.0,
3206
+ 0.0,
3207
+ 0.0,
3208
+ 0.0,
3209
+ 0.0,
3210
+ 0.0,
3211
+ 0.0,
3212
+ 0.0,
3213
+ 0.0,
3214
+ 0.0,
3215
+ 0.0,
3216
+ 0.0,
3217
+ 0.0,
3218
+ 0.0,
3219
+ 0.0,
3220
+ 0.0
3221
+ ],
3222
+ [
3223
+ 0.0,
3224
+ 0.0,
3225
+ 0.0,
3226
+ 0.0,
3227
+ 0.0,
3228
+ 0.0,
3229
+ 0.0,
3230
+ 0.0,
3231
+ 0.0,
3232
+ 0.0,
3233
+ 0.0,
3234
+ 0.0,
3235
+ 0.0,
3236
+ 0.0,
3237
+ 0.0,
3238
+ 0.0,
3239
+ 0.0,
3240
+ 0.0,
3241
+ 0.0,
3242
+ 0.0,
3243
+ 0.0,
3244
+ 0.0,
3245
+ 0.0,
3246
+ 0.0,
3247
+ 0.0,
3248
+ 0.0,
3249
+ 0.0,
3250
+ 0.0,
3251
+ 0.0,
3252
+ 0.0,
3253
+ 0.0,
3254
+ 0.0,
3255
+ 0.0,
3256
+ 0.0,
3257
+ 0.0,
3258
+ 0.0,
3259
+ 0.0,
3260
+ 0.0,
3261
+ 0.0,
3262
+ 0.0,
3263
+ 0.0,
3264
+ 0.0,
3265
+ 0.0,
3266
+ 0.0,
3267
+ 0.0,
3268
+ 0.0,
3269
+ 0.0,
3270
+ 0.0,
3271
+ 0.0,
3272
+ 0.0,
3273
+ 0.0,
3274
+ 0.0,
3275
+ 0.0,
3276
+ 0.0,
3277
+ 0.0,
3278
+ 0.0
3279
+ ],
3280
+ [
3281
+ 0.0,
3282
+ 0.0,
3283
+ 0.0,
3284
+ 0.0,
3285
+ 0.0,
3286
+ 0.0,
3287
+ 0.0,
3288
+ 0.0,
3289
+ 0.0,
3290
+ 0.0,
3291
+ 0.0,
3292
+ 0.0,
3293
+ 0.0,
3294
+ 0.0,
3295
+ 0.0,
3296
+ 0.0,
3297
+ 0.0,
3298
+ 0.0,
3299
+ 0.0,
3300
+ 0.0,
3301
+ 0.0,
3302
+ 0.0,
3303
+ 0.0,
3304
+ 0.0,
3305
+ 0.0,
3306
+ 0.0,
3307
+ 0.0,
3308
+ 0.0,
3309
+ 0.0,
3310
+ 0.0,
3311
+ 0.0,
3312
+ 0.0,
3313
+ 0.0,
3314
+ 0.0,
3315
+ 0.0,
3316
+ 0.0,
3317
+ 0.0,
3318
+ 0.0,
3319
+ 0.0,
3320
+ 0.0,
3321
+ 0.0,
3322
+ 0.0,
3323
+ 0.0,
3324
+ 0.0,
3325
+ 0.0,
3326
+ 0.0,
3327
+ 0.0,
3328
+ 0.0,
3329
+ 0.0,
3330
+ 0.0,
3331
+ 0.0,
3332
+ 0.0,
3333
+ 0.0,
3334
+ 0.0,
3335
+ 0.0,
3336
+ 0.0
3337
+ ],
3338
+ [
3339
+ 0.0,
3340
+ 0.0,
3341
+ 0.0,
3342
+ 0.0,
3343
+ 0.0,
3344
+ 0.0,
3345
+ 0.0,
3346
+ 0.0,
3347
+ 0.0,
3348
+ 0.0,
3349
+ 0.0,
3350
+ 0.0,
3351
+ 0.0,
3352
+ 0.0,
3353
+ 0.0,
3354
+ 0.0,
3355
+ 0.0,
3356
+ 0.0,
3357
+ 0.0,
3358
+ 0.0,
3359
+ 0.0,
3360
+ 0.0,
3361
+ 0.0,
3362
+ 0.0,
3363
+ 0.0,
3364
+ 0.0,
3365
+ 0.0,
3366
+ 0.0,
3367
+ 0.0,
3368
+ 0.0,
3369
+ 0.0,
3370
+ 0.0,
3371
+ 0.0,
3372
+ 0.0,
3373
+ 0.0,
3374
+ 0.0,
3375
+ 0.0,
3376
+ 0.0,
3377
+ 0.0,
3378
+ 0.0,
3379
+ 0.0,
3380
+ 0.0,
3381
+ 0.0,
3382
+ 0.0,
3383
+ 0.0,
3384
+ 0.0,
3385
+ 0.0,
3386
+ 0.0,
3387
+ 0.0,
3388
+ 0.0,
3389
+ 0.0,
3390
+ 0.0,
3391
+ 0.0,
3392
+ 0.0,
3393
+ 0.0,
3394
+ 0.0
3395
+ ],
3396
+ [
3397
+ 0.0,
3398
+ 0.0,
3399
+ 0.0,
3400
+ 0.0,
3401
+ 0.0,
3402
+ 0.0,
3403
+ 0.0,
3404
+ 0.0,
3405
+ 0.0,
3406
+ 0.0,
3407
+ 0.0,
3408
+ 0.0,
3409
+ 0.0,
3410
+ 0.0,
3411
+ 0.0,
3412
+ 0.0,
3413
+ 0.0,
3414
+ 0.0,
3415
+ 0.0,
3416
+ 0.0,
3417
+ 0.0,
3418
+ 0.0,
3419
+ 0.0,
3420
+ 0.0,
3421
+ 0.0,
3422
+ 0.0,
3423
+ 0.0,
3424
+ 0.0,
3425
+ 0.0,
3426
+ 0.0,
3427
+ 0.0,
3428
+ 0.0,
3429
+ 0.0,
3430
+ 0.0,
3431
+ 0.0,
3432
+ 0.0,
3433
+ 0.0,
3434
+ 0.0,
3435
+ 0.0,
3436
+ 0.0,
3437
+ 0.0,
3438
+ 0.0,
3439
+ 0.0,
3440
+ 0.0,
3441
+ 0.0,
3442
+ 0.0,
3443
+ 0.0,
3444
+ 0.0,
3445
+ 0.0,
3446
+ 0.0,
3447
+ 0.0,
3448
+ 0.0,
3449
+ 0.0,
3450
+ 0.0,
3451
+ 0.0,
3452
+ 0.0
3453
+ ],
3454
+ [
3455
+ 0.0,
3456
+ 0.0,
3457
+ 0.0,
3458
+ 0.0,
3459
+ 0.0,
3460
+ 0.0,
3461
+ 0.0,
3462
+ 0.0,
3463
+ 0.0,
3464
+ 0.0,
3465
+ 0.0,
3466
+ 0.0,
3467
+ 0.0,
3468
+ 0.0,
3469
+ 0.0,
3470
+ 0.0,
3471
+ 0.0,
3472
+ 0.0,
3473
+ 0.0,
3474
+ 0.0,
3475
+ 0.0,
3476
+ 0.0,
3477
+ 0.0,
3478
+ 0.0,
3479
+ 0.0,
3480
+ 0.0,
3481
+ 0.0,
3482
+ 0.0,
3483
+ 0.0,
3484
+ 0.0,
3485
+ 0.0,
3486
+ 0.0,
3487
+ 0.0,
3488
+ 0.0,
3489
+ 0.0,
3490
+ 0.0,
3491
+ 0.0,
3492
+ 0.0,
3493
+ 0.0,
3494
+ 0.0,
3495
+ 0.0,
3496
+ 0.0,
3497
+ 0.0,
3498
+ 0.0,
3499
+ 0.0,
3500
+ 0.0,
3501
+ 0.0,
3502
+ 0.0,
3503
+ 0.0,
3504
+ 0.0,
3505
+ 0.0,
3506
+ 0.0,
3507
+ 0.0,
3508
+ 0.0,
3509
+ 0.0,
3510
+ 0.0
3511
+ ],
3512
+ [
3513
+ 0.0,
3514
+ 0.0,
3515
+ 0.0,
3516
+ 0.0,
3517
+ 0.0,
3518
+ 0.0,
3519
+ 0.0,
3520
+ 0.0,
3521
+ 0.0,
3522
+ 0.0,
3523
+ 0.0,
3524
+ 0.0,
3525
+ 0.0,
3526
+ 0.0,
3527
+ 0.0,
3528
+ 0.0,
3529
+ 0.0,
3530
+ 0.0,
3531
+ 0.0,
3532
+ 0.0,
3533
+ 0.0,
3534
+ 0.0,
3535
+ 0.0,
3536
+ 0.0,
3537
+ 0.0,
3538
+ 0.0,
3539
+ 0.0,
3540
+ 0.0,
3541
+ 0.0,
3542
+ 0.0,
3543
+ 0.0,
3544
+ 0.0,
3545
+ 0.0,
3546
+ 0.0,
3547
+ 0.0,
3548
+ 0.0,
3549
+ 0.0,
3550
+ 0.0,
3551
+ 0.0,
3552
+ 0.0,
3553
+ 0.0,
3554
+ 0.0,
3555
+ 0.0,
3556
+ 0.0,
3557
+ 0.0,
3558
+ 0.0,
3559
+ 0.0,
3560
+ 0.0,
3561
+ 0.0,
3562
+ 0.0,
3563
+ 0.0,
3564
+ 0.0,
3565
+ 0.0,
3566
+ 0.0,
3567
+ 0.0,
3568
+ 0.0
3569
+ ],
3570
+ [
3571
+ 0.0,
3572
+ 0.0,
3573
+ 0.0,
3574
+ 0.0,
3575
+ 0.0,
3576
+ 0.0,
3577
+ 0.0,
3578
+ 0.0,
3579
+ 0.0,
3580
+ 0.0,
3581
+ 0.0,
3582
+ 0.0,
3583
+ 0.0,
3584
+ 0.0,
3585
+ 0.0,
3586
+ 0.0,
3587
+ 0.0,
3588
+ 0.0,
3589
+ 0.0,
3590
+ 0.0,
3591
+ 0.0,
3592
+ 0.0,
3593
+ 0.0,
3594
+ 0.0,
3595
+ 0.0,
3596
+ 0.0,
3597
+ 0.0,
3598
+ 0.0,
3599
+ 0.0,
3600
+ 0.0,
3601
+ 0.0,
3602
+ 0.0,
3603
+ 0.0,
3604
+ 0.0,
3605
+ 0.0,
3606
+ 0.0,
3607
+ 0.0,
3608
+ 0.0,
3609
+ 0.0,
3610
+ 0.0,
3611
+ 0.0,
3612
+ 0.0,
3613
+ 0.0,
3614
+ 0.0,
3615
+ 0.0,
3616
+ 0.0,
3617
+ 0.0,
3618
+ 0.0,
3619
+ 0.0,
3620
+ 0.0,
3621
+ 0.0,
3622
+ 0.0,
3623
+ 0.0,
3624
+ 0.0,
3625
+ 0.0,
3626
+ 0.0
3627
+ ],
3628
+ [
3629
+ 0.0,
3630
+ 0.0,
3631
+ 0.0,
3632
+ 0.0,
3633
+ 0.0,
3634
+ 0.0,
3635
+ 0.0,
3636
+ 0.0,
3637
+ 0.0,
3638
+ 0.0,
3639
+ 0.0,
3640
+ 0.0,
3641
+ 0.0,
3642
+ 0.0,
3643
+ 0.0,
3644
+ 0.0,
3645
+ 0.0,
3646
+ 0.0,
3647
+ 0.0,
3648
+ 0.0,
3649
+ 0.0,
3650
+ 0.0,
3651
+ 0.0,
3652
+ 0.0,
3653
+ 0.0,
3654
+ 0.0,
3655
+ 0.0,
3656
+ 0.0,
3657
+ 0.0,
3658
+ 0.0,
3659
+ 0.0,
3660
+ 0.0,
3661
+ 0.0,
3662
+ 0.0,
3663
+ 0.0,
3664
+ 0.0,
3665
+ 0.0,
3666
+ 0.0,
3667
+ 0.0,
3668
+ 0.0,
3669
+ 0.0,
3670
+ 0.0,
3671
+ 0.0,
3672
+ 0.0,
3673
+ 0.0,
3674
+ 0.0,
3675
+ 0.0,
3676
+ 0.0,
3677
+ 0.0,
3678
+ 0.0,
3679
+ 0.0,
3680
+ 0.0,
3681
+ 0.0,
3682
+ 0.0,
3683
+ 0.0,
3684
+ 0.0
3685
+ ],
3686
+ [
3687
+ 0.0,
3688
+ 0.0,
3689
+ 0.0,
3690
+ 0.0,
3691
+ 0.0,
3692
+ 0.0,
3693
+ 0.0,
3694
+ 0.0,
3695
+ 0.0,
3696
+ 0.0,
3697
+ 0.0,
3698
+ 0.0,
3699
+ 0.0,
3700
+ 0.0,
3701
+ 0.0,
3702
+ 0.0,
3703
+ 0.0,
3704
+ 0.0,
3705
+ 0.0,
3706
+ 0.0,
3707
+ 0.0,
3708
+ 0.0,
3709
+ 0.0,
3710
+ 0.0,
3711
+ 0.0,
3712
+ 0.0,
3713
+ 0.0,
3714
+ 0.0,
3715
+ 0.0,
3716
+ 0.0,
3717
+ 0.0,
3718
+ 0.0,
3719
+ 0.0,
3720
+ 0.0,
3721
+ 0.0,
3722
+ 0.0,
3723
+ 0.0,
3724
+ 0.0,
3725
+ 0.0,
3726
+ 0.0,
3727
+ 0.0,
3728
+ 0.0,
3729
+ 0.0,
3730
+ 0.0,
3731
+ 0.0,
3732
+ 0.0,
3733
+ 0.0,
3734
+ 0.0,
3735
+ 0.0,
3736
+ 0.0,
3737
+ 0.0,
3738
+ 0.0,
3739
+ 0.0,
3740
+ 0.0,
3741
+ 0.0,
3742
+ 0.0
3743
+ ],
3744
+ [
3745
+ 0.0,
3746
+ 0.0,
3747
+ 0.0,
3748
+ 0.0,
3749
+ 0.0,
3750
+ 0.0,
3751
+ 0.0,
3752
+ 0.0,
3753
+ 0.0,
3754
+ 0.0,
3755
+ 0.0,
3756
+ 0.0,
3757
+ 0.0,
3758
+ 0.0,
3759
+ 0.0,
3760
+ 0.0,
3761
+ 0.0,
3762
+ 0.0,
3763
+ 0.0,
3764
+ 0.0,
3765
+ 0.0,
3766
+ 0.0,
3767
+ 0.0,
3768
+ 0.0,
3769
+ 0.0,
3770
+ 0.0,
3771
+ 0.0,
3772
+ 0.0,
3773
+ 0.0,
3774
+ 0.0,
3775
+ 0.0,
3776
+ 0.0,
3777
+ 0.0,
3778
+ 0.0,
3779
+ 0.0,
3780
+ 0.0,
3781
+ 0.0,
3782
+ 0.0,
3783
+ 0.0,
3784
+ 0.0,
3785
+ 0.0,
3786
+ 0.0,
3787
+ 0.0,
3788
+ 0.0,
3789
+ 0.0,
3790
+ 0.0,
3791
+ 0.0,
3792
+ 0.0,
3793
+ 0.0,
3794
+ 0.0,
3795
+ 0.0,
3796
+ 0.0,
3797
+ 0.0,
3798
+ 0.0,
3799
+ 0.0,
3800
+ 0.0
3801
+ ],
3802
+ [
3803
+ 0.0,
3804
+ 0.0,
3805
+ 0.0,
3806
+ 0.0,
3807
+ 0.0,
3808
+ 0.0,
3809
+ 0.0,
3810
+ 0.0,
3811
+ 0.0,
3812
+ 0.0,
3813
+ 0.0,
3814
+ 0.0,
3815
+ 0.0,
3816
+ 0.0,
3817
+ 0.0,
3818
+ 0.0,
3819
+ 0.0,
3820
+ 0.0,
3821
+ 0.0,
3822
+ 0.0,
3823
+ 0.0,
3824
+ 0.0,
3825
+ 0.0,
3826
+ 0.0,
3827
+ 0.0,
3828
+ 0.0,
3829
+ 0.0,
3830
+ 0.0,
3831
+ 0.0,
3832
+ 0.0,
3833
+ 0.0,
3834
+ 0.0,
3835
+ 0.0,
3836
+ 0.0,
3837
+ 0.0,
3838
+ 0.0,
3839
+ 0.0,
3840
+ 0.0,
3841
+ 0.0,
3842
+ 0.0,
3843
+ 0.0,
3844
+ 0.0,
3845
+ 0.0,
3846
+ 0.0,
3847
+ 0.0,
3848
+ 0.0,
3849
+ 0.0,
3850
+ 0.0,
3851
+ 0.0,
3852
+ 0.0,
3853
+ 0.0,
3854
+ 0.0,
3855
+ 0.0,
3856
+ 0.0,
3857
+ 0.0,
3858
+ 0.0
3859
+ ],
3860
+ [
3861
+ 0.0,
3862
+ 0.0,
3863
+ 0.0,
3864
+ 0.0,
3865
+ 0.0,
3866
+ 0.0,
3867
+ 0.0,
3868
+ 0.0,
3869
+ 0.0,
3870
+ 0.0,
3871
+ 0.0,
3872
+ 0.0,
3873
+ 0.0,
3874
+ 0.0,
3875
+ 0.0,
3876
+ 0.0,
3877
+ 0.0,
3878
+ 0.0,
3879
+ 0.0,
3880
+ 0.0,
3881
+ 0.0,
3882
+ 0.0,
3883
+ 0.0,
3884
+ 0.0,
3885
+ 0.0,
3886
+ 0.0,
3887
+ 0.0,
3888
+ 0.0,
3889
+ 0.0,
3890
+ 0.0,
3891
+ 0.0,
3892
+ 0.0,
3893
+ 0.0,
3894
+ 0.0,
3895
+ 0.0,
3896
+ 0.0,
3897
+ 0.0,
3898
+ 0.0,
3899
+ 0.0,
3900
+ 0.0,
3901
+ 0.0,
3902
+ 0.0,
3903
+ 0.0,
3904
+ 0.0,
3905
+ 0.0,
3906
+ 0.0,
3907
+ 0.0,
3908
+ 0.0,
3909
+ 0.0,
3910
+ 0.0,
3911
+ 0.0,
3912
+ 0.0,
3913
+ 0.0,
3914
+ 0.0,
3915
+ 0.0,
3916
+ 0.0
3917
+ ],
3918
+ [
3919
+ 0.0,
3920
+ 0.0,
3921
+ 0.0,
3922
+ 0.0,
3923
+ 0.0,
3924
+ 0.0,
3925
+ 0.0,
3926
+ 0.0,
3927
+ 0.0,
3928
+ 0.0,
3929
+ 0.0,
3930
+ 0.0,
3931
+ 0.0,
3932
+ 0.0,
3933
+ 0.0,
3934
+ 0.0,
3935
+ 0.0,
3936
+ 0.0,
3937
+ 0.0,
3938
+ 0.0,
3939
+ 0.0,
3940
+ 0.0,
3941
+ 0.0,
3942
+ 0.0,
3943
+ 0.0,
3944
+ 0.0,
3945
+ 0.0,
3946
+ 0.0,
3947
+ 0.0,
3948
+ 0.0,
3949
+ 0.0,
3950
+ 0.0,
3951
+ 0.0,
3952
+ 0.0,
3953
+ 0.0,
3954
+ 0.0,
3955
+ 0.0,
3956
+ 0.0,
3957
+ 0.0,
3958
+ 0.0,
3959
+ 0.0,
3960
+ 0.0,
3961
+ 0.0,
3962
+ 0.0,
3963
+ 0.0,
3964
+ 0.0,
3965
+ 0.0,
3966
+ 0.0,
3967
+ 0.0,
3968
+ 0.0,
3969
+ 0.0,
3970
+ 0.0,
3971
+ 0.0,
3972
+ 0.0,
3973
+ 0.0,
3974
+ 0.0
3975
+ ],
3976
+ [
3977
+ 0.0,
3978
+ 0.0,
3979
+ 0.0,
3980
+ 0.0,
3981
+ 0.0,
3982
+ 0.0,
3983
+ 0.0,
3984
+ 0.0,
3985
+ 0.0,
3986
+ 0.0,
3987
+ 0.0,
3988
+ 0.0,
3989
+ 0.0,
3990
+ 0.0,
3991
+ 0.0,
3992
+ 0.0,
3993
+ 0.0,
3994
+ 0.0,
3995
+ 0.0,
3996
+ 0.0,
3997
+ 0.0,
3998
+ 0.0,
3999
+ 0.0,
4000
+ 0.0,
4001
+ 0.0,
4002
+ 0.0,
4003
+ 0.0,
4004
+ 0.0,
4005
+ 0.0,
4006
+ 0.0,
4007
+ 0.0,
4008
+ 0.0,
4009
+ 0.0,
4010
+ 0.0,
4011
+ 0.0,
4012
+ 0.0,
4013
+ 0.0,
4014
+ 0.0,
4015
+ 0.0,
4016
+ 0.0,
4017
+ 0.0,
4018
+ 0.0,
4019
+ 0.0,
4020
+ 0.0,
4021
+ 0.0,
4022
+ 0.0,
4023
+ 0.0,
4024
+ 0.0,
4025
+ 0.0,
4026
+ 0.0,
4027
+ 0.0,
4028
+ 0.0,
4029
+ 0.0,
4030
+ 0.0,
4031
+ 0.0,
4032
+ 0.0
4033
+ ],
4034
+ [
4035
+ 0.0,
4036
+ 0.0,
4037
+ 0.0,
4038
+ 0.0,
4039
+ 0.0,
4040
+ 0.0,
4041
+ 0.0,
4042
+ 0.0,
4043
+ 0.0,
4044
+ 0.0,
4045
+ 0.0,
4046
+ 0.0,
4047
+ 1.0,
4048
+ 1.0,
4049
+ 1.0,
4050
+ 0.0,
4051
+ 0.0,
4052
+ 0.0,
4053
+ 0.0,
4054
+ 0.0,
4055
+ 0.0,
4056
+ 0.0,
4057
+ 0.0,
4058
+ 0.0,
4059
+ 0.0,
4060
+ 0.0,
4061
+ 0.0,
4062
+ 0.0,
4063
+ 0.0,
4064
+ 0.0,
4065
+ 0.0,
4066
+ 0.0,
4067
+ 0.0,
4068
+ 0.0,
4069
+ 0.0,
4070
+ 0.0,
4071
+ 0.0,
4072
+ 0.0,
4073
+ 0.0,
4074
+ 0.0,
4075
+ 0.0,
4076
+ 0.0,
4077
+ 0.0,
4078
+ 0.0,
4079
+ 0.0,
4080
+ 0.0,
4081
+ 0.0,
4082
+ 0.0,
4083
+ 0.0,
4084
+ 0.0,
4085
+ 0.0,
4086
+ 0.0,
4087
+ 0.0,
4088
+ 0.0,
4089
+ 0.0,
4090
+ 0.0
4091
+ ],
4092
+ [
4093
+ 0.0,
4094
+ 0.0,
4095
+ 0.0,
4096
+ 0.0,
4097
+ 0.0,
4098
+ 0.0,
4099
+ 0.0,
4100
+ 0.0,
4101
+ 0.0,
4102
+ 0.0,
4103
+ 0.0,
4104
+ 1.0,
4105
+ 1.0,
4106
+ 1.0,
4107
+ 1.0,
4108
+ 1.0,
4109
+ 1.0,
4110
+ 0.0,
4111
+ 0.0,
4112
+ 0.0,
4113
+ 0.0,
4114
+ 0.0,
4115
+ 0.0,
4116
+ 0.0,
4117
+ 0.0,
4118
+ 0.0,
4119
+ 0.0,
4120
+ 0.0,
4121
+ 0.0,
4122
+ 0.0,
4123
+ 0.0,
4124
+ 0.0,
4125
+ 0.0,
4126
+ 0.0,
4127
+ 0.0,
4128
+ 0.0,
4129
+ 0.0,
4130
+ 0.0,
4131
+ 0.0,
4132
+ 0.0,
4133
+ 0.0,
4134
+ 0.0,
4135
+ 0.0,
4136
+ 0.0,
4137
+ 0.0,
4138
+ 0.0,
4139
+ 0.0,
4140
+ 0.0,
4141
+ 0.0,
4142
+ 0.0,
4143
+ 0.0,
4144
+ 0.0,
4145
+ 0.0,
4146
+ 0.0,
4147
+ 0.0,
4148
+ 0.0
4149
+ ],
4150
+ [
4151
+ 0.0,
4152
+ 0.0,
4153
+ 0.0,
4154
+ 0.0,
4155
+ 0.0,
4156
+ 0.0,
4157
+ 0.0,
4158
+ 0.0,
4159
+ 0.0,
4160
+ 0.0,
4161
+ 0.0,
4162
+ 1.0,
4163
+ 1.0,
4164
+ 2.0,
4165
+ 2.0,
4166
+ 2.0,
4167
+ 1.0,
4168
+ 0.0,
4169
+ 0.0,
4170
+ 0.0,
4171
+ 0.0,
4172
+ 0.0,
4173
+ 0.0,
4174
+ 0.0,
4175
+ 0.0,
4176
+ 0.0,
4177
+ 0.0,
4178
+ 0.0,
4179
+ 0.0,
4180
+ 0.0,
4181
+ 0.0,
4182
+ 0.0,
4183
+ 0.0,
4184
+ 0.0,
4185
+ 0.0,
4186
+ 0.0,
4187
+ 0.0,
4188
+ 0.0,
4189
+ 0.0,
4190
+ 0.0,
4191
+ 0.0,
4192
+ 0.0,
4193
+ 0.0,
4194
+ 0.0,
4195
+ 0.0,
4196
+ 0.0,
4197
+ 0.0,
4198
+ 0.0,
4199
+ 0.0,
4200
+ 0.0,
4201
+ 0.0,
4202
+ 0.0,
4203
+ 0.0,
4204
+ 0.0,
4205
+ 0.0,
4206
+ 0.0
4207
+ ],
4208
+ [
4209
+ 0.0,
4210
+ 0.0,
4211
+ 0.0,
4212
+ 0.0,
4213
+ 0.0,
4214
+ 0.0,
4215
+ 0.0,
4216
+ 0.0,
4217
+ 0.0,
4218
+ 0.0,
4219
+ 0.0,
4220
+ 1.0,
4221
+ 1.0,
4222
+ 2.0,
4223
+ 2.0,
4224
+ 2.0,
4225
+ 1.0,
4226
+ 0.0,
4227
+ 0.0,
4228
+ 0.0,
4229
+ 0.0,
4230
+ 0.0,
4231
+ 0.0,
4232
+ 0.0,
4233
+ 0.0,
4234
+ 0.0,
4235
+ 0.0,
4236
+ 0.0,
4237
+ 0.0,
4238
+ 0.0,
4239
+ 0.0,
4240
+ 0.0,
4241
+ 0.0,
4242
+ 0.0,
4243
+ 0.0,
4244
+ 0.0,
4245
+ 0.0,
4246
+ 0.0,
4247
+ 0.0,
4248
+ 0.0,
4249
+ 0.0,
4250
+ 0.0,
4251
+ 0.0,
4252
+ 0.0,
4253
+ 0.0,
4254
+ 0.0,
4255
+ 0.0,
4256
+ 0.0,
4257
+ 0.0,
4258
+ 0.0,
4259
+ 0.0,
4260
+ 0.0,
4261
+ 0.0,
4262
+ 0.0,
4263
+ 0.0,
4264
+ 0.0
4265
+ ],
4266
+ [
4267
+ 0.0,
4268
+ 0.0,
4269
+ 0.0,
4270
+ 0.0,
4271
+ 0.0,
4272
+ 0.0,
4273
+ 0.0,
4274
+ 1.0,
4275
+ 1.0,
4276
+ 1.0,
4277
+ 2.0,
4278
+ 2.0,
4279
+ 2.0,
4280
+ 2.0,
4281
+ 2.0,
4282
+ 2.0,
4283
+ 1.0,
4284
+ 0.0,
4285
+ 0.0,
4286
+ 0.0,
4287
+ 0.0,
4288
+ 0.0,
4289
+ 0.0,
4290
+ 0.0,
4291
+ 0.0,
4292
+ 0.0,
4293
+ 0.0,
4294
+ 0.0,
4295
+ 0.0,
4296
+ 0.0,
4297
+ 0.0,
4298
+ 0.0,
4299
+ 0.0,
4300
+ 0.0,
4301
+ 0.0,
4302
+ 0.0,
4303
+ 0.0,
4304
+ 0.0,
4305
+ 0.0,
4306
+ 0.0,
4307
+ 0.0,
4308
+ 0.0,
4309
+ 0.0,
4310
+ 0.0,
4311
+ 0.0,
4312
+ 0.0,
4313
+ 0.0,
4314
+ 0.0,
4315
+ 0.0,
4316
+ 0.0,
4317
+ 0.0,
4318
+ 0.0,
4319
+ 0.0,
4320
+ 0.0,
4321
+ 0.0,
4322
+ 0.0
4323
+ ],
4324
+ [
4325
+ 0.0,
4326
+ 0.0,
4327
+ 0.0,
4328
+ 0.0,
4329
+ 0.0,
4330
+ 0.0,
4331
+ 0.0,
4332
+ 1.0,
4333
+ 1.0,
4334
+ 1.0,
4335
+ 2.0,
4336
+ 2.0,
4337
+ 2.0,
4338
+ 2.0,
4339
+ 1.0,
4340
+ 1.0,
4341
+ 1.0,
4342
+ 0.0,
4343
+ 0.0,
4344
+ 0.0,
4345
+ 0.0,
4346
+ 0.0,
4347
+ 0.0,
4348
+ 0.0,
4349
+ 0.0,
4350
+ 0.0,
4351
+ 0.0,
4352
+ 0.0,
4353
+ 0.0,
4354
+ 0.0,
4355
+ 0.0,
4356
+ 0.0,
4357
+ 0.0,
4358
+ 0.0,
4359
+ 0.0,
4360
+ 0.0,
4361
+ 0.0,
4362
+ 0.0,
4363
+ 0.0,
4364
+ 0.0,
4365
+ 0.0,
4366
+ 0.0,
4367
+ 0.0,
4368
+ 0.0,
4369
+ 0.0,
4370
+ 0.0,
4371
+ 0.0,
4372
+ 0.0,
4373
+ 0.0,
4374
+ 0.0,
4375
+ 0.0,
4376
+ 0.0,
4377
+ 0.0,
4378
+ 0.0,
4379
+ 0.0,
4380
+ 0.0
4381
+ ],
4382
+ [
4383
+ 0.0,
4384
+ 0.0,
4385
+ 0.0,
4386
+ 0.0,
4387
+ 0.0,
4388
+ 0.0,
4389
+ 1.0,
4390
+ 2.0,
4391
+ 2.0,
4392
+ 2.0,
4393
+ 2.0,
4394
+ 2.0,
4395
+ 2.0,
4396
+ 2.0,
4397
+ 2.0,
4398
+ 1.0,
4399
+ 1.0,
4400
+ 0.0,
4401
+ 0.0,
4402
+ 0.0,
4403
+ 0.0,
4404
+ 0.0,
4405
+ 0.0,
4406
+ 0.0,
4407
+ 0.0,
4408
+ 0.0,
4409
+ 0.0,
4410
+ 0.0,
4411
+ 0.0,
4412
+ 0.0,
4413
+ 0.0,
4414
+ 0.0,
4415
+ 0.0,
4416
+ 0.0,
4417
+ 0.0,
4418
+ 0.0,
4419
+ 0.0,
4420
+ 0.0,
4421
+ 0.0,
4422
+ 0.0,
4423
+ 0.0,
4424
+ 0.0,
4425
+ 0.0,
4426
+ 0.0,
4427
+ 0.0,
4428
+ 0.0,
4429
+ 0.0,
4430
+ 0.0,
4431
+ 0.0,
4432
+ 0.0,
4433
+ 0.0,
4434
+ 0.0,
4435
+ 0.0,
4436
+ 0.0,
4437
+ 0.0,
4438
+ 0.0
4439
+ ],
4440
+ [
4441
+ 0.0,
4442
+ 0.0,
4443
+ 0.0,
4444
+ 0.0,
4445
+ 0.0,
4446
+ 2.0,
4447
+ 2.0,
4448
+ 2.0,
4449
+ 2.0,
4450
+ 2.0,
4451
+ 2.0,
4452
+ 2.0,
4453
+ 2.0,
4454
+ 2.0,
4455
+ 2.0,
4456
+ 2.0,
4457
+ 1.0,
4458
+ 0.0,
4459
+ 0.0,
4460
+ 0.0,
4461
+ 0.0,
4462
+ 0.0,
4463
+ 0.0,
4464
+ 0.0,
4465
+ 0.0,
4466
+ 0.0,
4467
+ 0.0,
4468
+ 0.0,
4469
+ 0.0,
4470
+ 0.0,
4471
+ 0.0,
4472
+ 0.0,
4473
+ 0.0,
4474
+ 0.0,
4475
+ 0.0,
4476
+ 0.0,
4477
+ 0.0,
4478
+ 0.0,
4479
+ 0.0,
4480
+ 0.0,
4481
+ 0.0,
4482
+ 0.0,
4483
+ 0.0,
4484
+ 0.0,
4485
+ 0.0,
4486
+ 0.0,
4487
+ 0.0,
4488
+ 0.0,
4489
+ 0.0,
4490
+ 0.0,
4491
+ 0.0,
4492
+ 0.0,
4493
+ 0.0,
4494
+ 0.0,
4495
+ 0.0,
4496
+ 0.0
4497
+ ],
4498
+ [
4499
+ 0.0,
4500
+ 0.0,
4501
+ 0.0,
4502
+ 0.0,
4503
+ 1.0,
4504
+ 2.0,
4505
+ 2.0,
4506
+ 2.0,
4507
+ 2.0,
4508
+ 2.0,
4509
+ 2.0,
4510
+ 2.0,
4511
+ 2.0,
4512
+ 2.0,
4513
+ 2.0,
4514
+ 2.0,
4515
+ 2.0,
4516
+ 2.0,
4517
+ 2.0,
4518
+ 0.0,
4519
+ 0.0,
4520
+ 0.0,
4521
+ 0.0,
4522
+ 0.0,
4523
+ 0.0,
4524
+ 0.0,
4525
+ 0.0,
4526
+ 0.0,
4527
+ 0.0,
4528
+ 0.0,
4529
+ 0.0,
4530
+ 0.0,
4531
+ 0.0,
4532
+ 0.0,
4533
+ 0.0,
4534
+ 0.0,
4535
+ 0.0,
4536
+ 0.0,
4537
+ 0.0,
4538
+ 0.0,
4539
+ 0.0,
4540
+ 0.0,
4541
+ 0.0,
4542
+ 0.0,
4543
+ 0.0,
4544
+ 0.0,
4545
+ 0.0,
4546
+ 0.0,
4547
+ 0.0,
4548
+ 0.0,
4549
+ 0.0,
4550
+ 0.0,
4551
+ 0.0,
4552
+ 0.0,
4553
+ 0.0,
4554
+ 0.0
4555
+ ],
4556
+ [
4557
+ 0.0,
4558
+ 0.0,
4559
+ 0.0,
4560
+ 0.0,
4561
+ 1.0,
4562
+ 2.0,
4563
+ 2.0,
4564
+ 2.0,
4565
+ 1.0,
4566
+ 1.0,
4567
+ 1.0,
4568
+ 2.0,
4569
+ 2.0,
4570
+ 2.0,
4571
+ 2.0,
4572
+ 2.0,
4573
+ 2.0,
4574
+ 2.0,
4575
+ 2.0,
4576
+ 0.0,
4577
+ 0.0,
4578
+ 0.0,
4579
+ 0.0,
4580
+ 0.0,
4581
+ 0.0,
4582
+ 0.0,
4583
+ 0.0,
4584
+ 0.0,
4585
+ 0.0,
4586
+ 0.0,
4587
+ 0.0,
4588
+ 0.0,
4589
+ 0.0,
4590
+ 0.0,
4591
+ 0.0,
4592
+ 0.0,
4593
+ 0.0,
4594
+ 0.0,
4595
+ 0.0,
4596
+ 0.0,
4597
+ 0.0,
4598
+ 0.0,
4599
+ 0.0,
4600
+ 0.0,
4601
+ 0.0,
4602
+ 0.0,
4603
+ 0.0,
4604
+ 0.0,
4605
+ 0.0,
4606
+ 0.0,
4607
+ 0.0,
4608
+ 0.0,
4609
+ 0.0,
4610
+ 0.0,
4611
+ 0.0,
4612
+ 0.0
4613
+ ],
4614
+ [
4615
+ 0.0,
4616
+ 0.0,
4617
+ 0.0,
4618
+ 0.0,
4619
+ 1.0,
4620
+ 1.0,
4621
+ 1.0,
4622
+ 1.0,
4623
+ 1.0,
4624
+ 2.0,
4625
+ 2.0,
4626
+ 2.0,
4627
+ 2.0,
4628
+ 2.0,
4629
+ 2.0,
4630
+ 2.0,
4631
+ 2.0,
4632
+ 2.0,
4633
+ 2.0,
4634
+ 0.0,
4635
+ 0.0,
4636
+ 0.0,
4637
+ 0.0,
4638
+ 0.0,
4639
+ 0.0,
4640
+ 0.0,
4641
+ 0.0,
4642
+ 0.0,
4643
+ 0.0,
4644
+ 0.0,
4645
+ 0.0,
4646
+ 0.0,
4647
+ 0.0,
4648
+ 0.0,
4649
+ 0.0,
4650
+ 0.0,
4651
+ 0.0,
4652
+ 0.0,
4653
+ 0.0,
4654
+ 0.0,
4655
+ 0.0,
4656
+ 0.0,
4657
+ 0.0,
4658
+ 0.0,
4659
+ 0.0,
4660
+ 0.0,
4661
+ 0.0,
4662
+ 0.0,
4663
+ 0.0,
4664
+ 0.0,
4665
+ 0.0,
4666
+ 0.0,
4667
+ 0.0,
4668
+ 0.0,
4669
+ 0.0,
4670
+ 0.0
4671
+ ],
4672
+ [
4673
+ 0.0,
4674
+ 0.0,
4675
+ 0.0,
4676
+ 0.0,
4677
+ 0.0,
4678
+ 0.0,
4679
+ 0.0,
4680
+ 0.0,
4681
+ 1.0,
4682
+ 2.0,
4683
+ 2.0,
4684
+ 2.0,
4685
+ 2.0,
4686
+ 2.0,
4687
+ 2.0,
4688
+ 2.0,
4689
+ 2.0,
4690
+ 1.0,
4691
+ 0.0,
4692
+ 0.0,
4693
+ 0.0,
4694
+ 0.0,
4695
+ 0.0,
4696
+ 0.0,
4697
+ 0.0,
4698
+ 0.0,
4699
+ 0.0,
4700
+ 0.0,
4701
+ 0.0,
4702
+ 0.0,
4703
+ 0.0,
4704
+ 0.0,
4705
+ 0.0,
4706
+ 0.0,
4707
+ 0.0,
4708
+ 0.0,
4709
+ 0.0,
4710
+ 0.0,
4711
+ 0.0,
4712
+ 0.0,
4713
+ 0.0,
4714
+ 0.0,
4715
+ 0.0,
4716
+ 0.0,
4717
+ 0.0,
4718
+ 0.0,
4719
+ 0.0,
4720
+ 0.0,
4721
+ 0.0,
4722
+ 0.0,
4723
+ 0.0,
4724
+ 0.0,
4725
+ 0.0,
4726
+ 0.0,
4727
+ 0.0,
4728
+ 0.0
4729
+ ],
4730
+ [
4731
+ 0.0,
4732
+ 0.0,
4733
+ 0.0,
4734
+ 0.0,
4735
+ 0.0,
4736
+ 0.0,
4737
+ 0.0,
4738
+ 0.0,
4739
+ 0.0,
4740
+ 2.0,
4741
+ 2.0,
4742
+ 2.0,
4743
+ 2.0,
4744
+ 2.0,
4745
+ 2.0,
4746
+ 2.0,
4747
+ 1.0,
4748
+ 0.0,
4749
+ 0.0,
4750
+ 0.0,
4751
+ 0.0,
4752
+ 0.0,
4753
+ 0.0,
4754
+ 0.0,
4755
+ 0.0,
4756
+ 0.0,
4757
+ 0.0,
4758
+ 0.0,
4759
+ 0.0,
4760
+ 0.0,
4761
+ 0.0,
4762
+ 0.0,
4763
+ 0.0,
4764
+ 0.0,
4765
+ 0.0,
4766
+ 0.0,
4767
+ 0.0,
4768
+ 0.0,
4769
+ 0.0,
4770
+ 0.0,
4771
+ 0.0,
4772
+ 0.0,
4773
+ 0.0,
4774
+ 0.0,
4775
+ 0.0,
4776
+ 0.0,
4777
+ 0.0,
4778
+ 0.0,
4779
+ 0.0,
4780
+ 0.0,
4781
+ 0.0,
4782
+ 0.0,
4783
+ 0.0,
4784
+ 0.0,
4785
+ 0.0,
4786
+ 0.0
4787
+ ],
4788
+ [
4789
+ 0.0,
4790
+ 0.0,
4791
+ 0.0,
4792
+ 0.0,
4793
+ 0.0,
4794
+ 0.0,
4795
+ 0.0,
4796
+ 0.0,
4797
+ 0.0,
4798
+ 0.0,
4799
+ 1.0,
4800
+ 2.0,
4801
+ 2.0,
4802
+ 2.0,
4803
+ 0.0,
4804
+ 0.0,
4805
+ 0.0,
4806
+ 0.0,
4807
+ 0.0,
4808
+ 0.0,
4809
+ 0.0,
4810
+ 0.0,
4811
+ 0.0,
4812
+ 0.0,
4813
+ 0.0,
4814
+ 0.0,
4815
+ 0.0,
4816
+ 0.0,
4817
+ 0.0,
4818
+ 0.0,
4819
+ 0.0,
4820
+ 0.0,
4821
+ 0.0,
4822
+ 0.0,
4823
+ 0.0,
4824
+ 0.0,
4825
+ 0.0,
4826
+ 0.0,
4827
+ 0.0,
4828
+ 0.0,
4829
+ 0.0,
4830
+ 0.0,
4831
+ 0.0,
4832
+ 0.0,
4833
+ 0.0,
4834
+ 0.0,
4835
+ 0.0,
4836
+ 0.0,
4837
+ 0.0,
4838
+ 0.0,
4839
+ 0.0,
4840
+ 0.0,
4841
+ 0.0,
4842
+ 0.0,
4843
+ 0.0,
4844
+ 0.0
4845
+ ],
4846
+ [
4847
+ 0.0,
4848
+ 0.0,
4849
+ 0.0,
4850
+ 0.0,
4851
+ 0.0,
4852
+ 0.0,
4853
+ 0.0,
4854
+ 0.0,
4855
+ 0.0,
4856
+ 0.0,
4857
+ 1.0,
4858
+ 1.0,
4859
+ 1.0,
4860
+ 0.0,
4861
+ 0.0,
4862
+ 0.0,
4863
+ 0.0,
4864
+ 0.0,
4865
+ 0.0,
4866
+ 0.0,
4867
+ 0.0,
4868
+ 0.0,
4869
+ 0.0,
4870
+ 0.0,
4871
+ 0.0,
4872
+ 0.0,
4873
+ 0.0,
4874
+ 0.0,
4875
+ 0.0,
4876
+ 0.0,
4877
+ 0.0,
4878
+ 0.0,
4879
+ 0.0,
4880
+ 0.0,
4881
+ 0.0,
4882
+ 0.0,
4883
+ 0.0,
4884
+ 0.0,
4885
+ 0.0,
4886
+ 0.0,
4887
+ 0.0,
4888
+ 0.0,
4889
+ 0.0,
4890
+ 0.0,
4891
+ 0.0,
4892
+ 0.0,
4893
+ 0.0,
4894
+ 0.0,
4895
+ 0.0,
4896
+ 0.0,
4897
+ 0.0,
4898
+ 0.0,
4899
+ 0.0,
4900
+ 0.0,
4901
+ 0.0,
4902
+ 0.0
4903
+ ]
4904
+ ]
4905
+ },
4906
+ "player": {
4907
+ "my_current_research_cost": null,
4908
+ "my_gold": 64,
4909
+ "my_government": 1,
4910
+ "my_is_alive": true,
4911
+ "my_science": 60,
4912
+ "my_score": 5,
4913
+ "my_tech_goal": 253,
4914
+ "my_techs_researched": 3,
4915
+ "my_total_bulbs_prod": 2
4916
+ },
4917
+ "tech": {
4918
+ "1": {
4919
+ "inv_state": 0,
4920
+ "is_req_for_goal": false,
4921
+ "is_researching": false,
4922
+ "is_tech_goal": false,
4923
+ "name": "Advanced Flight",
4924
+ "reqs": {
4925
+ "43": false,
4926
+ "64": false
4927
+ },
4928
+ "sup_improvements": [],
4929
+ "sup_units": [
4930
+ {
4931
+ "attack_strength": 12,
4932
+ "bombard_rate": 0,
4933
+ "build_cost": 120,
4934
+ "cargo": [
4935
+ 0,
4936
+ 0,
4937
+ 0,
4938
+ 0
4939
+ ],
4940
+ "city_size": 1,
4941
+ "city_slots": 0,
4942
+ "convert_time": 1,
4943
+ "converted_to": 52,
4944
+ "defense_strength": 1,
4945
+ "disembarks": [
4946
+ 30,
4947
+ 0,
4948
+ 0,
4949
+ 0
4950
+ ],
4951
+ "embarks": [
4952
+ 30,
4953
+ 0,
4954
+ 0,
4955
+ 0
4956
+ ],
4957
+ "firepower": 2,
4958
+ "flags": [
4959
+ 98,
4960
+ 0,
4961
+ 0,
4962
+ 0,
4963
+ 4,
4964
+ 0,
4965
+ 0,
4966
+ 0,
4967
+ 0,
4968
+ 0
4969
+ ],
4970
+ "fuel": 2,
4971
+ "gov_requirement": 6,
4972
+ "graphic_alt": "-",
4973
+ "graphic_str": "u.bomber",
4974
+ "happy_cost": 1,
4975
+ "helptext": "Bombers are specialized airborne units that may only attack ground targets, not other airborne units.\u0003",
4976
+ "hp": 20,
4977
+ "id": 26,
4978
+ "impr_requirement": 68,
4979
+ "move_bonus": [],
4980
+ "move_rate": 24,
4981
+ "name": "Bomber",
4982
+ "obsoleted_by": 29,
4983
+ "paratroopers_mr_req": 0,
4984
+ "paratroopers_mr_sub": 0,
4985
+ "paratroopers_range": 0,
4986
+ "pid": 140,
4987
+ "pop_cost": 0,
4988
+ "power_fact": [],
4989
+ "raise_chance": [],
4990
+ "roles": [
4991
+ 0,
4992
+ 0,
4993
+ 0,
4994
+ 0,
4995
+ 0,
4996
+ 0,
4997
+ 0,
4998
+ 0
4999
+ ],
5000
+ "rule_name": "Bomber",
5001
+ "sound_fight": "f_bomber",
5002
+ "sound_fight_alt": "f_generic",
5003
+ "sound_move": "m_bomber",
5004
+ "sound_move_alt": "m_generic",
5005
+ "targets": [
5006
+ 30,
5007
+ 0,
5008
+ 0,
5009
+ 0
5010
+ ],
5011
+ "tech_requirement": 1,
5012
+ "transport_capacity": 0,
5013
+ "unit_class_id": 5,
5014
+ "upkeep": [
5015
+ 0,
5016
+ 1,
5017
+ 0,
5018
+ 0,
5019
+ 0,
5020
+ 0
5021
+ ],
5022
+ "veteran_levels": 0,
5023
+ "veteran_name": [],
5024
+ "vision_radius_sq": 8,
5025
+ "vlayer": 0,
5026
+ "work_raise_chance": [],
5027
+ "worker": false
5028
+ },
5029
+ {
5030
+ "attack_strength": 1,
5031
+ "bombard_rate": 0,
5032
+ "build_cost": 160,
5033
+ "cargo": [
5034
+ 49,
5035
+ 0,
5036
+ 0,
5037
+ 0
5038
+ ],
5039
+ "city_size": 1,
5040
+ "city_slots": 0,
5041
+ "convert_time": 1,
5042
+ "converted_to": 52,
5043
+ "defense_strength": 9,
5044
+ "disembarks": [
5045
+ 30,
5046
+ 0,
5047
+ 0,
5048
+ 0
5049
+ ],
5050
+ "embarks": [
5051
+ 30,
5052
+ 0,
5053
+ 0,
5054
+ 0
5055
+ ],
5056
+ "firepower": 2,
5057
+ "flags": [
5058
+ 128,
5059
+ 128,
5060
+ 0,
5061
+ 4,
5062
+ 0,
5063
+ 0,
5064
+ 0,
5065
+ 0,
5066
+ 0,
5067
+ 0
5068
+ ],
5069
+ "fuel": 0,
5070
+ "gov_requirement": 6,
5071
+ "graphic_alt": "-",
5072
+ "graphic_str": "u.carrier",
5073
+ "happy_cost": 1,
5074
+ "helptext": "The Carrier is a mobile airport.\u0003TIP: Guard Carriers with a handful of fast-moving ships and a battleship, as losing a fully-equipped Carrier is VERY painful and expensive.\u0003",
5075
+ "hp": 40,
5076
+ "id": 40,
5077
+ "impr_requirement": 68,
5078
+ "move_bonus": [],
5079
+ "move_rate": 15,
5080
+ "name": "Carrier",
5081
+ "obsoleted_by": 52,
5082
+ "paratroopers_mr_req": 0,
5083
+ "paratroopers_mr_sub": 0,
5084
+ "paratroopers_range": 0,
5085
+ "pid": 140,
5086
+ "pop_cost": 0,
5087
+ "power_fact": [],
5088
+ "raise_chance": [],
5089
+ "roles": [
5090
+ 0,
5091
+ 0,
5092
+ 0,
5093
+ 0,
5094
+ 0,
5095
+ 0,
5096
+ 0,
5097
+ 0
5098
+ ],
5099
+ "rule_name": "Carrier",
5100
+ "sound_fight": "f_carrier",
5101
+ "sound_fight_alt": "f_generic",
5102
+ "sound_move": "m_carrier",
5103
+ "sound_move_alt": "m_generic",
5104
+ "targets": [
5105
+ 30,
5106
+ 0,
5107
+ 0,
5108
+ 0
5109
+ ],
5110
+ "tech_requirement": 1,
5111
+ "transport_capacity": 8,
5112
+ "unit_class_id": 2,
5113
+ "upkeep": [
5114
+ 0,
5115
+ 1,
5116
+ 0,
5117
+ 0,
5118
+ 0,
5119
+ 0
5120
+ ],
5121
+ "veteran_levels": 0,
5122
+ "veteran_name": [],
5123
+ "vision_radius_sq": 8,
5124
+ "vlayer": 0,
5125
+ "work_raise_chance": [],
5126
+ "worker": false
5127
+ },
5128
+ {
5129
+ "attack_strength": 0,
5130
+ "bombard_rate": 0,
5131
+ "build_cost": 140,
5132
+ "cargo": [
5133
+ 0,
5134
+ 0,
5135
+ 0,
5136
+ 0
5137
+ ],
5138
+ "city_size": 1,
5139
+ "city_slots": 0,
5140
+ "convert_time": 1,
5141
+ "converted_to": 52,
5142
+ "defense_strength": 1,
5143
+ "disembarks": [
5144
+ 30,
5145
+ 0,
5146
+ 0,
5147
+ 0
5148
+ ],
5149
+ "embarks": [
5150
+ 30,
5151
+ 0,
5152
+ 0,
5153
+ 0
5154
+ ],
5155
+ "firepower": 1,
5156
+ "flags": [
5157
+ 2,
5158
+ 0,
5159
+ 0,
5160
+ 0,
5161
+ 0,
5162
+ 0,
5163
+ 0,
5164
+ 0,
5165
+ 0,
5166
+ 0
5167
+ ],
5168
+ "fuel": 2,
5169
+ "gov_requirement": 6,
5170
+ "graphic_alt": "u.bomber",
5171
+ "graphic_str": "u.awacs",
5172
+ "happy_cost": 1,
5173
+ "helptext": "The AWACS (Airborne Warning and Control System) is an airplane with an advanced radar that can determine the location of enemy units over a wide area.\u0003",
5174
+ "hp": 20,
5175
+ "id": 51,
5176
+ "impr_requirement": 68,
5177
+ "move_bonus": [],
5178
+ "move_rate": 48,
5179
+ "name": "AWACS",
5180
+ "obsoleted_by": 52,
5181
+ "paratroopers_mr_req": 0,
5182
+ "paratroopers_mr_sub": 0,
5183
+ "paratroopers_range": 0,
5184
+ "pid": 140,
5185
+ "pop_cost": 0,
5186
+ "power_fact": [],
5187
+ "raise_chance": [],
5188
+ "roles": [
5189
+ 0,
5190
+ 0,
5191
+ 0,
5192
+ 0,
5193
+ 0,
5194
+ 0,
5195
+ 0,
5196
+ 0
5197
+ ],
5198
+ "rule_name": "AWACS",
5199
+ "sound_fight": "f_awacs",
5200
+ "sound_fight_alt": "f_generic",
5201
+ "sound_move": "m_awacs",
5202
+ "sound_move_alt": "m_generic",
5203
+ "targets": [
5204
+ 30,
5205
+ 0,
5206
+ 0,
5207
+ 0
5208
+ ],
5209
+ "tech_requirement": 1,
5210
+ "transport_capacity": 0,
5211
+ "unit_class_id": 5,
5212
+ "upkeep": [
5213
+ 0,
5214
+ 1,
5215
+ 0,
5216
+ 0,
5217
+ 0,
5218
+ 0
5219
+ ],
5220
+ "veteran_levels": 0,
5221
+ "veteran_name": [],
5222
+ "vision_radius_sq": 26,
5223
+ "vlayer": 0,
5224
+ "work_raise_chance": [],
5225
+ "worker": false
5226
+ }
5227
+ ]
5228
+ },
5229
+ "10": {
5230
+ "inv_state": 1,
5231
+ "is_req_for_goal": false,
5232
+ "is_researching": false,
5233
+ "is_tech_goal": false,
5234
+ "name": "Ceremonial Burial",
5235
+ "reqs": {},
5236
+ "sup_improvements": [
5237
+ {
5238
+ "build_cost": 30,
5239
+ "flags": [
5240
+ 0
5241
+ ],
5242
+ "genus": 2,
5243
+ "graphic_alt": "-",
5244
+ "graphic_str": "b.temple",
5245
+ "helptext": "Makes one unhappy citizen content. The Mysticism advance doubles this effect. With both Mysticism and the Oracle, 4 citizens are made content. Does not affect citizens made unhappy by military activity.\u0003",
5246
+ "id": 37,
5247
+ "name": "Temple",
5248
+ "obs_count": 0,
5249
+ "obs_reqs": [],
5250
+ "pid": 150,
5251
+ "reqs": [
5252
+ {
5253
+ "kind": 1,
5254
+ "present": true,
5255
+ "quiet": false,
5256
+ "range": 6,
5257
+ "survives": false,
5258
+ "value": 10
5259
+ }
5260
+ ],
5261
+ "reqs_count": 1,
5262
+ "rule_name": "Temple",
5263
+ "sabotage": 100,
5264
+ "soundtag": "b_temple",
5265
+ "soundtag_alt": "b_generic",
5266
+ "upkeep": 1
5267
+ }
5268
+ ],
5269
+ "sup_units": []
5270
+ },
5271
+ "2": {
5272
+ "inv_state": 1,
5273
+ "is_req_for_goal": false,
5274
+ "is_researching": false,
5275
+ "is_tech_goal": false,
5276
+ "name": "Alphabet",
5277
+ "reqs": {},
5278
+ "sup_improvements": [],
5279
+ "sup_units": []
5280
+ },
5281
+ "3": {
5282
+ "inv_state": 0,
5283
+ "is_req_for_goal": false,
5284
+ "is_researching": false,
5285
+ "is_tech_goal": false,
5286
+ "name": "Amphibious Warfare",
5287
+ "reqs": {
5288
+ "56": false,
5289
+ "78": false
5290
+ },
5291
+ "sup_improvements": [
5292
+ {
5293
+ "build_cost": 60,
5294
+ "flags": [
5295
+ 0
5296
+ ],
5297
+ "genus": 2,
5298
+ "graphic_alt": "-",
5299
+ "graphic_str": "b.port_facility",
5300
+ "helptext": "Allows a city to build veteran sea units. Also, damaged sea units which stay in town for one full turn without moving are completely restored.\u0003",
5301
+ "id": 23,
5302
+ "name": "Port Facility",
5303
+ "obs_count": 0,
5304
+ "obs_reqs": [],
5305
+ "pid": 150,
5306
+ "reqs": [
5307
+ {
5308
+ "kind": 1,
5309
+ "present": true,
5310
+ "quiet": false,
5311
+ "range": 6,
5312
+ "survives": false,
5313
+ "value": 3
5314
+ },
5315
+ {
5316
+ "kind": 14,
5317
+ "present": true,
5318
+ "quiet": false,
5319
+ "range": 2,
5320
+ "survives": false,
5321
+ "value": 1
5322
+ }
5323
+ ],
5324
+ "reqs_count": 2,
5325
+ "rule_name": "Port Facility",
5326
+ "sabotage": 100,
5327
+ "soundtag": "b_port_facility",
5328
+ "soundtag_alt": "b_generic",
5329
+ "upkeep": 3
5330
+ }
5331
+ ],
5332
+ "sup_units": [
5333
+ {
5334
+ "attack_strength": 8,
5335
+ "bombard_rate": 0,
5336
+ "build_cost": 60,
5337
+ "cargo": [
5338
+ 0,
5339
+ 0,
5340
+ 0,
5341
+ 0
5342
+ ],
5343
+ "city_size": 1,
5344
+ "city_slots": 0,
5345
+ "convert_time": 1,
5346
+ "converted_to": 52,
5347
+ "defense_strength": 5,
5348
+ "disembarks": [
5349
+ 30,
5350
+ 0,
5351
+ 0,
5352
+ 0
5353
+ ],
5354
+ "embarks": [
5355
+ 30,
5356
+ 0,
5357
+ 0,
5358
+ 0
5359
+ ],
5360
+ "firepower": 1,
5361
+ "flags": [
5362
+ 0,
5363
+ 0,
5364
+ 0,
5365
+ 0,
5366
+ 0,
5367
+ 32,
5368
+ 0,
5369
+ 0,
5370
+ 0,
5371
+ 0
5372
+ ],
5373
+ "fuel": 0,
5374
+ "gov_requirement": 6,
5375
+ "graphic_alt": "-",
5376
+ "graphic_str": "u.marines",
5377
+ "happy_cost": 1,
5378
+ "helptext": "Marines are infantry who are experts at marine warfare.\u0003",
5379
+ "hp": 20,
5380
+ "id": 12,
5381
+ "impr_requirement": 68,
5382
+ "move_bonus": [],
5383
+ "move_rate": 3,
5384
+ "name": "Marines",
5385
+ "obsoleted_by": 52,
5386
+ "paratroopers_mr_req": 0,
5387
+ "paratroopers_mr_sub": 0,
5388
+ "paratroopers_range": 0,
5389
+ "pid": 140,
5390
+ "pop_cost": 0,
5391
+ "power_fact": [],
5392
+ "raise_chance": [],
5393
+ "roles": [
5394
+ 32,
5395
+ 128,
5396
+ 64,
5397
+ 0,
5398
+ 0,
5399
+ 0,
5400
+ 0,
5401
+ 0
5402
+ ],
5403
+ "rule_name": "Marines",
5404
+ "sound_fight": "f_marines",
5405
+ "sound_fight_alt": "f_generic",
5406
+ "sound_move": "m_marines",
5407
+ "sound_move_alt": "m_generic",
5408
+ "targets": [
5409
+ 30,
5410
+ 0,
5411
+ 0,
5412
+ 0
5413
+ ],
5414
+ "tech_requirement": 3,
5415
+ "transport_capacity": 0,
5416
+ "unit_class_id": 1,
5417
+ "upkeep": [
5418
+ 0,
5419
+ 1,
5420
+ 0,
5421
+ 0,
5422
+ 0,
5423
+ 0
5424
+ ],
5425
+ "veteran_levels": 0,
5426
+ "veteran_name": [],
5427
+ "vision_radius_sq": 2,
5428
+ "vlayer": 0,
5429
+ "work_raise_chance": [],
5430
+ "worker": false
5431
+ }
5432
+ ]
5433
+ },
5434
+ "4": {
5435
+ "inv_state": 0,
5436
+ "is_req_for_goal": false,
5437
+ "is_researching": false,
5438
+ "is_tech_goal": false,
5439
+ "name": "Astronomy",
5440
+ "reqs": {
5441
+ "48": false,
5442
+ "55": false
5443
+ },
5444
+ "sup_improvements": [
5445
+ {
5446
+ "build_cost": 200,
5447
+ "flags": [
5448
+ 0
5449
+ ],
5450
+ "genus": 0,
5451
+ "graphic_alt": "-",
5452
+ "graphic_str": "b.copernicus_observatory",
5453
+ "helptext": "Boosts science production by 100% in the city where it is built.\u0003",
5454
+ "id": 42,
5455
+ "name": "Copernicus' Observatory",
5456
+ "obs_count": 0,
5457
+ "obs_reqs": [],
5458
+ "pid": 150,
5459
+ "reqs": [
5460
+ {
5461
+ "kind": 1,
5462
+ "present": true,
5463
+ "quiet": false,
5464
+ "range": 6,
5465
+ "survives": false,
5466
+ "value": 4
5467
+ }
5468
+ ],
5469
+ "reqs_count": 1,
5470
+ "rule_name": "Copernicus' Observatory",
5471
+ "sabotage": 0,
5472
+ "soundtag": "w_copernicus_observatory",
5473
+ "soundtag_alt": "w_generic",
5474
+ "upkeep": 0
5475
+ }
5476
+ ],
5477
+ "sup_units": []
5478
+ },
5479
+ "5": {
5480
+ "inv_state": 0,
5481
+ "is_req_for_goal": false,
5482
+ "is_researching": false,
5483
+ "is_tech_goal": false,
5484
+ "name": "Atomic Theory",
5485
+ "reqs": {
5486
+ "60": false,
5487
+ "83": false
5488
+ },
5489
+ "sup_improvements": [],
5490
+ "sup_units": []
5491
+ },
5492
+ "6": {
5493
+ "inv_state": 0,
5494
+ "is_req_for_goal": false,
5495
+ "is_researching": false,
5496
+ "is_tech_goal": false,
5497
+ "name": "Automobile",
5498
+ "reqs": {
5499
+ "15": false,
5500
+ "76": false
5501
+ },
5502
+ "sup_improvements": [
5503
+ {
5504
+ "build_cost": 120,
5505
+ "flags": [
5506
+ 0
5507
+ ],
5508
+ "genus": 2,
5509
+ "graphic_alt": "-",
5510
+ "graphic_str": "b.super_highways",
5511
+ "helptext": "Increases trade resources by 50% on all tiles with roads or railroads.\u0003",
5512
+ "id": 35,
5513
+ "name": "Super Highways",
5514
+ "obs_count": 0,
5515
+ "obs_reqs": [],
5516
+ "pid": 150,
5517
+ "reqs": [
5518
+ {
5519
+ "kind": 1,
5520
+ "present": true,
5521
+ "quiet": false,
5522
+ "range": 6,
5523
+ "survives": false,
5524
+ "value": 6
5525
+ }
5526
+ ],
5527
+ "reqs_count": 1,
5528
+ "rule_name": "Super Highways",
5529
+ "sabotage": 100,
5530
+ "soundtag": "b_super_highways",
5531
+ "soundtag_alt": "b_generic",
5532
+ "upkeep": 3
5533
+ }
5534
+ ],
5535
+ "sup_units": [
5536
+ {
5537
+ "attack_strength": 12,
5538
+ "bombard_rate": 0,
5539
+ "build_cost": 160,
5540
+ "cargo": [
5541
+ 0,
5542
+ 0,
5543
+ 0,
5544
+ 0
5545
+ ],
5546
+ "city_size": 1,
5547
+ "city_slots": 0,
5548
+ "convert_time": 1,
5549
+ "converted_to": 52,
5550
+ "defense_strength": 12,
5551
+ "disembarks": [
5552
+ 30,
5553
+ 0,
5554
+ 0,
5555
+ 0
5556
+ ],
5557
+ "embarks": [
5558
+ 30,
5559
+ 0,
5560
+ 0,
5561
+ 0
5562
+ ],
5563
+ "firepower": 2,
5564
+ "flags": [
5565
+ 0,
5566
+ 0,
5567
+ 0,
5568
+ 4,
5569
+ 0,
5570
+ 0,
5571
+ 0,
5572
+ 0,
5573
+ 0,
5574
+ 0
5575
+ ],
5576
+ "fuel": 0,
5577
+ "gov_requirement": 6,
5578
+ "graphic_alt": "-",
5579
+ "graphic_str": "u.battleship",
5580
+ "happy_cost": 1,
5581
+ "helptext": "The Battleship is the supreme naval unit with excellent offensive and defensive values.\u0003",
5582
+ "hp": 40,
5583
+ "id": 38,
5584
+ "impr_requirement": 68,
5585
+ "move_bonus": [],
5586
+ "move_rate": 12,
5587
+ "name": "Battleship",
5588
+ "obsoleted_by": 52,
5589
+ "paratroopers_mr_req": 0,
5590
+ "paratroopers_mr_sub": 0,
5591
+ "paratroopers_range": 0,
5592
+ "pid": 140,
5593
+ "pop_cost": 0,
5594
+ "power_fact": [],
5595
+ "raise_chance": [],
5596
+ "roles": [
5597
+ 0,
5598
+ 0,
5599
+ 0,
5600
+ 0,
5601
+ 0,
5602
+ 0,
5603
+ 0,
5604
+ 0
5605
+ ],
5606
+ "rule_name": "Battleship",
5607
+ "sound_fight": "f_battleship",
5608
+ "sound_fight_alt": "f_generic",
5609
+ "sound_move": "m_battleship",
5610
+ "sound_move_alt": "m_generic",
5611
+ "targets": [
5612
+ 30,
5613
+ 0,
5614
+ 0,
5615
+ 0
5616
+ ],
5617
+ "tech_requirement": 6,
5618
+ "transport_capacity": 0,
5619
+ "unit_class_id": 2,
5620
+ "upkeep": [
5621
+ 0,
5622
+ 1,
5623
+ 0,
5624
+ 0,
5625
+ 0,
5626
+ 0
5627
+ ],
5628
+ "veteran_levels": 0,
5629
+ "veteran_name": [],
5630
+ "vision_radius_sq": 8,
5631
+ "vlayer": 0,
5632
+ "work_raise_chance": [],
5633
+ "worker": false
5634
+ }
5635
+ ]
5636
+ },
5637
+ "7": {
5638
+ "inv_state": 0,
5639
+ "is_req_for_goal": false,
5640
+ "is_researching": false,
5641
+ "is_tech_goal": false,
5642
+ "name": "Banking",
5643
+ "reqs": {
5644
+ "80": false,
5645
+ "84": false
5646
+ },
5647
+ "sup_improvements": [
5648
+ {
5649
+ "build_cost": 80,
5650
+ "flags": [
5651
+ 0
5652
+ ],
5653
+ "genus": 2,
5654
+ "graphic_alt": "-",
5655
+ "graphic_str": "b.bank",
5656
+ "helptext": "Together with the Marketplace improvement, a Bank increases the luxury and tax production within a city by 100%.\u0003",
5657
+ "id": 2,
5658
+ "name": "Bank",
5659
+ "obs_count": 0,
5660
+ "obs_reqs": [],
5661
+ "pid": 150,
5662
+ "reqs": [
5663
+ {
5664
+ "kind": 1,
5665
+ "present": true,
5666
+ "quiet": false,
5667
+ "range": 6,
5668
+ "survives": false,
5669
+ "value": 7
5670
+ },
5671
+ {
5672
+ "kind": 3,
5673
+ "present": true,
5674
+ "quiet": false,
5675
+ "range": 3,
5676
+ "survives": false,
5677
+ "value": 16
5678
+ }
5679
+ ],
5680
+ "reqs_count": 2,
5681
+ "rule_name": "Bank",
5682
+ "sabotage": 100,
5683
+ "soundtag": "b_bank",
5684
+ "soundtag_alt": "b_generic",
5685
+ "upkeep": 2
5686
+ }
5687
+ ],
5688
+ "sup_units": []
5689
+ },
5690
+ "8": {
5691
+ "inv_state": 0,
5692
+ "is_req_for_goal": false,
5693
+ "is_researching": false,
5694
+ "is_tech_goal": false,
5695
+ "name": "Bridge Building",
5696
+ "reqs": {
5697
+ "19": false,
5698
+ "38": false
5699
+ },
5700
+ "sup_improvements": [],
5701
+ "sup_units": []
5702
+ },
5703
+ "9": {
5704
+ "inv_state": 1,
5705
+ "is_req_for_goal": false,
5706
+ "is_researching": false,
5707
+ "is_tech_goal": false,
5708
+ "name": "Bronze Working",
5709
+ "reqs": {},
5710
+ "sup_improvements": [
5711
+ {
5712
+ "build_cost": 100,
5713
+ "flags": [
5714
+ 0
5715
+ ],
5716
+ "genus": 0,
5717
+ "graphic_alt": "-",
5718
+ "graphic_str": "b.colossus",
5719
+ "helptext": "Each tile around the city where this wonder is built that is already generating some trade produces one extra trade resource.\u0003",
5720
+ "id": 41,
5721
+ "name": "Colossus",
5722
+ "obs_count": 1,
5723
+ "obs_reqs": [
5724
+ {
5725
+ "kind": 1,
5726
+ "present": true,
5727
+ "quiet": false,
5728
+ "range": 9,
5729
+ "survives": true,
5730
+ "value": 30
5731
+ }
5732
+ ],
5733
+ "pid": 150,
5734
+ "reqs": [
5735
+ {
5736
+ "kind": 1,
5737
+ "present": true,
5738
+ "quiet": false,
5739
+ "range": 6,
5740
+ "survives": false,
5741
+ "value": 9
5742
+ }
5743
+ ],
5744
+ "reqs_count": 1,
5745
+ "rule_name": "Colossus",
5746
+ "sabotage": 0,
5747
+ "soundtag": "w_colossus",
5748
+ "soundtag_alt": "w_generic",
5749
+ "upkeep": 0
5750
+ }
5751
+ ],
5752
+ "sup_units": [
5753
+ {
5754
+ "attack_strength": 1,
5755
+ "bombard_rate": 0,
5756
+ "build_cost": 20,
5757
+ "cargo": [
5758
+ 0,
5759
+ 0,
5760
+ 0,
5761
+ 0
5762
+ ],
5763
+ "city_size": 1,
5764
+ "city_slots": 0,
5765
+ "convert_time": 1,
5766
+ "converted_to": 52,
5767
+ "defense_strength": 2,
5768
+ "disembarks": [
5769
+ 30,
5770
+ 0,
5771
+ 0,
5772
+ 0
5773
+ ],
5774
+ "embarks": [
5775
+ 30,
5776
+ 0,
5777
+ 0,
5778
+ 0
5779
+ ],
5780
+ "firepower": 1,
5781
+ "flags": [
5782
+ 0,
5783
+ 0,
5784
+ 0,
5785
+ 0,
5786
+ 0,
5787
+ 0,
5788
+ 0,
5789
+ 0,
5790
+ 0,
5791
+ 0
5792
+ ],
5793
+ "fuel": 0,
5794
+ "gov_requirement": 6,
5795
+ "graphic_alt": "-",
5796
+ "graphic_str": "u.phalanx",
5797
+ "happy_cost": 1,
5798
+ "helptext": "The Phalanx is armored infantry, suitable for defending your cities.\u0003",
5799
+ "hp": 10,
5800
+ "id": 4,
5801
+ "impr_requirement": 68,
5802
+ "move_bonus": [],
5803
+ "move_rate": 3,
5804
+ "name": "Phalanx",
5805
+ "obsoleted_by": 7,
5806
+ "paratroopers_mr_req": 0,
5807
+ "paratroopers_mr_sub": 0,
5808
+ "paratroopers_range": 0,
5809
+ "pid": 140,
5810
+ "pop_cost": 0,
5811
+ "power_fact": [],
5812
+ "raise_chance": [],
5813
+ "roles": [
5814
+ 65,
5815
+ 0,
5816
+ 128,
5817
+ 0,
5818
+ 0,
5819
+ 0,
5820
+ 0,
5821
+ 0
5822
+ ],
5823
+ "rule_name": "Phalanx",
5824
+ "sound_fight": "f_phalanx",
5825
+ "sound_fight_alt": "f_generic",
5826
+ "sound_move": "m_phalanx",
5827
+ "sound_move_alt": "m_generic",
5828
+ "targets": [
5829
+ 30,
5830
+ 0,
5831
+ 0,
5832
+ 0
5833
+ ],
5834
+ "tech_requirement": 9,
5835
+ "transport_capacity": 0,
5836
+ "unit_class_id": 1,
5837
+ "upkeep": [
5838
+ 0,
5839
+ 1,
5840
+ 0,
5841
+ 0,
5842
+ 0,
5843
+ 0
5844
+ ],
5845
+ "veteran_levels": 0,
5846
+ "veteran_name": [],
5847
+ "vision_radius_sq": 2,
5848
+ "vlayer": 0,
5849
+ "work_raise_chance": [],
5850
+ "worker": false
5851
+ }
5852
+ ]
5853
+ }
5854
+ },
5855
+ "unit": {
5856
+ "110": {
5857
+ "health": 10,
5858
+ "home_city": -1,
5859
+ "moves_left": 0,
5860
+ "type_attack_strength": 0,
5861
+ "type_defense_strength": 1,
5862
+ "type_move_rate": 3,
5863
+ "type_rule_name": "Workers",
5864
+ "type_worker": true,
5865
+ "upkeep_food": 0,
5866
+ "upkeep_gold": 0,
5867
+ "upkeep_shield": 0,
5868
+ "veteran": 0
5869
+ },
5870
+ "111": {
5871
+ "health": 10,
5872
+ "home_city": -1,
5873
+ "moves_left": 0,
5874
+ "type_attack_strength": 0,
5875
+ "type_defense_strength": 1,
5876
+ "type_move_rate": 3,
5877
+ "type_rule_name": "Workers",
5878
+ "type_worker": true,
5879
+ "upkeep_food": 0,
5880
+ "upkeep_gold": 0,
5881
+ "upkeep_shield": 0,
5882
+ "veteran": 0
5883
+ },
5884
+ "112": {
5885
+ "health": 10,
5886
+ "home_city": -1,
5887
+ "moves_left": 0,
5888
+ "type_attack_strength": 0,
5889
+ "type_defense_strength": 1,
5890
+ "type_move_rate": 3,
5891
+ "type_rule_name": "Explorer",
5892
+ "type_worker": false,
5893
+ "upkeep_food": 0,
5894
+ "upkeep_gold": 0,
5895
+ "upkeep_shield": 0,
5896
+ "veteran": 0
5897
+ }
5898
+ }
5899
+ }
tests/test_adapter.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from freeciv_env.adapter import RawSnapshot, prepare_observation
7
+
8
+
9
+ def test_prepare_observation_from_freecivbot_fixture() -> None:
10
+ fixtures = Path("tests/fixtures")
11
+ state = json.loads((fixtures / "turn_state.json").read_text())
12
+ actions = json.loads((fixtures / "turn_actions.json").read_text())
13
+
14
+ prepared = prepare_observation(
15
+ RawSnapshot(turn=15, state=state, actions=actions),
16
+ reward=0.0,
17
+ done=False,
18
+ status="ready",
19
+ metadata={},
20
+ )
21
+ obs = prepared.observation
22
+
23
+ assert obs.turn == 15
24
+ assert obs.city_count == 2
25
+ assert obs.unit_count == 3
26
+ assert obs.known_tiles > 0
27
+ assert obs.visible_tiles > 0
28
+ assert any(action.action_type == "move_unit" for action in obs.legal_actions)
29
+ assert any(action.action_type == "set_city_production" for action in obs.legal_actions)
30
+ assert any(action.action_type == "set_research" for action in obs.legal_actions)
31
+ assert "Turn 15" in obs.summary
32
+ assert "Legal actions exposed" in obs.summary
tests/test_environment.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from freeciv_env.models import FreecivAction
4
+ from freeciv_env.server.freeciv_environment import FreecivEnvironment
5
+ from tests.fakes import FakeFreecivSession
6
+
7
+
8
+ def make_env() -> FreecivEnvironment:
9
+ return FreecivEnvironment(session_factory=FakeFreecivSession, max_turns=5)
10
+
11
+
12
+ def test_reset_exposes_expected_action_surface() -> None:
13
+ env = make_env()
14
+ obs = env.reset()
15
+
16
+ assert obs.status == "ready"
17
+ assert obs.turn == 1
18
+ assert any(action.action_type == "end_turn" for action in obs.legal_actions)
19
+ assert any(action.action_type == "move_unit" for action in obs.legal_actions)
20
+ assert any(action.action_type == "build_city" for action in obs.legal_actions)
21
+ assert any(action.action_type == "set_city_production" for action in obs.legal_actions)
22
+ assert any(action.action_type == "set_research" for action in obs.legal_actions)
23
+
24
+ env.close()
25
+
26
+
27
+ def test_invalid_action_is_penalized_without_advancing_state() -> None:
28
+ env = make_env()
29
+ env.reset()
30
+
31
+ obs = env.step(FreecivAction(action_type="move_unit", unit_id=999, direction=7))
32
+
33
+ assert obs.status == "invalid_action"
34
+ assert obs.reward == -0.25
35
+ assert obs.turn == 1
36
+ assert env.state.turn == 1
37
+
38
+ env.close()
39
+
40
+
41
+ def test_move_unit_rewards_exploration_progress() -> None:
42
+ env = make_env()
43
+ env.reset()
44
+
45
+ obs = env.step(FreecivAction(action_type="move_unit", unit_id=201, direction=0))
46
+
47
+ assert obs.status == "ok"
48
+ assert obs.known_tiles == 5
49
+ assert obs.reward > 0.01
50
+ assert obs.unit_count == 1
51
+
52
+ env.close()
53
+
54
+
55
+ def test_build_city_rewards_city_growth() -> None:
56
+ env = make_env()
57
+ env.reset()
58
+
59
+ obs = env.step(FreecivAction(action_type="build_city", unit_id=201))
60
+
61
+ assert obs.city_count == 2
62
+ assert obs.unit_count == 0
63
+ assert obs.reward >= 0.66
64
+
65
+ env.close()
66
+
67
+
68
+ def test_set_research_rewards_tech_progress() -> None:
69
+ env = make_env()
70
+ env.reset()
71
+
72
+ obs = env.step(FreecivAction(action_type="set_research", target="Pottery"))
73
+
74
+ assert obs.techs_researched == 1
75
+ assert obs.reward >= 0.30
76
+
77
+ env.close()
78
+
79
+
80
+ def test_end_turn_advances_turn_counter() -> None:
81
+ env = make_env()
82
+ env.reset()
83
+
84
+ obs = env.step(FreecivAction(action_type="end_turn"))
85
+
86
+ assert obs.turn == 2
87
+ assert env.state.turn == 2
88
+ assert obs.reward > 0.0
89
+
90
+ env.close()
tests/test_grpo_utils.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from freeciv_env.grpo import build_turn_prompt, oracle_action_index, parse_action_choice, reward_from_oracle
2
+ from freeciv_env.server.freeciv_environment import FreecivEnvironment
3
+ from tests.fakes import FakeFreecivSession
4
+
5
+
6
+ def test_build_turn_prompt_lists_indices() -> None:
7
+ env = FreecivEnvironment(session_factory=FakeFreecivSession, max_turns=5)
8
+ observation = env.reset()
9
+ prompt = build_turn_prompt(observation)
10
+ assert "1: End the current turn" in prompt
11
+ assert "State:" in prompt
12
+ env.close()
13
+
14
+
15
+ def test_parse_action_choice_returns_expected_action() -> None:
16
+ env = FreecivEnvironment(session_factory=FakeFreecivSession, max_turns=5)
17
+ observation = env.reset()
18
+ chosen = parse_action_choice("2", observation.legal_actions)
19
+ assert chosen is not None
20
+ assert chosen.action_type == "move_unit"
21
+ assert chosen.unit_id == 201
22
+ assert chosen.direction == 0
23
+ env.close()
24
+
25
+
26
+ def test_parse_action_choice_rejects_invalid_index() -> None:
27
+ env = FreecivEnvironment(session_factory=FakeFreecivSession, max_turns=5)
28
+ observation = env.reset()
29
+ assert parse_action_choice("99", observation.legal_actions) is None
30
+ assert parse_action_choice("nope", observation.legal_actions) is None
31
+ env.close()
32
+
33
+
34
+ def test_oracle_prefers_build_city() -> None:
35
+ env = FreecivEnvironment(session_factory=FakeFreecivSession, max_turns=5)
36
+ observation = env.reset()
37
+ assert oracle_action_index(observation.legal_actions) == 0
38
+ env.close()
39
+
40
+
41
+ def test_reward_from_oracle_scores_exact_match() -> None:
42
+ rewards = reward_from_oracle(["0", "2", "bad"], best_index=[0, 1, 2])
43
+ assert rewards == [1.0, 0.0, -0.25]
tests/test_server_roundtrip.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import subprocess
4
+ import sys
5
+ import time
6
+ from contextlib import contextmanager
7
+ from typing import Generator
8
+
9
+ import requests
10
+
11
+ from freeciv_env.client import FreecivEnv
12
+ from freeciv_env.models import FreecivAction
13
+
14
+
15
+ @contextmanager
16
+ def run_server(module_path: str, port: int) -> Generator[str, None, None]:
17
+ process = subprocess.Popen(
18
+ [
19
+ sys.executable,
20
+ "-m",
21
+ "uvicorn",
22
+ f"{module_path}:app",
23
+ "--host",
24
+ "127.0.0.1",
25
+ "--port",
26
+ str(port),
27
+ ],
28
+ stdout=subprocess.PIPE,
29
+ stderr=subprocess.PIPE,
30
+ )
31
+ base_url = f"http://127.0.0.1:{port}"
32
+ try:
33
+ deadline = time.time() + 10
34
+ while time.time() < deadline:
35
+ try:
36
+ if requests.get(f"{base_url}/health", timeout=1).status_code == 200:
37
+ break
38
+ except requests.exceptions.ConnectionError:
39
+ time.sleep(0.1)
40
+ else:
41
+ stderr = process.stderr.read().decode() if process.stderr else ""
42
+ raise TimeoutError(stderr)
43
+ yield base_url
44
+ finally:
45
+ process.terminate()
46
+ try:
47
+ process.wait(timeout=5)
48
+ except subprocess.TimeoutExpired:
49
+ process.kill()
50
+ process.wait()
51
+
52
+
53
+ def test_websocket_roundtrip_with_fake_backend() -> None:
54
+ with run_server("tests.fake_server", port=8130) as base_url:
55
+ client = FreecivEnv(base_url=base_url)
56
+ client.connect()
57
+ try:
58
+ reset_result = client.reset()
59
+ assert reset_result.observation.turn == 1
60
+ assert any(action.action_type == "move_unit" for action in reset_result.observation.legal_actions)
61
+
62
+ step_result = client.step(FreecivAction(action_type="move_unit", unit_id=201, direction=0))
63
+ assert step_result.observation.turn == 1
64
+ assert step_result.observation.known_tiles == 5
65
+
66
+ state = client.state()
67
+ assert state.turn == 1
68
+ assert state.score == step_result.observation.score
69
+ finally:
70
+ client.close()
uv.lock ADDED
The diff for this file is too large to render. See raw diff