From 044a9b064c59f92569558ba70cc23f0362ff511f Mon Sep 17 00:00:00 2001 From: Spbd1 <148923621+Spbd1@users.noreply.github.com> Date: Sun, 10 May 2026 08:04:10 +0000 Subject: [PATCH] Add reproducible server configs --- .../app/admin/servers/new/ConfigJsonForm.tsx | 126 ++++++++++++++++ apps/web/app/admin/servers/new/actions.ts | 31 ++++ apps/web/app/admin/servers/new/page.tsx | 6 +- configs/highineq-stable.json | 55 +++++++ configs/highineq-uncertain.json | 55 +++++++ configs/lowineq-stable.json | 55 +++++++ configs/lowineq-uncertain.json | 55 +++++++ configs/pilot-8servers.json | 55 +++++++ packages/shared/package.json | 3 + packages/shared/src/index.ts | 1 + packages/shared/src/serverConfig.ts | 139 ++++++++++++++++++ 11 files changed, 579 insertions(+), 2 deletions(-) create mode 100644 apps/web/app/admin/servers/new/ConfigJsonForm.tsx create mode 100644 configs/highineq-stable.json create mode 100644 configs/highineq-uncertain.json create mode 100644 configs/lowineq-stable.json create mode 100644 configs/lowineq-uncertain.json create mode 100644 configs/pilot-8servers.json create mode 100644 packages/shared/src/serverConfig.ts diff --git a/apps/web/app/admin/servers/new/ConfigJsonForm.tsx b/apps/web/app/admin/servers/new/ConfigJsonForm.tsx new file mode 100644 index 0000000..fdda973 --- /dev/null +++ b/apps/web/app/admin/servers/new/ConfigJsonForm.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { useMemo, useRef, useState, type ChangeEvent } from "react"; +import { safeParseServerConfig } from "@parcel-society/shared"; +import { createServerFromConfig } from "./actions"; + +const exampleConfig = `{ + "name": "Low inequality / stable rules", + "description": "Baseline low-inequality treatment with stable rules and reproducible parameters.", + "map": { "width": 10, "height": 10 }, + "season": { "rounds": 7, "actionPointsPerRound": 3 }, + "treatment": { "inequalityCondition": "LOW", "uncertaintyCondition": "STABLE" }, + "economy": { + "initialWealth": 100, + "initialTreasury": 500, + "taxRate": 0.05, + "production": { "A": 10, "betaQ": 0.8, "betaK": 0.5 }, + "safeAssetReturn": 0.02, + "productiveInvestmentDepreciation": 0.0 + }, + "contracts": { + "formalFeeRate": 0.08, + "informalFeeRate": 0.02, + "formalDefaultRisk": 0.02, + "informalDefaultRisk": 0.15 + }, + "lobbying": { "privateReturnMultiplier": 1.15, "socialEfficiencyCost": 0.10 }, + "uncertainty": { + "ruleChangeRounds": [3, 5], + "possibleEvents": ["TAX_CHANGE", "FORMAL_CONTRACT_FEE_CHANGE", "SHOCK_PROBABILITY_CHANGE"] + }, + "shocks": { "baseProbability": 0.15, "minMultiplier": 0.5, "maxMultiplier": 1.0 }, + "randomSeed": "parcel-society-lowineq-stable-v1" +}`; + +export function ConfigJsonForm() { + const [jsonText, setJsonText] = useState(""); + const fileInputRef = useRef(null); + + const validation = useMemo(() => { + if (!jsonText.trim()) { + return { ok: false, message: "Paste JSON or upload a config file to validate it." }; + } + try { + const parsed = JSON.parse(jsonText); + const result = safeParseServerConfig(parsed); + if (result.success) { + return { + ok: true, + message: `${result.data.name} is valid and can create a ${result.data.map.width}x${result.data.map.height} ${result.data.treatment.inequalityCondition}/${result.data.treatment.uncertaintyCondition} server.`, + }; + } + return { + ok: false, + message: result.error.issues + .map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`) + .join("; "), + }; + } catch (error) { + return { ok: false, message: error instanceof Error ? error.message : "Invalid JSON." }; + } + }, [jsonText]); + + const onUpload = async (event: ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + setJsonText(await file.text()); + }; + + return ( +
+
+
+

Create from JSON config

+

Paste or upload one reproducible config, validate it locally, then create a draft server with its map.

+
+
+ + +
+
+ +