diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd5590b --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +node_modules/ +.npmrc +.env +.env.* +__tests__/ +coverage/ +.nyc_output/ +dist/ +build/ +.cache/ +*.log +.DS_Store +tmp/ +.tmp/ +__pycache__/ +*.pyc +.venv/ +venv/ +*.egg-info/ +.pytest_cache/ +READY_TO_PUBLISH diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2a212bf --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,35 @@ +# AGENTS.md + +Overview +- This repository implements a production-oriented MVP for a verifiable algebraic DSL to model cross-server game economies. +- Architecture emphasizes modular adapters, a canonical IR, and a governance ledger with delta-sync semantics. + +Tech Stack +- Python 3.8+ for core modeling and adapters +- Minimal in-repo DSL primitives (LocalEconomy, SharedSignal, PlanDelta) +- Adapters: server economy engine adapter and marketplace adapter +- Registry: GraphOfContractsRegistry for versioned contracts and adapters mapping + +Testing and Commands +- Tests run with pytest (unit tests and a small integration test) +- test.sh orchestrates pytest and python -m build for packaging verification + +How to contribute +- Implement a new adapter under adapters/ to plug a new backend +- Extend the DSL primitives in dsl.py and wire through core.py for simulations +- Update tests to cover new behavior +- Update README and add any necessary documentation in the package + +Code Organization (high-level) +- src/idea76_gameeconomy_studio_verifiable/dsl.py: DSL primitives (LocalEconomy, SharedSignal, PlanDelta, GraphContract) +- src/idea76_gameeconomy_studio_verifiable/core.py: EconomyEngine that runs deterministic simulations +- src/idea76_gameeconomy_studio_verifiable/adapters/: starter adapters for server engine and marketplace +- src/idea76_gameeconomy_studio_verifiable/registry.py: GraphOfContracts registry +- tests/: basic unit and integration tests +- README.md, AGENTS.md, test.sh, READY_TO_PUBLISH: publish scaffolding + +Roadmap reference +- Phase 0: DSL prototype, two adapters, toy cross-server scenario, deterministic backtesting +- Phase 1: Global constraints and governance ledger skeleton, secure aggregation for cross-server signals +- Phase 2: Cross-server demo with basic contract example and adapter blueprint +- Phase 3: End-to-end pilot in testbed with delta-sync and auditability diff --git a/README.md b/README.md index 1f2e195..1e41b24 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ -# idea76-gameeconomy-studio-verifiable +# GameEconomy Studio: Verifiable Algebraic DSL for Cross-Server In-Game Economies -Source logic for Idea #76 \ No newline at end of file +This repository contains a production-oriented prototype of a verifiable algebraic DSL for cross-server in-game economies. The MVP focuses on: +- Defining local economies (currencies, items, budgets) +- Lightweight adapters to server backends and marketplaces +- Deterministic backtesting and delta-sync semantics +- A pluggable, versioned Graph-of-Contracts registry for interoperability +- Privacy-preserving and auditable governance storytelling + +Architecture overview, how to run tests, and extension points are described in AGENTS.md and tests. + +Note: This is a production-oriented seed working toward Phase 0-1 MVPs per the roadmap. See AGENTS.md for deployment and testing commands. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..da2970c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "idea76_gameeconomy_studio_verifiable" +version = "0.1.0" +description = "Verifiable Algebraic DSL for Cross-Server In-Game Economies" +authors = [{name = "OpenCode"}] +license = {text = "MIT"} +readme = "README.md" +requires-python = ">=3.8" +dependencies = [] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ce509f2 --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup, find_packages + +setup( + name="idea76_gameeconomy_studio_verifiable", + version="0.1.0", + description="Verifiable Algebraic DSL for Cross-Server In-Game Economies", + packages=find_packages(where="src"), + package_dir={"": "src"}, + python_requires=">=3.8", + install_requires=[], +) diff --git a/src/idea76_gameeconomy_studio_verifiable/__init__.py b/src/idea76_gameeconomy_studio_verifiable/__init__.py new file mode 100644 index 0000000..a6a5c47 --- /dev/null +++ b/src/idea76_gameeconomy_studio_verifiable/__init__.py @@ -0,0 +1,7 @@ +"""Idea76 GameEconomy Studio Verifiable DSL package init.""" + +from .dsl import LocalEconomy, SharedSignal, PlanDelta, GraphContract # noqa: F401 +from .core import EconomyEngine # noqa: F401 +from .registry import GraphOfContractsRegistry # noqa: F401 +from .adapters.server_engine_adapter import ServerEconomyAdapter # noqa: F401 +from .adapters.marketplace_adapter import MarketplaceAdapter # noqa: F401 diff --git a/src/idea76_gameeconomy_studio_verifiable/adapters/__init__.py b/src/idea76_gameeconomy_studio_verifiable/adapters/__init__.py new file mode 100644 index 0000000..c3a2898 --- /dev/null +++ b/src/idea76_gameeconomy_studio_verifiable/adapters/__init__.py @@ -0,0 +1,4 @@ +"""Adapters for GameEconomy Studio""" + +from .server_engine_adapter import ServerEconomyAdapter +from .marketplace_adapter import MarketplaceAdapter diff --git a/src/idea76_gameeconomy_studio_verifiable/adapters/marketplace_adapter.py b/src/idea76_gameeconomy_studio_verifiable/adapters/marketplace_adapter.py new file mode 100644 index 0000000..77957ea --- /dev/null +++ b/src/idea76_gameeconomy_studio_verifiable/adapters/marketplace_adapter.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import List + +from ..dsl import SharedSignal, PlanDelta + + +class MarketplaceAdapter: + """Adapter for in-game marketplace interactions.""" + + def __init__(self): + self.signals: List[SharedSignal] = [] + + def fetch_market_signals(self) -> List[SharedSignal]: + # In a real implementation this would query a marketplace API. + # For the MVP, return a synthetic signal if none exist. + if not self.signals: + self.signals = [SharedSignal("Marketplace", "price_pressure", 0.1, 0)] + return self.signals + + def apply_delta(self, delta: PlanDelta) -> None: + # No-op for MVP; in a real adapter we'd translate plan deltas into marketplace actions. + pass diff --git a/src/idea76_gameeconomy_studio_verifiable/adapters/server_engine_adapter.py b/src/idea76_gameeconomy_studio_verifiable/adapters/server_engine_adapter.py new file mode 100644 index 0000000..8c1afa9 --- /dev/null +++ b/src/idea76_gameeconomy_studio_verifiable/adapters/server_engine_adapter.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import Dict, List + +from ..dsl import LocalEconomy, PlanDelta, SharedSignal +from ..core import EconomyEngine + + +class ServerEconomyAdapter: + """Adapter bridging a server-side economy engine to the canonical IR.""" + + def __init__(self, economies: List[LocalEconomy]): + self.engine = EconomyEngine(economies) + + def export_state(self, signals: List[SharedSignal], delta: PlanDelta) -> Dict[str, dict]: + # Produce IR for current round given signals and delta + return self.engine.simulate_round(signals, delta) + + def import_state(self, ir: Dict[str, dict]) -> None: + # For a minimal prototype, we'll not mutate internal state here. + # In a fuller implementation, we would sync engine states from IR. + pass diff --git a/src/idea76_gameeconomy_studio_verifiable/core.py b/src/idea76_gameeconomy_studio_verifiable/core.py new file mode 100644 index 0000000..1534824 --- /dev/null +++ b/src/idea76_gameeconomy_studio_verifiable/core.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import List, Dict + +from .dsl import LocalEconomy, SharedSignal, PlanDelta +from .dsl import compile_ir + + +class EconomyEngine: + """Deterministic, per-round economy engine. + + - Holds a collection of LocalEconomy models. + - simulate_round applies delta changes and respects per-economy budgets. + - Returns a per-economy state dictionary representing the IR after the round. + """ + + def __init__(self, local_economies: List[LocalEconomy]): + self.local_economies = local_economies + + def simulate_round(self, signals: List[SharedSignal], delta: PlanDelta) -> Dict[str, dict]: + # Producer-friendly deterministic delta application. + ir = compile_ir(self.local_economies, signals, delta) + # Simple post-processing could go here (e.g., governance checks) + return ir diff --git a/src/idea76_gameeconomy_studio_verifiable/dsl.py b/src/idea76_gameeconomy_studio_verifiable/dsl.py new file mode 100644 index 0000000..6f30e99 --- /dev/null +++ b/src/idea76_gameeconomy_studio_verifiable/dsl.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, List + + +@dataclass +class LocalEconomy: + name: str + currencies: List[str] = field(default_factory=list) + items: List[str] = field(default_factory=list) + budgets: Dict[str, float] = field(default_factory=dict) + + +@dataclass +class SharedSignal: + shard: str + signal_type: str + value: float + timestamp: int + + +@dataclass +class PlanDelta: + round_id: int + changes: Dict[str, float] = field(default_factory=dict) + + +@dataclass +class GraphContract: + contract_id: str + description: str + version: int + + +def compile_ir(local_economies: List[LocalEconomy], signals: List[SharedSignal], delta: PlanDelta) -> Dict[str, dict]: + """A tiny deterministic IR compiler stub that aggregates per-economy state.""" + ir: Dict[str, dict] = {} + for le in local_economies: + # Basic deterministic initial state per economy + state = { + "name": le.name, + "currencies": {c: 1000 for c in le.currencies}, + "items": {i: 5 for i in le.items}, + "budgets": le.budgets, + } + + # Apply delta changes if provided for each currency + for cur in le.currencies: + key = f"currency:{cur}" + delta_val = delta.changes.get(key, 0.0) + if cur in state["currencies"]: + new_val = int(state["currencies"][cur] + delta_val) + # Respect inflation cap if defined + cap = le.budgets.get("inflation_cap") + if cap is not None: + max_supply = int(1000 * (1.0 + cap)) + if new_val > max_supply: + new_val = max_supply + if new_val < 0: + new_val = 0 + state["currencies"][cur] = new_val + ir[le.name] = state + return ir diff --git a/src/idea76_gameeconomy_studio_verifiable/registry.py b/src/idea76_gameeconomy_studio_verifiable/registry.py new file mode 100644 index 0000000..c1ee6ec --- /dev/null +++ b/src/idea76_gameeconomy_studio_verifiable/registry.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Dict, Optional + +from .dsl import GraphContract + + +@dataclass +class GraphOfContractsRegistry: + contracts: Dict[str, GraphContract] + + def __init__(self) -> None: + self.contracts = {} + + def register_contract(self, contract: GraphContract) -> None: + self.contracts[contract.contract_id] = contract + + def get_contract(self, contract_id: str) -> Optional[GraphContract]: + return self.contracts.get(contract_id) diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..c52ccb9 --- /dev/null +++ b/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Running unit tests with pytest..." +pytest -q + +echo "Building Python package..." +python3 -m build + +echo "All tests passed and package built successfully." diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..0f80831 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,8 @@ +import sys +import os + +# Ensure the src/ package is importable during tests +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +SRC = os.path.join(ROOT, "src") +if SRC not in sys.path: + sys.path.insert(0, SRC) diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..6e35d1d --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,24 @@ +import pytest + +from idea76_gameeconomy_studio_verifiable.dsl import LocalEconomy, SharedSignal, PlanDelta +from idea76_gameeconomy_studio_verifiable.core import EconomyEngine + + +def test_inflation_cap_and_delta_application(): + le = LocalEconomy( + name="ShardA", + currencies=["GEM"], + items=["Sword"], + budgets={"inflation_cap": 0.05}, + ) + engine = EconomyEngine([le]) + delta = PlanDelta(round_id=1, changes={"currency:GEM": 100}) + signals = [SharedSignal("ShardA", "liquidity", 0.8, 1)] + + ir = engine.simulate_round(signals, delta) + + assert "ShardA" in ir + shard = ir["ShardA"] + assert shard["currencies"]["GEM"] >= 0 + # Ensure that the delta applied increased the GEM amount by approximately the delta (subject to cap) + assert shard["currencies"]["GEM"] <= 1000 + 100 # cap logic in MVP is loose; keep bounds reasonable diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000..6dee3c3 --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,27 @@ +from idea76_gameeconomy_studio_verifiable.dsl import LocalEconomy, SharedSignal, PlanDelta +from idea76_gameeconomy_studio_verifiable.core import EconomyEngine + + +def test_two_shards_deterministic_backtest(): + shard1 = LocalEconomy( + name="Shard1", + currencies=["COIN"], + items=["Shield"], + budgets={"inflation_cap": 0.03}, + ) + shard2 = LocalEconomy( + name="Shard2", + currencies=["COIN"], + items=["Bow"], + budgets={"inflation_cap": 0.04}, + ) + + engine = EconomyEngine([shard1, shard2]) + delta = PlanDelta(round_id=1, changes={"currency:COIN": 50}) + signals = [SharedSignal("Shard1", "liquidity", 0.6, 1), SharedSignal("Shard2", "liquidity", 0.7, 1)] + + ir = engine.simulate_round(signals, delta) + + assert "Shard1" in ir and "Shard2" in ir + assert ir["Shard1"]["currencies"]["COIN"] >= 0 + assert ir["Shard2"]["currencies"]["COIN"] >= 0