{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://wonghongwei.com/api/sg-mahjong/research/v1/submission.schema.json",
  "title": "Singapore Mahjong research submission",
  "description": "Pilot contract for de-identified table-rule evidence, expert-labelled AI positions and observed product-use findings. Schema validity does not establish that a claim is true.",
  "type": "object",
  "additionalProperties": false,
  "required": ["schemaVersion", "contractVersion", "submissionStatus", "studyId", "generatedAt", "dataPolicy", "evidence", "tableProfiles", "aiPositions", "observations", "decisionPackets"],
  "properties": {
    "schemaVersion": { "const": 1 },
    "contractVersion": { "const": "1.0.0" },
    "submissionStatus": { "enum": ["template", "draft", "pilot", "final"] },
    "studyId": { "type": "string", "minLength": 4, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]+$" },
    "generatedAt": { "type": "string", "format": "date-time" },
    "dataPolicy": { "$ref": "#/$defs/dataPolicy" },
    "evidence": { "type": "array", "items": { "$ref": "#/$defs/evidence" } },
    "tableProfiles": { "type": "array", "items": { "$ref": "#/$defs/tableProfile" } },
    "aiPositions": { "type": "array", "items": { "$ref": "#/$defs/aiPosition" } },
    "observations": { "type": "array", "items": { "$ref": "#/$defs/observation" } },
    "decisionPackets": { "type": "array", "maxItems": 10, "items": { "$ref": "#/$defs/decisionPacket" } }
  },
  "$defs": {
    "tile": { "type": "string", "pattern": "^(?:[bcd][1-9]|[ESWN]|[RGW]D|F[1-4]|S[1-4]|RAT|CAT|ROOSTER|CENTIPEDE)$" },
    "seat": { "type": "integer", "minimum": 0, "maximum": 3 },
    "idList": { "type": "array", "items": { "type": "string", "minLength": 3, "maxLength": 80 }, "uniqueItems": true },
    "dataPolicy": {
      "type": "object", "additionalProperties": false,
      "required": ["containsDirectIdentifiers", "includesRawRecordings", "representativeness", "scopeStatement", "retentionNote"],
      "properties": {
        "containsDirectIdentifiers": { "const": false },
        "includesRawRecordings": { "const": false },
        "representativeness": { "enum": ["sample-only", "not-applicable"] },
        "scopeStatement": { "type": "string", "minLength": 20, "maxLength": 1000 },
        "retentionNote": { "type": "string", "minLength": 20, "maxLength": 1000 }
      }
    },
    "evidence": {
      "type": "object", "additionalProperties": false,
      "required": ["id", "type", "status", "title", "observedAt", "limitation"],
      "properties": {
        "id": { "type": "string", "pattern": "^EV-[A-Za-z0-9._-]+$" },
        "type": { "enum": ["public-rulebook", "interview-note", "table-observation", "gameplay-log", "expert-review", "usability-observation"] },
        "status": { "enum": ["established", "documented", "observed", "experimental", "unresolved"] },
        "title": { "type": "string", "minLength": 3, "maxLength": 200 },
        "observedAt": { "type": "string", "format": "date" },
        "publicUrl": { "type": "string", "format": "uri", "maxLength": 500 },
        "limitation": { "type": "string", "minLength": 10, "maxLength": 1000 }
      }
    },
    "tableProfile": {
      "type": "object", "additionalProperties": false,
      "required": ["id", "label", "scope", "evidenceIds", "dimensions", "limitations"],
      "properties": {
        "id": { "type": "string", "pattern": "^TP-[A-Za-z0-9._-]+$" },
        "label": { "type": "string", "minLength": 3, "maxLength": 160 },
        "scope": {
          "type": "object", "additionalProperties": false,
          "required": ["tableType", "participantCount", "experienceBands", "languageContexts", "publicInstitution"],
          "properties": {
            "tableType": { "enum": ["household", "friend-group", "instructor", "club", "competition", "other"] },
            "participantCount": { "type": "integer", "minimum": 1, "maximum": 10000 },
            "experienceBands": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "enum": ["beginner", "regular", "experienced", "mixed", "not-recorded"] } },
            "languageContexts": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 2, "maxLength": 80 } },
            "publicInstitution": { "type": "boolean" }
          }
        },
        "evidenceIds": { "$ref": "#/$defs/idList" },
        "dimensions": { "type": "array", "items": { "$ref": "#/$defs/ruleDimension" } },
        "limitations": { "type": "array", "items": { "type": "string", "minLength": 10, "maxLength": 1000 } }
      }
    },
    "ruleDimension": {
      "type": "object", "additionalProperties": false,
      "required": ["path", "value", "evidenceIds", "confidence", "materialEffect"],
      "properties": {
        "path": { "enum": [
          "tileSet.composition", "tileSet.replacement", "setup.wall", "setup.diceBreak", "setup.dealerJump", "setup.supplementWall", "setup.finalLiveTile",
          "claims.priority", "claims.timing", "claims.multipleHu", "claims.robbedKong", "claims.lastTile", "claims.passedHu", "claims.passedSet", "claims.lockReset", "claims.chiSwap", "claims.ownDiscard",
          "scoring.minimumTai", "scoring.capTai", "scoring.baseUnit", "payments.discard", "payments.selfDraw", "payments.dealer", "payments.bao", "payments.multipleWinner",
          "bao.trigger", "bao.duration", "bao.liableEvents", "bao.selfDraw", "bao.multipleWinner",
          "pingHu.shape", "pingHu.eye", "pingHu.bonuses", "pingHu.openChi", "pingHu.wait", "pingHu.selfDraw", "pingHu.tai", "pingHu.stacking",
          "immediate.kong", "immediate.animals", "immediate.flowersSeasons", "immediate.bites", "specialHands.catalog", "specialHands.stacking",
          "penalties.falseHu", "penalties.falseClaim", "round.draw", "round.dealerRetention", "round.prevailingWind", "round.sessionEnd"
        ] },
        "value": { "oneOf": [{ "type": "null" }, { "type": "boolean" }, { "type": "number" }, { "type": "string", "maxLength": 2000 }, { "type": "array", "maxItems": 100 }, { "type": "object" }] },
        "unknownReason": { "type": "string", "minLength": 10, "maxLength": 1000 },
        "evidenceIds": { "$ref": "#/$defs/idList" },
        "confidence": { "enum": ["high", "medium", "low", "mixed", "unresolved"] },
        "materialEffect": { "type": "string", "minLength": 10, "maxLength": 1000 },
        "contradiction": { "type": "string", "minLength": 10, "maxLength": 1000 }
      }
    },
    "meld": {
      "type": "object", "additionalProperties": false, "required": ["type", "tiles"],
      "properties": { "type": { "enum": ["chi", "pong", "kong"] }, "tiles": { "type": "array", "minItems": 3, "maxItems": 4, "items": { "$ref": "#/$defs/tile" } } }
    },
    "publicSeat": {
      "type": "object", "additionalProperties": false, "required": ["seat", "melds", "bonuses"],
      "properties": { "seat": { "$ref": "#/$defs/seat" }, "melds": { "type": "array", "items": { "$ref": "#/$defs/meld" } }, "bonuses": { "type": "array", "items": { "$ref": "#/$defs/tile" } } }
    },
    "publicState": {
      "type": "object", "additionalProperties": false,
      "required": ["roundWind", "dealerSeat", "turnSeat", "wallTilesRemaining", "liveWallTilesRemaining", "discards", "openTiles", "chipBalances"],
      "properties": {
        "roundWind": { "enum": ["E", "S", "W", "N"] }, "dealerSeat": { "$ref": "#/$defs/seat" }, "turnSeat": { "$ref": "#/$defs/seat" },
        "wallTilesRemaining": { "type": "integer", "minimum": 0, "maximum": 148 }, "liveWallTilesRemaining": { "type": "integer", "minimum": 0, "maximum": 148 },
        "discards": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["seat", "tile", "sequence"], "properties": { "seat": { "$ref": "#/$defs/seat" }, "tile": { "$ref": "#/$defs/tile" }, "sequence": { "type": "integer", "minimum": 1 } } } },
        "openTiles": { "type": "array", "minItems": 4, "maxItems": 4, "items": { "$ref": "#/$defs/publicSeat" } },
        "chipBalances": { "type": "array", "minItems": 4, "maxItems": 4, "items": { "type": "number" } },
        "lastDiscardSeat": { "$ref": "#/$defs/seat" }, "lastDiscardTile": { "$ref": "#/$defs/tile" }
      }
    },
    "legalAction": {
      "type": "object", "additionalProperties": false, "required": ["id", "type"],
      "properties": {
        "id": { "type": "string", "pattern": "^ACT-[A-Za-z0-9._-]+$" }, "type": { "enum": ["draw", "discard", "pass", "chi", "pong", "kong", "hu"] },
        "tile": { "$ref": "#/$defs/tile" }, "tiles": { "type": "array", "minItems": 2, "maxItems": 4, "items": { "$ref": "#/$defs/tile" } }, "fromSeat": { "$ref": "#/$defs/seat" }
      }
    },
    "expertReview": {
      "type": "object", "additionalProperties": false,
      "required": ["reviewerId", "rankedActionIds", "acceptableActionIds", "severeMistakeActionIds", "rationale", "tags", "confidence"],
      "properties": {
        "reviewerId": { "type": "string", "pattern": "^REV-[A-Za-z0-9._-]+$" },
        "rankedActionIds": { "type": "array", "minItems": 1, "maxItems": 3, "uniqueItems": true, "items": { "type": "string" } },
        "acceptableActionIds": { "$ref": "#/$defs/idList" }, "severeMistakeActionIds": { "$ref": "#/$defs/idList" },
        "rationale": { "type": "string", "minLength": 20, "maxLength": 3000 },
        "tags": { "type": "array", "uniqueItems": true, "items": { "enum": ["offence", "defence", "mixed", "risk", "value", "tempo", "wait-quality", "tile-scarcity", "profile-rule"] } },
        "confidence": { "enum": ["high", "medium", "low"] }
      }
    },
    "aiPosition": {
      "type": "object", "additionalProperties": false,
      "required": ["id", "profileId", "evidenceIds", "split", "phase", "actingSeat", "actingHand", "publicState", "legalActions", "expertReviews"],
      "properties": {
        "id": { "type": "string", "pattern": "^AI-[A-Za-z0-9._-]+$" }, "profileId": { "type": "string", "minLength": 3, "maxLength": 100 },
        "evidenceIds": { "$ref": "#/$defs/idList" }, "split": { "enum": ["pilot", "tuning", "holdout"] }, "phase": { "enum": ["early", "middle", "late"] },
        "decisionType": { "enum": ["discard", "claim", "kong", "hu", "pass"] }, "actingSeat": { "$ref": "#/$defs/seat" },
        "actingHand": { "type": "array", "minItems": 1, "maxItems": 14, "items": { "$ref": "#/$defs/tile" } },
        "publicState": { "$ref": "#/$defs/publicState" }, "legalActions": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/legalAction" } },
        "expertReviews": { "type": "array", "items": { "$ref": "#/$defs/expertReview" } },
        "difficulty": { "enum": ["easy", "standard", "hard"] }, "measuredLatencyMs": { "type": "number", "minimum": 0 }
      }
    },
    "observation": {
      "type": "object", "additionalProperties": false,
      "required": ["id", "evidenceIds", "sessionType", "participantCount", "experienceBands", "device", "connectivity", "directIdentifiersCollected", "recordingConsent", "tasks", "issues"],
      "properties": {
        "id": { "type": "string", "pattern": "^OBS-[A-Za-z0-9._-]+$" }, "evidenceIds": { "$ref": "#/$defs/idList" },
        "sessionType": { "enum": ["beginner-learning", "physical-table"] }, "participantCount": { "type": "integer", "minimum": 1, "maximum": 20 },
        "experienceBands": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "enum": ["first-time", "beginner", "regular", "experienced", "mixed"] } },
        "device": { "type": "string", "minLength": 3, "maxLength": 160 }, "viewportWidth": { "type": "integer", "minimum": 240, "maximum": 10000 },
        "connectivity": { "enum": ["offline", "poor", "typical-mobile", "wifi", "mixed"] }, "directIdentifiersCollected": { "const": false }, "recordingConsent": { "type": "boolean" },
        "tasks": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["id", "label", "outcome", "errorCount"], "properties": {
          "id": { "type": "string", "pattern": "^TASK-[A-Za-z0-9._-]+$" }, "label": { "type": "string", "minLength": 3, "maxLength": 200 },
          "outcome": { "enum": ["completed", "partial", "abandoned", "not-attempted"] }, "durationSeconds": { "type": "number", "minimum": 0 }, "errorCount": { "type": "integer", "minimum": 0 }, "note": { "type": "string", "maxLength": 2000 }
        } } },
        "issues": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["id", "severity", "surface", "finding", "materialEffect", "proposedChange"], "properties": {
          "id": { "type": "string", "pattern": "^ISSUE-[A-Za-z0-9._-]+$" }, "severity": { "type": "integer", "minimum": 1, "maximum": 4 },
          "surface": { "enum": ["guide", "game", "companion", "profile-share", "scorer", "backup", "offline", "install", "other"] },
          "finding": { "type": "string", "minLength": 20, "maxLength": 2000 }, "materialEffect": { "type": "string", "minLength": 10, "maxLength": 1000 }, "proposedChange": { "type": "string", "minLength": 10, "maxLength": 2000 }
        } } }
      }
    },
    "decisionPacket": {
      "type": "object", "additionalProperties": false,
      "required": ["id", "decision", "evidenceIds", "materialEffect", "specification", "migration", "verification", "privacyAccessibility", "openDecision"],
      "properties": {
        "id": { "type": "string", "pattern": "^DEC-[A-Za-z0-9._-]+$" }, "decision": { "enum": ["implement-now", "prototype-test", "retain-current", "reject"] },
        "evidenceIds": { "$ref": "#/$defs/idList" }, "materialEffect": { "type": "string", "minLength": 10, "maxLength": 2000 },
        "specification": { "type": "string", "minLength": 20, "maxLength": 5000 }, "migration": { "type": "string", "minLength": 10, "maxLength": 3000 },
        "verification": { "type": "string", "minLength": 10, "maxLength": 3000 }, "privacyAccessibility": { "type": "string", "minLength": 10, "maxLength": 3000 },
        "openDecision": { "type": ["string", "null"], "maxLength": 2000 }
      }
    }
  }
}
