7.4 KiB
What a “contract” is (Neon Sprawl)
Decomposition modules and the dependency register use contract as the name for a stable boundary: something two systems (client and server, two server subsystems, or server and content pipeline) can depend on without sharing an undocumented struct.
This doc fixes artifact types, ownership, and versioning so “Contract needed” in the register is actionable.
Baseline stack alignment: tech stack — authoritative server in C#; client in Godot/GDScript; Protobuf for versioned client–server messages (JSON acceptable only for the earliest spike); JSON/YAML + JSON Schema for content tables. Who owns truth for movement, camera, and combat: client vs server authority.
Contract kinds
| Kind | Use for | Authoritative form | Typical consumers |
|---|---|---|---|
| Wire | Player intents, state snapshots, RPC payloads crossing the network | Protocol Buffer .proto definitions; generated C# on server; mirrored types or codegen path in GDScript client |
Client runtime, server host |
| Server-internal | Shapes used only inside the ASP.NET process (persistence DTOs, internal queues) until promoted | C# records/types in the server solution; behavior covered by tests | Server modules only |
| Content | Data-driven defs (SkillDef, RecipeDef, QuestDef, …) |
JSON or YAML files + JSON Schema (or CUE later) validated in CI | Server load; tools; optional client bundles |
| Telemetry | Analytics and ops events (E9.M1) | Versioned event catalog (JSON Schema or equivalent) + transport envelope; align names with OpenTelemetry where practical | Client emitters, ingest, dashboards |
| HTTP (optional) | Health, admin, or tooling — not the primary game sim path | OpenAPI if exposed; keep game loop on Protobuf per tech stack | Ops, CI, external tools |
A single logical name (e.g. MoveCommand) might appear as a protobuf message on the wire and a different internal server type that maps to/from it; the wire schema is what client and server must agree on. Until Protobuf exists for a message, a spike may use JSON with an explicit throwaway note in PRs and a tracked issue to migrate.
Mapping module “Key contracts” to kinds
Rough default (exceptions per module):
- Intents and state sync (
MoveCommand,PositionState,CombatAction,CombatResolution, …) → Wire (protobuf). - Catalog / defs (
RecipeDef,QuestDef,ItemDef,SkillDef, …) → Content (files + JSON Schema). - Pure server resolution (e.g. internal threat tables, idempotency keys not sent to client) → Server-internal until a client or tool needs them; then promote to wire or document as admin-only HTTP.
When unsure, default to wire protobuf for anything the Godot client must send or interpret; default to content + schema for anything authored as bulk data.
Repository layout (convention)
Paths are conventions until the first codegen lands; adjust in one doc PR when you add the first artifact.
| Artifact | Planned location | Notes |
|---|---|---|
.proto files |
contracts/proto/ (repo root) |
Keeps schemas visible to both server and client tooling; generate C# into server/ and client bindings per chosen Godot/protobuf workflow |
| JSON Schema for content | contracts/schemas/content/ (or co-located schema.json next to data) |
Validate in CI before server boot in strict modes |
| Telemetry catalog | contracts/schemas/telemetry/ or under docs/ until ingest exists |
Must carry schema / semver for E9.M1 |
Server-only helpers stay next to features (e.g. NeonSprawl.Server/Game/...) until extracted to a shared assembly if multiple hosts need them.
Naming
- Register and epics use PascalCase names (
CraftRequest,LevelUpEvent). Protobuf messages use the same PascalCase style; protobuf field names followsnake_casein.protoif you adopt common style guides (generated C# still maps to idiomatic names). - JSON Schema
$idand file names should include a version or folder (v1/) when breaking changes ship.
Versioning and “Ready”
- Wire: additive changes via new fields and reserved numbers; breaking changes require a new message or a negotiated major bump and client rollout strategy.
- Content: schema version in CI; server rejects or migrates unknown versions per policy.
- Telemetry: bump EventSchemaVersion (E9.M1); document in changelog for dashboards.
A module’s register Status can move to Ready when the contracts it exports are published in the agreed form (proto checked in, or schema in CI, plus a minimal consumer test or stub).
What this doc does not decide
- Exact Protobuf package layout and service vs unary choices — decide when the first real RPC is added.
- Godot protobuf library choice — tooling decision; still must consume the same
.prototruth as the server.
Related docs
- tech stack — Protobuf vs JSON spike, content validation, client/server split
- Client vs server authority — intents vs state, E1/E5 defaults, deferred networking choices
- PvP and the combat engine — E5.M1 gating with E6.M1, deny reasons
- Quest scope and party rules — per-character quest state, party credit, turn-in
- Data reload and telemetry operations policy — content reload, PII, sampling, retention, baseline events
- Documentation and implementation alignment — register Status vs repo, tracking table
- Module dependency register
- Decomposition README