{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://wanxiang.ai/contracts/play-loop/game-event.schema.json", "title": "GameEvent", "description": "playtest/2 与 playtest/3 共用的版本化业务事件。ctx.log 诊断不属于本契约;只有 ctx.event 产生的 native 事件和 historical_replay 明确适配的 legacy 事件可以进入业务证据流。playtest/3 可追加 eventRole 区分当前 profile proof 与跨 profile observation。", "type": "object", "required": [ "eventVersion", "evidenceMode", "scope", "namespace", "type", "message", "payload", "payloadCanonical", "payloadHash", "producer", "seq", "actionId", "virtualTimeMs", "sourceEventVersion", "adapterVersion", "sourceMessageHash" ], "properties": { "eventVersion": { "const": "game-event/1" }, "evidenceMode": { "enum": ["native", "legacy-adapted"] }, "scope": { "const": "game" }, "namespace": { "type": "string", "pattern": "^game/[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" }, "type": { "$ref": "#/$defs/eventType" }, "eventRole": { "enum": ["profile-proof", "cross-profile-observation"], "description": "playtest/3 运行时分类。当前 proof profile 事件可参与义务候选;其它已注册 profile 事件只供 Judge 观察,不能满足义务。" }, "message": { "type": "string" }, "payload": { "type": "object" }, "payloadCanonical": { "type": "string", "minLength": 2, "maxLength": 65536, "description": "Node runner 的 stableStringify(payload) 原文;payloadHash 只对这段 UTF-8 文本取 SHA-256,跨语言消费者不得重序列化浮点复算。" }, "payloadHash": { "$ref": "#/$defs/sha256" }, "producer": { "type": "string", "minLength": 1 }, "seq": { "type": "integer", "minimum": 1 }, "actionId": { "type": "string", "pattern": "^action-[1-9][0-9]*$" }, "virtualTimeMs": { "type": "integer", "minimum": 0 }, "sourceEventVersion": { "type": ["string", "null"] }, "adapterVersion": { "type": ["string", "null"] }, "sourceMessageHash": { "anyOf": [ { "$ref": "#/$defs/sha256" }, { "type": "null" } ] } }, "patternProperties": { "^_": {} }, "additionalProperties": false, "allOf": [ { "if": { "properties": { "evidenceMode": { "const": "native" } }, "required": ["evidenceMode"] }, "then": { "properties": { "sourceEventVersion": { "type": "null" }, "adapterVersion": { "type": "null" }, "sourceMessageHash": { "type": "null" } } }, "else": { "properties": { "sourceEventVersion": { "const": "legacy-game-log/1" }, "adapterVersion": { "type": "string", "minLength": 1 }, "sourceMessageHash": { "$ref": "#/$defs/sha256" } } } }, { "if": { "properties": { "type": { "const": "puzzle.swap-committed" } }, "required": ["type"] }, "then": { "properties": { "payload": { "$ref": "#/$defs/swapCommittedPayload" } } } }, { "if": { "properties": { "type": { "const": "puzzle.match-formed" } }, "required": ["type"] }, "then": { "properties": { "payload": { "$ref": "#/$defs/matchFormedPayload" } } } } ], "$defs": { "eventType": { "type": "string", "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" }, "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "cell": { "type": "object", "required": ["row", "column"], "properties": { "row": { "type": "integer", "minimum": 1, "maximum": 12 }, "column": { "type": "integer", "minimum": 1, "maximum": 12 } }, "additionalProperties": false }, "pair": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "$ref": "#/$defs/cell" } }, "swapCommittedPayload": { "type": "object", "required": ["moveId", "pair", "boardRevisionBefore", "boardRevisionAfter"], "properties": { "moveId": { "type": "string", "minLength": 1 }, "pair": { "$ref": "#/$defs/pair" }, "boardRevisionBefore": { "type": "integer", "minimum": 0 }, "boardRevisionAfter": { "type": "integer", "minimum": 1 } }, "additionalProperties": false }, "matchRun": { "type": "object", "required": ["axis", "cells"], "properties": { "axis": { "enum": ["horizontal", "vertical"] }, "cells": { "type": "array", "minItems": 3, "maxItems": 12, "items": { "$ref": "#/$defs/cell" } } }, "additionalProperties": false }, "matchFormedPayload": { "type": "object", "required": ["moveId", "runs", "matchedCells", "matchedCount"], "properties": { "moveId": { "type": "string", "minLength": 1 }, "runs": { "type": "array", "minItems": 1, "maxItems": 8, "items": { "$ref": "#/$defs/matchRun" } }, "matchedCells": { "type": "array", "minItems": 3, "maxItems": 144, "items": { "$ref": "#/$defs/cell" } }, "matchedCount": { "type": "integer", "minimum": 3, "maximum": 144 } }, "additionalProperties": false } } }