# ChineseCA 空间 · 建造接口

给玩家的 AI 工具看的说明。玩家在 https://world.chineseca.com/build/ 生成 **建造令牌**，交给 AI，AI 就能替玩家建作品、上传模型、读体检报告、申请展位。

- 目前只开放给 ChineseCA 加州华人论坛前 100 个注册账号，不收费。
- 令牌只能调用 `/api/build/v1/*`，不能投票，不能碰论坛账号或其他接口。
- 重新生成令牌，旧令牌立即失效。

## 鉴权

每个请求带上请求头：

```
Authorization: Bearer ccb_…（玩家给你的令牌）
```

错误统一返回 JSON：`{"error": "中文说明", "code": "…"}`。`401 BAD_TOKEN` 表示令牌无效，请让玩家重新生成。

## 规则

机器可读的版本：`GET /api/build/v1/spec`（不需要令牌）。

| 项目 | 规则 |
| --- | --- |
| 文件 | 自包含的 `.glb`（glTF 2.0 二进制）。标准版最大 20 MB，低清版 5 MB，高清版 80 MB |
| 清晰度 | 标准版（`std`）必传；低清版（`low`，面数 ≤ 标准的 30%）给手机；高清版（`high`，面数 ≤ 标准的 10 倍）给好电脑；封面图（PNG/JPEG/WebP，≤ 2 MB，边长 ≤ 4096）给最弱的设备。每一档都要通过同样的尺寸检查 |
| 坐标 | 单位米，Y 向上；原点在建造空间地面正中；**+Z 是作品正面**（朝街） |
| 建筑 | x、z 都在 −8 … 8 之间（16 × 16 米），y 在 0 … 15 之间（限高 15 米），标准版 ≤ 50,000 个三角面 |
| 机器人 | 一个 `.zip`：根目录 `robot.xml`（MuJoCo MJCF）+ 网格（.stl/.obj/.msh）+ 贴图（.png/.jpg）。≤ 50 MB，解压后 ≤ 150 MB。MuJoCo 坐标：Z 向上，原点在地面正中；初始姿态整个落在 x、y ∈ [−8, 8]、z ∈ [0, 15]。≤ 200 个刚体、150 个关节、100 个执行器。不能用插件（`<extension>`/`<plugin>`），文件路径只能指向包内 |
| 小件物品 S | 最长的一边 ≤ 0.5 米，≤ 5,000 个三角面 |
| 中件物品 M | 最长的一边 ≤ 2 米，≤ 15,000 个三角面 |
| 大件物品 L | 最长的一边 ≤ 4 米，≤ 30,000 个三角面 |
| 禁止 | 引用外部网址的贴图或缓冲区；`extensionsRequired`（Draco、Meshopt、KTX2 等压缩）；超过 16 张贴图；超过 2,000 个节点 |
| 容差 | 1 厘米 |

“不穿墙”的意思：模型每个顶点（按节点变换后的世界坐标）都必须在建造空间里，不能伸出地块、不能埋到地下。服务器按每个网格的包围盒检查。

模型只能是数据（几何、材质、贴图），不能带脚本。

## 流程

### 1. 看看自己是谁

```
GET /api/build/v1/me
```

返回 `username`、`eligible`、`token`（前缀、生成时间）、`works`（作品列表）。

### 2. 建一个作品

```
POST /api/build/v1/works
Content-Type: application/json

{"kind": "building", "title": "竹影茶亭", "intro": "一两句话介绍"}
```

- `kind`：`building`（建筑）或 `item`（物品）
- `tier`：物品必填，`S` / `M` / `L`
- `title` ≤ 40 字，`intro` ≤ 300 字
- 每人最多 20 个作品

返回作品对象，记下 `id`。改名字或介绍：`PATCH /api/build/v1/works/{id}`，字段同上（不能改类型）。

### 3. 上传模型，读体检报告

```
PUT /api/build/v1/works/{id}/model
Content-Type: model/gltf-binary

<.glb 文件的原始字节>
```

例如：

```
curl -X PUT https://world.chineseca.com/api/build/v1/works/12/model \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: model/gltf-binary" \
  --data-binary @tea-house.glb
```

返回：

```json
{
  "version": 3,
  "ok": false,
  "model": null,
  "report": {
    "ok": false,
    "triangles": 18234,
    "size": [17.2, 9.0, 12.0],
    "bounds": {"min": [-8.6, 0, -6], "max": [8.6, 9, 6]},
    "issues": [{"level": "error", "code": "OUT_X", "msg": "左右超出地块 0.60 米……"}]
  }
}
```

- 每次上传都是一个新版本。`ok: true` 才会保存文件，`model` 是它的地址。
- `level: "error"` 必须改；`level: "warn"` 是提醒（比如三角面接近上限）。
- 常见代码：`OUT_X` / `OUT_Z`（伸出地块）、`BELOW_GROUND`（低于地面）、`TOO_TALL`（超高）、`TOO_LARGE`（物品超出档位）、`TOO_HEAVY`（三角面太多）、`EXTERNAL`（引用外部网址）、`EXTENSION`（用了压缩扩展）、`BAD_FILE`（不是合法的 .glb）。
- 每人 24 小时内最多上传 30 次。

**建议**：有 error 就按 `msg` 改模型再传，直到 `ok: true`。

### 3a. 动作（让作品自己动）

`.glb` 里可以带动作：骨骼动画（skin）、节点动画（平移 / 旋转 / 缩放）、变形动画（morph targets）。服务器逐帧采样**每一个动作**，检查动起来的整个过程：

- 不能伸出空间、不能钻到地下、不能超高；物品动起来最长的一边也不能超过档位。
- 判断偏保守：骨骼蒙皮按“每根骨头把整块网格搬过去”的范围算，只会多算不会少算。
- 有问题的动作会单独报错，例如 `ANIM_OUT_X`：动作「walk」做到一半时左右方向超出建造空间 1.5 米。
- 上限：每个模型 ≤ 32 个动作，每副骨骼 ≤ 256 根骨头，每个动作 ≤ 120 秒；不支持稀疏访问器（sparse）。
- 报告里的 `animations`：每个动作的名字、时长、动起来的尺寸、是否通过、对应的规范名。

**动作名规范**：用下面的英文名，平台和开发者的“大脑”就能用统一的名字叫作品做动作（大小写、`Armature|` 这类前缀、`_01` 这类编号都会被忽略）。

`idle` 待机（展出时默认循环）、`walk` 走、`run` 跑、`wave` 挥手、`greet` 打招呼、`bow` 鞠躬、`nod` 点头、`shake` 摇头、`talk` 说话、`point` 指向、`think` 思考、`happy` 开心、`sad` 难过、`dance` 跳舞、`sit` 坐下、`stand` 站起、`look` 张望、`alert` 提醒（不得用警灯样式）

工作台预览可以逐个播放动作、调速度，有问题的动作播放时线框变红。没通过检查但读得出来的模型会存成**草稿**，只有作者自己能预览，方便找问题；草稿不能申请展出。

> 动作类型：`.glb` 动作是“录好的动画”，适合表演、手势、表情；要按物理真实地走、站稳、被推不倒，用机器人包（MuJoCo）在实验场里测试。

### 3a2. 只有一个高清模型

在工作台点“上传一个模型，自动生成各档”：在你自己的浏览器里减面（网格聚类，保留贴图坐标和骨骼）、缩贴图（标准 2048、低清 512），生成标准版和低清版，原模型作为高清版，最后截一张封面。用 AI 调接口的，也可以自己生成各档再分别上传。

画面：所有预览都用“虚拟摄影棚”环境反射 + 电影色调（ACES），金属、白色塑料、亮面罩的质感接近产品渲染图；访客设备卡顿时自动降低分辨率。

### 3b. 其他清晰度和封面图

```
PUT /api/build/v1/works/{id}/model?quality=low     低清版 .glb
PUT /api/build/v1/works/{id}/model?quality=high    高清版 .glb
PUT /api/build/v1/works/{id}/poster                封面图（PNG/JPEG/WebP 原始字节）
```

访客的浏览器按设备自动挑一档（没有 WebGL 或省流量 → 封面图；手机、旧电脑 → 低清；普通电脑 → 标准；好电脑 → 高清），访客也可以手动切换。缺哪一档就用最接近的。

### 3c. 机器人

```
POST /api/build/v1/works            {"kind": "robot", "title": "四足小狗"}
PUT  /api/build/v1/works/{id}/package     <.zip 原始字节>
```

服务器会：安全解包 → 扫描 MJCF（不允许插件、DTD、包外路径）→ 套上标准考场（16 × 16 米地面 + 四面 2 米高的墙）→ 用 MuJoCo 3.15.0 编译 → 量初始姿态的包围盒 → 不加控制试跑 2 秒，看会不会数值发散。报告里有 `counts`（刚体、关节、执行器等）、`size`、`mass`、`settle.realtime`（试跑速度是实时的几倍）。

- 想让机器人在实验场里站得住，用 `<position>` 执行器（带 `kp`）比 `<motor>` 容易：控制值为 0 时它会保持初始姿态。
- 标准考场文件：`/lab/arena.xml`。和 `robot.xml` 放在同一个文件夹，用 MuJoCo 打开，就是线上同一个场地，可以在自己电脑上训练。
- 实验场 `/lab/?work={id}`：浏览器里跑 MuJoCo，有“站立 10 秒”“抗推”两道考题和每个执行器的滑块。成绩目前是浏览器里的自测成绩。
- 机器人作品也可以加外观 `.glb`（`model?quality=std/low/high`，按建筑空间检查）和封面图，用于展览街鸟瞰。
- **外观绑定物理骨架**：外观 `.glb` 按机器人的**初始姿态**摆好（glTF 习惯 Y 向上；朝向和 MuJoCo 一致——glTF 的 +X = MuJoCo 的 +X，机器人面朝 +X），里面和 `robot.xml` 的 `body` **同名**的节点会跟着那个 body 动。没有同名节点时，整个外观跟着躯干走。骨骼（bone）同名也可以。实验场里可以切换“看外观 / 看物理形状”。示例：`/lab/example-humanoid.xml`。
- 展示时（展览街、分享页）机器人外观会自动转成面朝街道。

### 4. 申请展览街展位

```
POST /api/build/v1/works/{id}/apply
Content-Type: application/json

{"slot": "W1-SCA-SGV-EXPO-01-03"}
```

- 用每一档最新通过检查的文件申请；申请时这一套文件被**冻结**，投票和展出都用它，之后再上传不会换掉（要换就撤回重新申请）。
- 展位编号可以在 `GET /api/expo/slots` 查到（`taken` 为空的是空展位）。
- 申请后，模型会公开给所有人看，进入投票。

### 5. 投票（只在网页上，由真人完成）

展览街 https://world.chineseca.com/expo/ 上，用论坛账号登录的人每件作品可以投 1 票，作者不能投自己。**超过 3 票自动上展**。同一个展位有几件作品在争，谁先够票谁上，其他的自动结束，可以换展位重新申请。

作者可以在工作台撤回申请；管理员可以撤下违规作品，所有操作留痕。

## 规则放宽（红线之外都可以申请）

平台的原则：**只要不碰平台安全，尽量给自由。** 上面的尺寸、面数、文件大小、动作数量等上限都是默认值，可以为某件作品（或整个账号）申请放宽。

**红线，不能放宽**：引用外部网址或本机文件；脚本、程序、MuJoCo 插件、SVG 等可执行内容；压缩包路径越界、加密包、压缩炸弹；伸出自己的地块或展位（占用别人的空间）；碰现实世界（摄像头、麦克风、定位、访客真实身份）；冒充警察、政府、平台官方；超过服务器和网络的硬上限（单个文件约 95 MB）。

**怎么申请**：检查报告里能放宽的错误会带 `limit` 字段，告诉你是哪一项、现在的上限、实际需要多少：

```json
{"level": "error", "code": "TOO_TALL", "msg": "高 18 米，超过限高 15 米", "limit": {"key": "space.height", "value": 15, "actual": 18}}
```

```
GET  /api/build/v1/waivers                 可放宽的项目、红线、我的申请
POST /api/build/v1/waivers                 {"key": "space.height", "workId": 12, "value": 18, "reason": "钟楼需要 18 米"}
POST /api/build/v1/waivers/{id}/withdraw   撤回
```

- 申请值不超过默认值一定倍数（多数是 2 倍）的，**自动批准**；更大的由平台管理员审批，要写一句理由。限高、地下深度不自动批（会影响街景）。
- 每一项都有最大值（`ceiling`），超过就是硬上限，批不了。
- 批准后，平台马上用新上限**重新检查这件作品最新的草稿**，合格的直接变成可用版本，不用再传一次（返回里的 `recheck`）。
- 账号级的两项：`quota.uploads`（每天上传次数）、`quota.works`（作品个数），不用填 `workId`。
- 管理员可以收回放宽（会写理由），收回后新上传按默认规则检查。

| key | 内容 | 默认 | 最大 |
| --- | --- | --- | --- |
| `glb.tris` | 标准版三角面（低清、高清按比例跟着放宽） | 建筑 50,000 / 物品按档位 | 500,000 |
| `glb.bytes` | 标准版文件大小（MB，高清跟着放宽） | 20 | 60 |
| `glb.images` / `glb.nodes` | 贴图张数 / 节点个数 | 16 / 2,000 | 64 / 10,000 |
| `anim.clips` / `anim.joints` / `anim.seconds` | 动作个数 / 每副骨骼骨头数 / 单个动作秒数 | 32 / 256 / 120 | 128 / 1,024 / 600 |
| `space.height` / `space.depth` | 建筑限高 / 地下深度（米） | 15 / 0 | 40 / 6 |
| `item.size` | 物品最长边（米） | 档位上限 | 8 |
| `robot.zipBytes` 等 | 机器人包大小、刚体、关节、执行器、形状、网格顶点 | 见上文 | 见 `GET /api/build/v1/spec` |
| `quota.uploads` / `quota.works` | 每天上传次数 / 作品个数（账号级） | 30 / 20 | 300 / 200 |

## 开发者能力申请（让作品看、说、动）

建造接口只管作品本身。想让机器人看见周围、和访客对话、做动作、走动、说话，要为**自己的某件作品**申请能力。

**规则写死、自动执行**：所有规则（下面的数字）都写在平台代码里，每次调用逐条检查，不靠人工。**符合规则的申请马上自动批准**；作品还没有通过检查的模型、或账号最近 30 天被暂停 / 收回过能力的，转平台管理员人工审批。完整规则随时可查：`GET /api/build/v1/policy`（不用令牌）。

**算力自备**：AI 推理、语音合成、控制程序都跑在你自己的服务上，费用自己承担。平台不提供免费 AI 算力，也不保存你的 AI 密钥。

```
GET  /api/build/v1/policy                    能力目录、开发者规则、全部运行规则（公开）
GET  /api/build/v1/capabilities              我的申请
POST /api/build/v1/capabilities              提交申请（符合规则自动批准）
POST /api/build/v1/capabilities/{id}/withdraw  撤回（立即停用，进行中的对话马上关闭）
```

```json
{ "workId": 3, "caps": ["scene.read", "vision", "anim", "chat.text", "zone.events", "move", "voice", "brain"],
  "purpose": "展位导览机器人：访客打开展位时打招呼，回答展品问题，做动作", "endpoint": "https://robot.example.com/brain", "agree": true }
```

| 能力 | 名称 | 规则（写死） |
| --- | --- | --- |
| `anim` | 动画与灯光 | 只能播作品里有的动作；速度 0.25–2；过渡 0–1.5 秒；每分钟 40 次。灯光每秒最多变 3 次；红蓝交替当场拦下并记违规 |
| `chat.text` | 文字对话 | 访客主动打字才开启；回复只给这位访客；一次 120 字、每分钟 20 次；不能带网址、不能冒充官方 / 警察 / 政府（拦下并记违规）；访客 3 分钟不说话自动结束 |
| `scene.read` | 虚拟场景只读 | 自己展位周围 24 米内的作品（编号、标题、位置、尺寸）和正在看展位的访客人数、临时编号；每分钟 60 次 |
| `vision` | 视野与遮挡 | 在 `scene` 结果里给每个物体加 `visible`：视角 120°，按包围盒算遮挡 |
| `zone.events` | 区域进出 | 访客打开 / 离开**自己的展位**时通知；同一位访客 30 秒内只通知一次；45 秒没心跳算离开 |
| `move` | 展位内移动 | 作品整个留在展位内（离边缘 0.5 米）；最快 1.5 米/秒；每分钟 30 次；走不出展位，没有“跟随访客” |
| `voice` | 语音 | mp3（Layer III）/ ogg（Opus、Vorbis）/ wav，≤ 15 秒、≤ 512 KB，每分钟 6 段；由平台转发播放，访客默认静音，音量上限 0.8，6 米外按距离变小，40 米外听不到 |
| `brain` | 自带大脑 | 先握手验证；平台推送事件签名，3 秒超时，响应 ≤ 16 KB，每分钟最多 60 次，每次最多 8 个动作；连续失败 20 次暂停推送 10 分钟 |

**违规自动处理**：被拦下的内容违规（网址、冒充、警灯）记一次，只记规则代码不记内容。24 小时内 5 次 → 作品所有能力自动暂停 24 小时；24 小时内 3 位不同访客举报 → 自动暂停，等管理员复核。暂停后所有运行接口返回 `423`，原因和恢复时间都在返回里。

### 运行接口（让作品动起来）

用建造令牌（`Authorization: Bearer ccb_…`）调用，只能控制自己的作品。令牌只放在你自己的程序里：**访客浏览器永远只跟本站说话**，拿到的是平台算好的状态，连不到你的服务器，你也拿不到访客的 IP。

```
GET  /api/build/v1/works/{id}/runtime              状态：作品的动作名、展位、已开的能力、24 小时违规、当前状态、在看的人数、进行中的对话
GET  /api/build/v1/works/{id}/runtime/events?after=  事件（只在内存里，保留 10 分钟、最多 200 条）
GET  /api/build/v1/works/{id}/runtime/scene        场景（scene.read；有 vision 时带 visible）
POST /api/build/v1/works/{id}/runtime/anim         {"name":"wave","loop":false,"speed":1,"fade":0.3} 或 {"stop":true}
POST /api/build/v1/works/{id}/runtime/light        {"color":"#ffaa00"}，null 关灯
POST /api/build/v1/works/{id}/runtime/say          {"text":"…"} 头顶气泡（大家都看得到）；{"text":"…","session":"s_…"} 只回复这位访客
POST /api/build/v1/works/{id}/runtime/move         {"x":2,"z":3,"speed":1,"heading":0}
PUT  /api/build/v1/works/{id}/runtime/voice[?session=s_…]   请求体是音频文件本身
POST /api/build/v1/works/{id}/runtime/brain        {"url":"https://…"} 登记大脑接口，返回签名密钥（只显示一次）
POST /api/build/v1/works/{id}/runtime/brain/verify 握手
```

**坐标**：展位坐标，单位米，Y 向上，原点在自己展位地面正中，+Z 朝街；`heading` 是弧度，0 = 面朝 +Z。北排、南排展位你都不用管，平台自己转。

**动作**：`loop` 不填时 `idle`、`walk` 循环，其他播一次；单次动作播完发 `anim.done`，前端自动回到 `idle`。`{"stop":true}` 也回到 `idle`。移动时如果作品有 `walk` 动作、你又没指定别的动作，前端按实际速度同步步伐（`walk` 按 1 米/秒设计，速度 1.5 就播 1.5 倍）。走到后发 `move.done`。

**事件**（`type`）：

| type | 什么时候 | 内容 |
| --- | --- | --- |
| `zone.enter` / `zone.leave` | 访客打开 / 离开你的展位 | `from`：如“访客#3fa2” |
| `chat` | 访客对作品说话 | `session`、`from`、`text` |
| `chat.close` | 访客结束、屏蔽，或 3 分钟没说话 | `session`、`from`；之后再回复返回 `409` |
| `anim.done` / `anim.stopped` | 单次动作播完 / 被停止 | `name` |
| `move.done` | 走到了 | `x`、`z` |
| `strike` | 一次违规被拦下 | `code`（URL、IMPERSONATE、POLICE_LIGHT）、`count` |
| `suspended` / `restored` | 被暂停 / 恢复 | `note`、`until` |
| `brain.error` / `brain.verified` | 推送失败或大脑返回的动作被拦 / 握手成功 | `code`、`note` |

**访客的临时编号**按天、按作品变化：同一位访客明天是另一个编号，在你的两件作品前也是两个编号。平台不保存对话，你的服务端也不应保存。

### 大脑接口（平台推给你）

1. `POST …/runtime/brain`（可带 `url`，不带就用申请时填的地址）→ 拿到签名密钥 `ccs_…`（只显示一次，重新登记会换新的）。
2. `POST …/runtime/brain/verify` → 平台发 `{"type":"verify","work":3,"challenge":"…"}`，你原样返回 `{"challenge":"…"}`。
3. 之后有新事件时，平台发：

```
POST https://你的地址
X-ChineseCA-Timestamp: 1761300000
X-ChineseCA-Signature: hex(HMAC-SHA256(密钥, 时间戳 + "." + 请求体))
X-ChineseCA-Work: 3

{"type":"events","work":3,"policy":"2026-10-24","events":[…],"scene":{…}}
```

请先验签、并拒绝 5 分钟以前的时间戳。3 秒内返回：

```json
{ "actions": [ {"type":"say","session":"s_…","text":"欢迎！"}, {"type":"anim","name":"wave","loop":false}, {"type":"move","x":1,"z":2} ] }
```

`type` 可以是 `anim`、`stop`、`light`、`say`、`move`，和拉取接口**走同一套检查**；被拦下的动作在事件里以 `brain.error` 告诉你。平台只连公网地址：解析到内网 IP、跳转、超时、太大都算失败。

### 提案：平台还没有的能力、规则、上限

```
GET  /api/world/proposals                  所有人都能看（不含自动驳回的）
GET  /api/build/v1/proposals?status=        open / planned / shipped / declined / blocked
POST /api/build/v1/proposals               {"kind":"capability|rule|limit|other","title":"…","detail":"要做什么、为什么、怎样保证访客安全"}
POST /api/build/v1/proposals/{id}/vote     {"on":true} 附议（不能附议自己的）
```

碰到红线（现实摄像头 / 麦克风 / 录音录像，访客身份 / IP / 定位 / 私聊，外部网址 / 执行代码，冒充，追踪访客）的提案自动驳回并说明碰到哪一条；其余公开，管理员标记“已采纳 / 已上线 / 不采纳（附理由）”。采纳的规则写进平台代码后对所有开发者生效。每人每天最多 5 个。

## 其他

- 展位：每条展览街两排共 8 个，每个 16 × 16 米。北排正面朝南，南排会转 180° 让正面朝街——你只管让作品正面朝 +Z。
- 创意区（CX）暂时保留，以后再开放。
