SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;


-- 使用 utf8mb4 连接字符集，保证中文注释、中文名称和 emoji 在导入时不被降级。
CREATE DATABASE IF NOT EXISTS hyapp_game DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE hyapp_game;

CREATE TABLE IF NOT EXISTS game_platforms (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  platform_code VARCHAR(64) NOT NULL COMMENT '平台编码',
  platform_name VARCHAR(128) NOT NULL COMMENT '平台名称',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  api_base_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'API 基准 URL',
  adapter_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT '适配器类型',
  callback_secret_ciphertext VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '回调密钥密文',
  callback_ip_whitelist JSON NULL COMMENT '回调IP白名单',
  adapter_config JSON NULL COMMENT '适配器配置',
  sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重，数值越小越靠前',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, platform_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏平台表';

CREATE TABLE IF NOT EXISTS game_catalog (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  game_id VARCHAR(96) NOT NULL COMMENT '游戏 ID',
  platform_code VARCHAR(64) NOT NULL COMMENT '平台编码',
  provider_game_id VARCHAR(128) NOT NULL COMMENT '提供方游戏 ID',
  game_name VARCHAR(128) NOT NULL COMMENT '游戏名称',
  category VARCHAR(64) NOT NULL DEFAULT '' COMMENT '分类',
  icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '图标 URL',
  cover_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '封面 URL',
  launch_mode VARCHAR(32) NOT NULL DEFAULT 'h5_popup' COMMENT '启动模式',
  orientation VARCHAR(32) NOT NULL DEFAULT 'portrait' COMMENT '屏幕方向',
  safe_height INT NOT NULL DEFAULT 0 COMMENT '游戏安全高',
  min_coin BIGINT NOT NULL DEFAULT 0 COMMENT '最小金币',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重，数值越小越靠前',
  tags JSON NULL COMMENT '标签',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, game_id),
  UNIQUE KEY uk_game_provider(app_code, platform_code, provider_game_id),
  KEY idx_game_platform_status(app_code, platform_code, status, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏目录表';

CREATE TABLE IF NOT EXISTS game_display_rules (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  rule_id VARCHAR(96) NOT NULL COMMENT '规则 ID',
  game_id VARCHAR(96) NOT NULL COMMENT '游戏 ID',
  scene VARCHAR(64) NOT NULL COMMENT '场景',
  region_id BIGINT NOT NULL DEFAULT 0 COMMENT '区域 ID',
  language VARCHAR(32) NOT NULL DEFAULT '' COMMENT '语言',
  platform VARCHAR(16) NOT NULL DEFAULT '' COMMENT '平台',
  visible TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否可见',
  enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '启用',
  sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重，数值越小越靠前',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, rule_id),
  KEY idx_game_display(app_code, scene, region_id, visible, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏展示规则表';

CREATE TABLE IF NOT EXISTS game_launch_sessions (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  session_id VARCHAR(96) NOT NULL COMMENT '会话 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  display_user_id VARCHAR(32) NOT NULL COMMENT '展示用户 ID',
  room_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '房间 ID',
  region_id BIGINT NOT NULL DEFAULT 0 COMMENT '用户启动游戏时的区域 ID',
  scene VARCHAR(64) NOT NULL COMMENT '场景',
  platform_code VARCHAR(64) NOT NULL COMMENT '平台编码',
  game_id VARCHAR(96) NOT NULL COMMENT '游戏 ID',
  provider_game_id VARCHAR(128) NOT NULL COMMENT '提供方游戏 ID',
  launch_token_hash VARCHAR(128) NOT NULL COMMENT '启动令牌哈希值',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  expires_at_ms BIGINT NOT NULL COMMENT '过期时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, session_id),
  KEY idx_game_launch_user(app_code, user_id, created_at_ms),
  KEY idx_game_launch_recent(app_code, user_id, scene, created_at_ms, game_id),
  KEY idx_game_launch_token(app_code, launch_token_hash)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏启动会话表';

CREATE TABLE IF NOT EXISTS game_orders (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  order_id VARCHAR(96) NOT NULL COMMENT '订单 ID',
  platform_code VARCHAR(64) NOT NULL COMMENT '平台编码',
  provider_order_id VARCHAR(160) NOT NULL COMMENT '提供方订单 ID',
  provider_round_id VARCHAR(160) NOT NULL DEFAULT '' COMMENT '提供方轮次 ID',
  game_id VARCHAR(96) NOT NULL COMMENT '游戏 ID',
  provider_game_id VARCHAR(128) NOT NULL COMMENT '提供方游戏 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  room_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '房间 ID',
  region_id BIGINT NOT NULL DEFAULT 0 COMMENT '用户启动游戏时的区域 ID',
  op_type VARCHAR(32) NOT NULL COMMENT 'op类型',
  coin_amount BIGINT NOT NULL COMMENT '金币数量',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '钱包交易 ID',
  wallet_balance_after BIGINT NOT NULL DEFAULT 0 COMMENT '钱包余额后',
  request_hash VARCHAR(128) NOT NULL COMMENT '请求哈希值',
  raw_payload_ref VARCHAR(256) NOT NULL DEFAULT '' COMMENT '原始负载外部引用',
  failure_code VARCHAR(64) NOT NULL DEFAULT '' COMMENT '失败编码',
  failure_message VARCHAR(256) NOT NULL DEFAULT '' COMMENT '失败消息',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, order_id),
  UNIQUE KEY uk_game_provider_order(app_code, platform_code, provider_order_id),
  KEY idx_game_order_user_time(app_code, user_id, created_at_ms),
  KEY idx_game_order_round(app_code, platform_code, provider_round_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏钱包订单表';

CREATE TABLE IF NOT EXISTS game_dice_matches (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  match_id VARCHAR(96) NOT NULL COMMENT '骰子局 ID',
  game_id VARCHAR(96) NOT NULL COMMENT '内部游戏 ID',
  platform_code VARCHAR(64) NOT NULL COMMENT '平台编码快照',
  provider_game_id VARCHAR(128) NOT NULL COMMENT '提供方游戏 ID 快照',
  room_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '房间 ID',
  region_id BIGINT NOT NULL DEFAULT 0 COMMENT '用户区域 ID',
  min_players INT NOT NULL COMMENT '最少开局人数',
  max_players INT NOT NULL COMMENT '最多参与人数',
  current_players INT NOT NULL DEFAULT 0 COMMENT '当前参与人数',
  stake_coin BIGINT NOT NULL COMMENT '单人下注金币',
  round_no INT NOT NULL DEFAULT 1 COMMENT '轮次',
  status VARCHAR(32) NOT NULL COMMENT '局状态',
  result VARCHAR(64) NOT NULL DEFAULT '' COMMENT '局结果',
  join_deadline_ms BIGINT NOT NULL DEFAULT 0 COMMENT '加入截止时间，UTC epoch ms',
  ready_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '匹配完成时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  settled_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '结算时间，UTC epoch ms',
  canceled_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '取消时间，UTC epoch ms',
  fee_bps INT NOT NULL DEFAULT 500 COMMENT '本局平台抽水万分比快照',
  pool_bps INT NOT NULL DEFAULT 100 COMMENT '本局奖池入池万分比快照',
  match_mode VARCHAR(32) NOT NULL DEFAULT '' COMMENT '匹配模式 human/robot',
  forced_result VARCHAR(32) NOT NULL DEFAULT '' COMMENT '机器人局强制结果，新手保护、消耗策略或奖池不足时使用',
  pool_delta_coin BIGINT NOT NULL DEFAULT 0 COMMENT '本局对奖池的净变化',
  PRIMARY KEY(app_code, match_id),
  KEY idx_game_dice_room_active(app_code, room_id, status, created_at_ms),
  KEY idx_game_dice_status_time(app_code, status, updated_at_ms, match_id),
  KEY idx_game_dice_waiting(app_code, game_id, stake_coin, status, created_at_ms, match_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='骰子游戏局表';

CREATE TABLE IF NOT EXISTS game_dice_participants (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  match_id VARCHAR(96) NOT NULL COMMENT '骰子局 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  participant_type VARCHAR(32) NOT NULL DEFAULT 'user' COMMENT '参与者类型 user/robot',
  seat_no INT NOT NULL COMMENT '座位号',
  status VARCHAR(32) NOT NULL COMMENT '参与状态',
  stake_coin BIGINT NOT NULL COMMENT '下注金币',
  dice_points_json JSON NULL COMMENT '骰子点数',
  rps_gesture VARCHAR(32) NOT NULL DEFAULT '' COMMENT '石头剪刀布预选手势',
  result VARCHAR(32) NOT NULL DEFAULT '' COMMENT '参与结果',
  payout_coin BIGINT NOT NULL DEFAULT 0 COMMENT '返奖金币',
  debit_order_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '下注订单 ID',
  payout_order_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '派奖订单 ID',
  refund_order_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '退款订单 ID',
  balance_after BIGINT NOT NULL DEFAULT 0 COMMENT '最近一次钱包账后余额',
  joined_at_ms BIGINT NOT NULL COMMENT '加入时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, match_id, user_id),
  UNIQUE KEY uk_game_dice_seat(app_code, match_id, seat_no),
  KEY idx_game_dice_participant_user(app_code, user_id, updated_at_ms),
  KEY idx_game_dice_participant_status(app_code, match_id, status, seat_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='骰子游戏参与者表';

-- 骰子旧库已经存在 match/participant 表时，CREATE TABLE 不会补列；这里把 H5 实时流程依赖的阶段、费率、机器人和奖池快照列幂等补齐。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'ready_at_ms') = 0,
  'ALTER TABLE game_dice_matches ADD COLUMN ready_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''匹配完成时间，UTC epoch ms'' AFTER join_deadline_ms',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'canceled_at_ms') = 0,
  'ALTER TABLE game_dice_matches ADD COLUMN canceled_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''取消时间，UTC epoch ms'' AFTER settled_at_ms',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'fee_bps') = 0,
  'ALTER TABLE game_dice_matches ADD COLUMN fee_bps INT NOT NULL DEFAULT 500 COMMENT ''本局平台抽水万分比快照'' AFTER canceled_at_ms',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'pool_bps') = 0,
  'ALTER TABLE game_dice_matches ADD COLUMN pool_bps INT NOT NULL DEFAULT 100 COMMENT ''本局奖池入池万分比快照'' AFTER fee_bps',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'match_mode') = 0,
  'ALTER TABLE game_dice_matches ADD COLUMN match_mode VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''匹配模式 human/robot'' AFTER pool_bps',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'forced_result') = 0,
  'ALTER TABLE game_dice_matches ADD COLUMN forced_result VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''机器人局强制结果，新手保护、消耗策略或奖池不足时使用'' AFTER match_mode',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'pool_delta_coin') = 0,
  'ALTER TABLE game_dice_matches ADD COLUMN pool_delta_coin BIGINT NOT NULL DEFAULT 0 COMMENT ''本局对奖池的净变化'' AFTER forced_result',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- 参与者类型是机器人扩展的事实字段；旧局默认真人，避免历史数据阻塞查询和结算。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_participants' AND COLUMN_NAME = 'participant_type') = 0,
  'ALTER TABLE game_dice_participants ADD COLUMN participant_type VARCHAR(32) NOT NULL DEFAULT ''user'' COMMENT ''参与者类型 user/robot'' AFTER user_id',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- rock 真实猜拳复用自研局表，但胜负来自参与者预选手势；旧库补空值，后续新局强制写入。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_participants' AND COLUMN_NAME = 'rps_gesture') = 0,
  'ALTER TABLE game_dice_participants ADD COLUMN rps_gesture VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''石头剪刀布预选手势'' AFTER dice_points_json',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- 全服等待队列按 app/game/stake/status 命中；旧库没有这个索引时，高并发匹配会退化成扫描历史局。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND INDEX_NAME = 'idx_game_dice_waiting') = 0,
  'ALTER TABLE game_dice_matches ADD INDEX idx_game_dice_waiting (app_code, game_id, stake_coin, status, created_at_ms, match_id)',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_participants' AND INDEX_NAME = 'idx_game_dice_participant_status') = 0,
  'ALTER TABLE game_dice_participants ADD INDEX idx_game_dice_participant_status (app_code, match_id, status, seat_no)',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

CREATE TABLE IF NOT EXISTS game_self_game_configs (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '配置状态',
  stake_options_json JSON NULL COMMENT '可下注金币档位',
  fee_bps INT NOT NULL DEFAULT 500 COMMENT '平台抽水万分比',
  pool_bps INT NOT NULL DEFAULT 100 COMMENT '奖池入池万分比',
  min_players INT NOT NULL DEFAULT 2 COMMENT '最少开局人数',
  max_players INT NOT NULL DEFAULT 2 COMMENT '最多参与人数',
  robot_enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用机器人补位',
  robot_match_wait_ms BIGINT NOT NULL DEFAULT 3000 COMMENT '等待真人后机器人补位毫秒数',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, game_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏配置表';

CREATE TABLE IF NOT EXISTS game_self_game_pools (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  balance_coin BIGINT NOT NULL DEFAULT 0 COMMENT '机器人共享奖池余额',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, game_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏机器人奖池表';

CREATE TABLE IF NOT EXISTS game_self_game_pool_transactions (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  adjustment_id VARCHAR(96) NOT NULL COMMENT '奖池流水 ID',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  match_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '关联骰子局 ID',
  user_id BIGINT NOT NULL DEFAULT 0 COMMENT '关联真实用户 ID',
  direction VARCHAR(16) NOT NULL COMMENT '方向 in/out',
  amount_coin BIGINT NOT NULL COMMENT '变化金币',
  reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '变化原因',
  balance_after BIGINT NOT NULL COMMENT '变化后奖池余额',
  created_by BIGINT NOT NULL DEFAULT 0 COMMENT '后台操作者或系统用户',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY(app_code, adjustment_id),
  KEY idx_game_pool_tx_game_time(app_code, game_id, created_at_ms),
  KEY idx_game_pool_tx_match(app_code, match_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏奖池流水表';

CREATE TABLE IF NOT EXISTS game_self_game_stake_pools (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  stake_coin BIGINT NOT NULL COMMENT '下注档位金币',
  balance_coin BIGINT NOT NULL DEFAULT 0 COMMENT '档位奖池余额',
  reserved_coin BIGINT NOT NULL DEFAULT 0 COMMENT '预留冻结金额',
  target_balance_coin BIGINT NOT NULL DEFAULT 0 COMMENT '目标水位',
  safe_balance_coin BIGINT NOT NULL DEFAULT 0 COMMENT '绿色水位阈值',
  warning_balance_coin BIGINT NOT NULL DEFAULT 0 COMMENT '黄色水位阈值',
  danger_balance_coin BIGINT NOT NULL DEFAULT 0 COMMENT '红色水位阈值',
  status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '档位奖池状态',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, game_id, stake_coin)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏档位奖池表';

CREATE TABLE IF NOT EXISTS game_self_game_stake_pool_transactions (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  adjustment_id VARCHAR(96) NOT NULL COMMENT '档位奖池流水 ID',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  stake_coin BIGINT NOT NULL COMMENT '下注档位金币',
  match_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '关联自研游戏局 ID',
  user_id BIGINT NOT NULL DEFAULT 0 COMMENT '关联真实用户 ID',
  direction VARCHAR(16) NOT NULL COMMENT '方向 in/out',
  amount_coin BIGINT NOT NULL COMMENT '余额变化金币',
  reserved_delta_coin BIGINT NOT NULL DEFAULT 0 COMMENT '冻结变化金币',
  reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '变化原因',
  balance_after BIGINT NOT NULL COMMENT '变化后余额',
  reserved_after BIGINT NOT NULL DEFAULT 0 COMMENT '变化后冻结金额',
  created_by BIGINT NOT NULL DEFAULT 0 COMMENT '后台操作者或系统用户',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY(app_code, adjustment_id),
  KEY idx_stake_pool_tx_pool_time(app_code, game_id, stake_coin, created_at_ms),
  KEY idx_stake_pool_tx_match(app_code, match_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏档位奖池流水表';

CREATE TABLE IF NOT EXISTS game_self_game_new_user_policies (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用新用户保护',
  protection_rounds INT NOT NULL DEFAULT 30 COMMENT '保护有效机器人局数',
  protection_hours INT NOT NULL DEFAULT 168 COMMENT '保护时长小时',
  max_stake_coin BIGINT NOT NULL DEFAULT 5000 COMMENT '最大保护下注档位',
  lifetime_subsidy_quota_coin BIGINT NOT NULL DEFAULT 30000 COMMENT '生命周期补贴额度',
  day1_subsidy_quota_coin BIGINT NOT NULL DEFAULT 15000 COMMENT '首日补贴额度',
  single_round_subsidy_cap_coin BIGINT NOT NULL DEFAULT 5000 COMMENT '单局保护上限',
  strategy_level VARCHAR(32) NOT NULL DEFAULT 'soft_boost' COMMENT '保护策略等级',
  lose_streak_protection_enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用新手连输保护',
  lose_streak_trigger INT NOT NULL DEFAULT 2 COMMENT '触发新手连输保护的连续输局数',
  normal_phase_target_win_rate_percent INT NOT NULL DEFAULT 53 COMMENT '成熟期有效机器人局目标胜率百分比，50 表示完全自然随机',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, game_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏新用户保护策略表';

CREATE TABLE IF NOT EXISTS game_self_game_user_protection_states (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  user_id BIGINT NOT NULL COMMENT '真实用户 ID',
  used_rounds INT NOT NULL DEFAULT 0 COMMENT '已消耗有效机器人局数',
  lifetime_subsidy_coin BIGINT NOT NULL DEFAULT 0 COMMENT '已消耗生命周期补贴',
  day1_subsidy_coin BIGINT NOT NULL DEFAULT 0 COMMENT '已消耗首日补贴',
  status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '保护状态',
  first_applied_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '首次保护时间',
  last_applied_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最近保护时间',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, game_id, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏用户新手保护状态表';

CREATE TABLE IF NOT EXISTS game_self_game_user_protection_events (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  match_id VARCHAR(96) NOT NULL COMMENT '自研游戏局 ID',
  user_id BIGINT NOT NULL COMMENT '真实用户 ID',
  used_round_delta INT NOT NULL DEFAULT 0 COMMENT '保护局数变化',
  lifetime_subsidy_coin_delta BIGINT NOT NULL DEFAULT 0 COMMENT '生命周期补贴变化',
  day1_subsidy_coin_delta BIGINT NOT NULL DEFAULT 0 COMMENT '首日补贴变化',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY(app_code, game_id, match_id, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏新手保护消耗事件表';

CREATE TABLE IF NOT EXISTS game_self_game_risk_rules (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用风控分层',
  protection_profit_limit_coin BIGINT NOT NULL DEFAULT 0 COMMENT '保护期收益上限',
  full_protection_max_score INT NOT NULL DEFAULT 29 COMMENT '完整保护最高风险分',
  reduced_protection_max_score INT NOT NULL DEFAULT 49 COMMENT '降级保护最高风险分',
  weak_protection_max_score INT NOT NULL DEFAULT 69 COMMENT '弱保护最高风险分',
  disable_protection_min_score INT NOT NULL DEFAULT 70 COMMENT '关闭保护最低风险分',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, game_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏机器人策略风控规则表';

CREATE TABLE IF NOT EXISTS game_self_game_strategy_logs (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  log_id VARCHAR(96) NOT NULL COMMENT '策略日志 ID',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  stake_coin BIGINT NOT NULL COMMENT '下注档位金币',
  match_id VARCHAR(96) NOT NULL COMMENT '自研游戏局 ID',
  user_id BIGINT NOT NULL COMMENT '真实用户 ID',
  strategy_code VARCHAR(64) NOT NULL COMMENT '策略编码',
  decision VARCHAR(64) NOT NULL COMMENT '策略决策',
  force_result VARCHAR(32) NOT NULL DEFAULT '' COMMENT '写入局的强制结果',
  reason_code VARCHAR(64) NOT NULL COMMENT '原因码',
  reason_json JSON NULL COMMENT '决策快照',
  pool_balance_coin BIGINT NOT NULL DEFAULT 0 COMMENT '决策时档位奖池余额',
  pool_level VARCHAR(32) NOT NULL DEFAULT '' COMMENT '决策时水位',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY(app_code, log_id),
  UNIQUE KEY uk_strategy_match_user(app_code, match_id, user_id),
  KEY idx_strategy_user_time(app_code, user_id, created_at_ms),
  KEY idx_strategy_game_time(app_code, game_id, stake_coin, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏机器人策略日志表';

CREATE TABLE IF NOT EXISTS game_self_game_robots (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID',
  user_id BIGINT NOT NULL COMMENT '真实 App 机器人用户 ID',
  status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '机器人状态',
  created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  last_used_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最近一次被匹配补位时间，UTC epoch ms',
  used_count BIGINT NOT NULL DEFAULT 0 COMMENT '累计被匹配补位次数',
  PRIMARY KEY(app_code, game_id, user_id),
  KEY idx_game_robot_status(app_code, game_id, status, updated_at_ms, user_id),
  KEY idx_game_robot_random_pool(app_code, status, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏机器人登记表';

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_self_game_new_user_policies' AND COLUMN_NAME = 'lose_streak_protection_enabled') = 0,
  'ALTER TABLE game_self_game_new_user_policies ADD COLUMN lose_streak_protection_enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''是否启用新手连输保护'' AFTER strategy_level',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_self_game_new_user_policies' AND COLUMN_NAME = 'lose_streak_trigger') = 0,
  'ALTER TABLE game_self_game_new_user_policies ADD COLUMN lose_streak_trigger INT NOT NULL DEFAULT 2 COMMENT ''触发新手连输保护的连续输局数'' AFTER lose_streak_protection_enabled',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_self_game_new_user_policies' AND COLUMN_NAME = 'normal_phase_target_win_rate_percent') = 0,
  'ALTER TABLE game_self_game_new_user_policies ADD COLUMN normal_phase_target_win_rate_percent INT NOT NULL DEFAULT 53 COMMENT ''成熟期有效机器人局目标胜率百分比，50 表示完全自然随机'' AFTER lose_streak_trigger',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- 机器人使用时间和累计次数只用于后台观察随机命中分布；旧表补默认 0，避免存量机器人资料读取失败。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_self_game_robots' AND COLUMN_NAME = 'last_used_at_ms') = 0,
  'ALTER TABLE game_self_game_robots ADD COLUMN last_used_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''最近一次被匹配补位时间，UTC epoch ms'' AFTER updated_at_ms',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_self_game_robots' AND COLUMN_NAME = 'used_count') = 0,
  'ALTER TABLE game_self_game_robots ADD COLUMN used_count BIGINT NOT NULL DEFAULT 0 COMMENT ''累计被匹配补位次数'' AFTER last_used_at_ms',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_self_game_robots' AND INDEX_NAME = 'idx_game_robot_rotation') > 0,
  'ALTER TABLE game_self_game_robots DROP INDEX idx_game_robot_rotation',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_self_game_robots' AND INDEX_NAME = 'idx_game_robot_random_pool') = 0,
  'ALTER TABLE game_self_game_robots ADD INDEX idx_game_robot_random_pool (app_code, status, user_id)',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

CREATE TABLE IF NOT EXISTS game_explore_winners (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  win_id VARCHAR(96) NOT NULL COMMENT 'Explore Square 播报 ID',
  game_code VARCHAR(64) NOT NULL COMMENT 'App H5 入口 key，例如 game_dice/game_rock',
  game_id VARCHAR(96) NOT NULL COMMENT '来源游戏 ID',
  user_id BIGINT NOT NULL COMMENT '获胜用户 ID',
  display_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '展示昵称快照',
  avatar_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '展示头像快照',
  coin_amount BIGINT NOT NULL COMMENT '获胜金币数',
  won_at_ms BIGINT NOT NULL COMMENT '获胜时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, win_id),
  KEY idx_game_explore_winners_time(app_code, won_at_ms, win_id),
  KEY idx_game_explore_winners_game(app_code, game_code, won_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Explore Square 游戏获胜播报表';

CREATE TABLE IF NOT EXISTS game_callback_logs (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  callback_id VARCHAR(96) NOT NULL COMMENT '回调 ID',
  platform_code VARCHAR(64) NOT NULL COMMENT '平台编码',
  operation VARCHAR(64) NOT NULL COMMENT '操作',
  request_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '链路请求 ID，用于排查问题',
  provider_request_id VARCHAR(160) NOT NULL DEFAULT '' COMMENT '提供方请求 ID',
  signature_valid TINYINT(1) NOT NULL DEFAULT 0 COMMENT '签名有效',
  request_hash VARCHAR(128) NOT NULL COMMENT '请求哈希值',
  response_code VARCHAR(64) NOT NULL DEFAULT '' COMMENT '响应编码',
  response_body_hash VARCHAR(128) NOT NULL DEFAULT '' COMMENT '响应正文哈希值',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY(app_code, callback_id),
  KEY idx_game_callback_platform_time(app_code, platform_code, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏回调日志表';

CREATE TABLE IF NOT EXISTS game_repair_orders (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  repair_id VARCHAR(96) NOT NULL COMMENT '修复 ID',
  platform_code VARCHAR(64) NOT NULL COMMENT '平台编码',
  provider_order_id VARCHAR(160) NOT NULL COMMENT '提供方订单 ID',
  reason VARCHAR(128) NOT NULL COMMENT '原因',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  operator_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '操作管理员 ID',
  attempts INT NOT NULL DEFAULT 0 COMMENT '尝试次数',
  next_run_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一个运行时间，UTC epoch ms',
  last_error_code VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最后错误编码',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, repair_id),
  UNIQUE KEY uk_game_repair_provider_order(app_code, platform_code, provider_order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏修复订单表';

CREATE TABLE IF NOT EXISTS game_level_event_outbox (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  event_id VARCHAR(160) NOT NULL COMMENT '事件幂等 ID',
  order_id VARCHAR(96) NOT NULL COMMENT '订单 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  game_id VARCHAR(96) NOT NULL COMMENT '游戏 ID',
  provider_order_id VARCHAR(160) NOT NULL COMMENT '提供方订单 ID',
  provider_round_id VARCHAR(160) NOT NULL DEFAULT '' COMMENT '提供方轮次 ID',
  coin_amount BIGINT NOT NULL COMMENT '金币数量',
  wallet_transaction_id VARCHAR(96) NOT NULL COMMENT '钱包交易 ID',
  payload_json JSON NULL COMMENT '业务负载 JSON 快照',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  attempt_count INT NOT NULL DEFAULT 0 COMMENT '尝试次数',
  next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一次重试时间，UTC epoch ms',
  locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前锁定 worker',
  locked_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '锁定截止时间，UTC epoch ms',
  failure_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '失败原因',
  occurred_at_ms BIGINT NOT NULL COMMENT '业务发生时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, event_id),
  UNIQUE KEY uk_game_level_event_order(app_code, order_id),
  KEY idx_game_level_event_pending(app_code, status, next_retry_at_ms, created_at_ms),
  KEY idx_game_level_event_claim(app_code, status, locked_until_ms, next_retry_at_ms, created_at_ms, event_id),
  KEY idx_game_level_event_claim_order(app_code, status, locked_until_ms, created_at_ms, event_id, next_retry_at_ms),
  KEY idx_game_level_event_retention(app_code, status, updated_at_ms, event_id),
  KEY idx_game_level_event_user(app_code, user_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏等级事件 outbox 表';

CREATE TABLE IF NOT EXISTS game_outbox (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  event_id VARCHAR(160) NOT NULL COMMENT '事件幂等 ID',
  event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
  order_id VARCHAR(96) NOT NULL COMMENT '订单 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  platform_code VARCHAR(64) NOT NULL COMMENT '平台编码',
  game_id VARCHAR(96) NOT NULL COMMENT '游戏 ID',
  op_type VARCHAR(32) NOT NULL COMMENT '游戏改账方向',
  coin_amount BIGINT NOT NULL COMMENT '金币数量',
  payload_json JSON NOT NULL COMMENT '业务负载 JSON 快照',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  worker_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'MQ 投递 worker ID',
  lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'MQ 投递锁过期时间，UTC epoch ms',
  retry_count INT NOT NULL DEFAULT 0 COMMENT '重试次数',
  next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一次 MQ 投递重试时间，UTC epoch ms',
  last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次失败原因',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY(app_code, event_id),
  KEY idx_game_outbox_status_created(app_code, status, next_retry_at_ms, created_at_ms, event_id),
  KEY idx_game_outbox_lock(app_code, status, lock_until_ms, created_at_ms, event_id),
  KEY idx_game_outbox_retention(app_code, status, updated_at_ms, event_id),
  KEY idx_game_outbox_order(app_code, order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏统计事件 outbox 表';
