| """Generate web/city/constants.js + web/mock/world.json from engine constants |
| and genesis — the ONE source of truth crossing the Python/JS boundary. |
| |
| Run from the repo root (the integrator runs this; output is deterministic, no |
| timestamps): |
| |
| python tools/gen_web.py |
| """ |
|
|
| from __future__ import annotations |
|
|
| import json |
| import sys |
| from pathlib import Path |
|
|
| ROOT = Path(__file__).resolve().parents[1] |
| sys.path.insert(0, str(ROOT)) |
|
|
| from engine import constants as C |
| from engine.genesis import genesis_features |
|
|
|
|
| def _js(value) -> str: |
| return json.dumps(value, indent=2, sort_keys=False) |
|
|
|
|
| def constants_js() -> str: |
| buildings = { |
| kind: { |
| "w": spec["w"], "d": spec["d"], |
| "floors": list(spec["floors"]), |
| "residents": spec["residents"], "jobs": spec["jobs"], |
| "attract": spec["attract"], |
| |
| "duration": spec["duration_s"], "duration_s": spec["duration_s"], |
| } |
| for kind, spec in C.BUILDINGS.items() |
| } |
| durations = {kind: spec["duration_s"] for kind, spec in C.BUILDINGS.items()} |
| parts = [ |
| "// GENERATED by tools/gen_web.py from engine/constants.py — do not edit.", |
| "// Values are the ARCHITECTURE.md contract; regenerate, never hand-tune.", |
| "", |
| f"export const GRID = {C.GRID};", |
| f"export const CELL = {C.CELL};", |
| f"export const COORD_MIN = {C.COORD_MIN};", |
| f"export const COORD_MAX = {C.COORD_MAX};", |
| f"export const CITY_EPOCH_S = {C.CITY_EPOCH_S};", |
| f"export const DAY_S = {C.DAY_S};", |
| "", |
| "export function riverCols(cz) {", |
| " return cz <= -5 ? [7, 8] : cz <= 3 ? [8, 9] : [9, 10];", |
| "}", |
| "", |
| f"export const BUILDINGS = {_js(buildings)};", |
| "", |
| f"export const ROAD_CLASSES = {_js(C.ROAD_CLASSES)};", |
| f"export const ROAD_DURATION_S = {C.ROAD_DURATION_S};", |
| f"export const DURATIONS = {_js(durations)};", |
| f"export const SYNONYMS = {_js(C.SYNONYMS)};", |
| f"export const STREET_NAMES = {_js(list(C.STREET_NAMES))};", |
| "", |
| "// traffic (mirrors engine/traffic.py — the Python side owns the facts)", |
| f"export const RUSH_HOURS = {_js(list(C.RUSH_HOURS))};", |
| f"export const RUSH_SIGMA = {C.RUSH_SIGMA};", |
| f"export const RUSH_BASE = {C.RUSH_BASE};", |
| f"export const RUSH_AMP = {C.RUSH_AMP};", |
| f"export const CAR_RATE = {C.CAR_RATE};", |
| f"export const CAR_MIN = {C.CAR_MIN};", |
| f"export const CAR_MAX = {C.CAR_MAX};", |
| f"export const CITIZENS_PER_POP = {C.CITIZENS_PER_POP};", |
| f"export const CITIZENS_MIN = {C.CITIZENS_MIN};", |
| f"export const CITIZENS_MAX = {C.CITIZENS_MAX};", |
| f"export const EMA_TAU_S = {C.EMA_TAU_S};", |
| f"export const TRIP_ATTRACT_FACTOR = {C.TRIP_ATTRACT_FACTOR};", |
| f"export const TRIP_DIST_EXP = {C.TRIP_DIST_EXP};", |
| f"export const DEMAND_PER_COMMUTER = {C.DEMAND_PER_COMMUTER};", |
| f"export const CONGESTION_COST_FACTOR = {C.CONGESTION_COST_FACTOR};", |
| f"export const TRAFFIC_TOP_CELLS = {C.TRAFFIC_TOP_CELLS};", |
| f"export const JAM_RATIO = {C.JAM_RATIO};", |
| f"export const GROWTH_RADIUS_MIN = {C.GROWTH_RADIUS_MIN};", |
| "", |
| ] |
| return "\n".join(parts) |
|
|
|
|
| def world_json() -> str: |
| features = genesis_features() |
| payload = { |
| "world": { |
| "version": len(features), |
| "epoch": 0, |
| "features": features, |
| } |
| } |
| return json.dumps(payload, indent=2) + "\n" |
|
|
|
|
| def main() -> None: |
| constants_path = ROOT / "web" / "city" / "constants.js" |
| world_path = ROOT / "web" / "mock" / "world.json" |
| world_path.parent.mkdir(parents=True, exist_ok=True) |
| constants_path.write_text(constants_js(), encoding="utf-8") |
| world_path.write_text(world_json(), encoding="utf-8") |
| print(f"wrote {constants_path.relative_to(ROOT)}") |
| print(f"wrote {world_path.relative_to(ROOT)}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|