---
name: arrow_path_data_format.aicomponent
description: 路径消除玩法的关卡数据格式定义（TypeScript 类型）和解析器。定义 RawLevelJson（Cocos 导出格式）→ ParsedLevel（Phaser 可用格式）的转换，包含坐标系翻转和路径离散化。
triggers: 需要定义或解析路径消除类玩法的关卡数据结构时触发。
---

# 箭头路径关卡数据格式（Arrow Path Data Format）

## 说明

本 skill 提供路径消除玩法所需的全套关卡数据类型和解析逻辑：

- `RawLevelJson`：关卡原始 JSON 格式（从 Cocos Creator 导出，y 轴向上）
- `ParsedLevel`：解析后供 Phaser 使用的规范化格式（y 轴向下）
- `LevelPath`：解析后的单条路径（含离散格子索引、方向箭头）
- `parseLevelJson()`：负责坐标翻转、路径离散化、颜色提取

## Scaffold

向 Remix 项目贡献以下文件：

| 目标路径 | 来源 | 说明 |
|---------|-----|-----|
| `src/game/levels/LevelTypes.ts` | `ref/LevelTypes.ts` | 全套类型定义（GridPoint / ArrowDirection / LevelPath / RawLevelJson / ParsedLevel / ThemeVariantConfig） |
| `src/game/levels/LevelParser.ts` | `ref/LevelParser.ts` | `parseLevelJson()` 解析函数 |

## Imports

本 skill 无外部依赖（纯数据结构，不依赖 Phaser 或其他 skill）。

## 关键类型

### RawLevelJson（原始关卡格式）

```typescript
type RawLevelJson = {
  name: number | string;       // 关卡序号
  row: number;                 // 棋盘行数
  col: number;                 // 棋盘列数
  arrows: (number | string)[][][]; // 路径点数组（Cocos 格式，y 向上）
  levelTime: number;           // 关卡时限（秒，0 = 无时限）
  background?: { imageKey, dataUrl?, opacity? };
};
```

### ParsedLevel（规范化格式）

```typescript
type ParsedLevel = {
  index: number;               // 关卡序号
  rows: number;
  cols: number;
  paths: LevelPath[];          // 解析后的所有路径
  timeSeconds: number;         // 关卡时限
  background?: { imageKey, dataUrl?, opacity? };
};

type LevelPath = {
  points: GridPoint[];         // 路径拐角点坐标（Phaser 坐标系，y 向下）
  head: GridPoint;             // 箭头末端格子
  direction: ArrowDirection;   // 箭头方向（从末段方向推导）
  indices: number[];           // 覆盖的所有格子的 row-major 索引（y * cols + x）
  color?: string;              // 路径颜色（十六进制字符串）
};
```

## Recipe

| 决策 | 原因 |
|------|------|
| **两格式设计** | `RawLevelJson`（Cocos 导出格式）与 `ParsedLevel`（游戏可用格式）分离，保留原始数据可溯源；`parseLevelJson()` 在边界统一处理坐标翻转和离散化，不让业务层感知格式差异 |
| **Y 轴翻转集中在解析层** | Cocos 坐标系 y 向上，Phaser/Three.js 坐标系 y 向下；在解析器统一翻转，其他所有组件只需使用 Phaser 坐标系，不再各自处理 |
| **路径离散化（indices 数组）** | 预计算路径覆盖的所有格子索引，避免碰撞检测时重复计算；`indices` 是 row-major 线性索引，方便 Set 查找 |
| **纯数据结构，无引擎依赖** | 类型定义和解析器不依赖 Phaser；可在 Node.js 测试环境（vitest）中直接使用 |

## Adapter

- **Role**: `levelDataFormat` — 路径消除玩法关卡数据类型定义与解析器
- **Provides**: `RawLevelJson`、`ParsedLevel`、`LevelPath`、`GridPoint`、`ArrowDirection` 类型，`parseLevelJson()` / `parseLevelFor3D()` 函数
- **Requires**: 无（纯数据结构，无运行时依赖）
- **Consumed by**: `level_state.aicomponent`（LevelManager 加载关卡）、`path_renderer.aicomponent`（绘制 ParsedLevel.paths）、`path_input_handler.aicomponent`（输入判断）、`level_data_validator.aivalidator`、`gameplay_unit_test.aivalidator`
- **Integration point**: `src/game/levels/LevelTypes.ts` + `src/game/levels/LevelParser.ts` —— 所有使用关卡数据的组件从此处 import 类型

## Skill Definition

```yaml
tools:
  - read_file
  - write_file
inputs:
  - source: src/game/levels/LevelTypes.ts
  - source: src/game/levels/LevelParser.ts
outputs:
  - levelTypes: GridPoint / ArrowDirection / LevelPath / RawLevelJson / ParsedLevel / ThemeVariantConfig
  - levelParser: parseLevelJson() function
```

## 注意事项

- Cocos 坐标系 y 轴向上（0 在底部），Phaser 坐标系 y 轴向下（0 在顶部）
- `parseLevelJson()` 在解析时自动完成翻转：`phaserY = rows - 1 - cocosY`
- `indices` 数组包含路径覆盖的所有格子（含中间格），用于碰撞检测

## 3D 坐标系支持（Three.js）

本 skill 支持两种坐标系输出：

| 坐标系 | 适用引擎 | 说明 |
|--------|---------|------|
| `phaser-y-down` | Phaser 3 | 2D 屏幕坐标，y 轴向下，单位为像素 |
| `threejs-xz` | Three.js | 3D XZ 平面坐标，X 向右 / Z 向下（俯视），Y=0 |

### 坐标映射关系

由于 Phaser 的 `(x, y_down)` 和 Three.js 俯视的 `(x, z_down)` 语义一致：
- Phaser `GridPoint.x` → Three.js `position.x`
- Phaser `GridPoint.y` → Three.js `position.z`
- Three.js `position.y` 固定为 0（地面高度）

### 方向角度映射

| Direction | 2D (rotation, 图片朝上) | 3D (rotation.y, 模型朝+Z) |
|-----------|----------------------|--------------------------|
| Up | 0 | π |
| Right | π/2 | π/2 |
| Down | π | 0 |
| Left | -π/2 | -π/2 |

### 使用示例（Three.js）

```typescript
import { parseLevelFor3D } from "./LevelParser";
import { directionToAngleY, directionToDelta3D } from "./LevelTypes";
import type { GridPoint, LevelPath } from "./LevelTypes";

const level = parseLevelFor3D(rawJson);
const cellSize = 1.0; // Three.js 世界单位

// 将 GridPoint 转为世界坐标
function cellToWorld(pt: GridPoint): { x: number; y: number; z: number } {
  return { x: pt.x * cellSize, y: 0, z: pt.y * cellSize };
}

// 设置模型方向
mesh.rotation.y = directionToAngleY(path.direction);

// 计算滑出位移
const delta = directionToDelta3D(path.direction);
const slideDistance = stepsToExit * cellSize;
mesh.position.x += delta.dx * slideDistance;
mesh.position.z += delta.dz * slideDistance;
```
