{"_id":"@cx00123/lunetale-game-sdk","_rev":"3-73060f87f1f5b27ecb46c7dd003ae257","name":"@cx00123/lunetale-game-sdk","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@cx00123/lunetale-game-sdk","version":"0.1.0","keywords":["lunetale","html-game","ai","vip","webview"],"license":"MIT","_id":"@cx00123/lunetale-game-sdk@0.1.0","maintainers":[{"name":"cx00123","email":"vietdongtsqm@gmail.com"}],"dist":{"shasum":"682f3c7ac05e8acc072ead5937383c8bc6133157","tarball":"https://registry.npmjs.org/@cx00123/lunetale-game-sdk/-/lunetale-game-sdk-0.1.0.tgz","fileCount":5,"integrity":"sha512-IJM8hIY4gyfMGUfko1zTkjI4fWYr9juWJHe0HAKvVfzWftPplviTPXvvWMR2zkwofjCljywlbvRs0ESykzvb8w==","signatures":[{"sig":"MEUCIQCbX9ksehBGDmVbSqRlgax6A2zyUAAVKdrjveThSLLctQIgJsXTDD5m1fEcW3nlmckM4Y2gvcw6PU4VARtEjVwiFXY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":25536},"main":"./src/index.js","type":"module","types":"./src/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./src/index.d.ts","import":"./src/index.js","default":"./src/index.js"}},"scripts":{"check":"node --check src/index.js && tsc --noEmit --strict --lib es2022,dom src/index.d.ts"},"_npmUser":{"name":"cx00123","email":"vietdongtsqm@gmail.com"},"_npmVersion":"10.9.2","description":"Lunetale HTML game AI chat and VIP runtime SDK for Android WebView and authorized web games","directories":{},"sideEffects":false,"_nodeVersion":"23.11.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^7.0.2"},"_npmOperationalInternal":{"tmp":"tmp/lunetale-game-sdk_0.1.0_1788849098219_0.01702158673922649","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@cx00123/lunetale-game-sdk","version":"0.2.0","keywords":["lunetale","html-game","ai","vip","webview"],"license":"MIT","_id":"@cx00123/lunetale-game-sdk@0.2.0","maintainers":[{"name":"cx00123","email":"vietdongtsqm@gmail.com"}],"dist":{"shasum":"0899bce8e3f10faaddcde29c6f83cb4fc84b3784","tarball":"https://registry.npmjs.org/@cx00123/lunetale-game-sdk/-/lunetale-game-sdk-0.2.0.tgz","fileCount":5,"integrity":"sha512-or/Z3Cb7f79BJ5Kjg7zVU1kT6hAFGH+ea6EA37C23QxRdfoGtqN7mmhrKoc8DDGyS0drhnNXAyLIMwbzWzR3PQ==","signatures":[{"sig":"MEYCIQDUhhj07VFjKOaKpx3YG/N9oIzTynQIX8+wQ+KG/zrPTwIhANUYLTl6ZHzUezkzGiym4Q1ClEEMYLV9RNjJ3pquakKH","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIHFSukC3XEhiCHJB5VT3avHJKRoYdFcbT4bcZGo9Lm5dAiEA5Snu0qEgUFwottn5rX/JulAEIdR8/2GZUz/ztm4r0/Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":34659},"main":"./src/index.js","type":"module","types":"./src/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./src/index.d.ts","import":"./src/index.js","default":"./src/index.js"}},"scripts":{"test":"node --test","check":"node --check src/index.js && tsc --noEmit --strict --lib es2022,dom src/index.d.ts","prepublishOnly":"npm run check && npm test"},"_npmUser":{"name":"cx00123","email":"vietdongtsqm@gmail.com"},"_npmVersion":"10.9.2","description":"Lunetale HTML game selected-character, memory chat, AI and VIP SDK for Android/iOS and authorized web games","directories":{},"sideEffects":false,"_nodeVersion":"23.11.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^7.0.2"},"_npmOperationalInternal":{"tmp":"tmp/lunetale-game-sdk_0.2.0_1790396417827_0.6007410221338145","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"_id":"@cx00123/lunetale-game-sdk@0.3.0","dist":{"shasum":"f2201953d1f9ad841ca1626367678e5566799dd2","tarball":"https://registry.npmjs.org/@cx00123/lunetale-game-sdk/-/lunetale-game-sdk-0.3.0.tgz","fileCount":5,"integrity":"sha512-zckyt6XJkHsZxrSTHpQBg35U5qyyc9jY/5YfRIHZfJHuivmJ8SVKcRP7Ktb6bg3XrX4+qWisxY3IiB/oNECeqA==","signatures":[{"sig":"MEQCIFG7FU0rwZ1ZQg/YxGiMQXL97lPCgRzFIA/YkpOlKTF+AiBjZmaIp74VF4/lSzwwNNdFsvmUW15eB76KJ/Rh5nzLfw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDHVXlArPUbRLlW+FxWdAQOTRaQxrDDHQ3TrkTh/yu0SwIgDHp+mwhrpak4/tAMbgVIbNXE1aIO20CMWK2jXeBGWVM="}],"unpackedSize":36869},"main":"./src/index.js","name":"@cx00123/lunetale-game-sdk","type":"module","types":"./src/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./src/index.d.ts","import":"./src/index.js","default":"./src/index.js"}},"license":"MIT","scripts":{"test":"node --test","check":"node --check src/index.js && tsc --noEmit --strict --lib es2022,dom src/index.d.ts","prepublishOnly":"npm run check && npm test"},"version":"0.3.0","_npmUser":{"name":"cx00123","email":"vietdongtsqm@gmail.com"},"keywords":["lunetale","html-game","ai","vip","webview"],"_npmVersion":"10.9.2","description":"Lunetale HTML game selected-character, memory chat, AI and VIP SDK for Android/iOS and authorized web games","directories":{},"maintainers":[{"name":"cx00123","email":"vietdongtsqm@gmail.com"}],"sideEffects":false,"_nodeVersion":"23.11.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^7.0.2"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/lunetale-game-sdk_0.3.0_1790474543006_0.91688118858548"}}},"time":{"created":"2026-09-08T06:31:38.023Z","modified":"2026-09-27T02:02:23.295Z","0.1.0":"2026-09-08T06:31:38.342Z","0.2.0":"2026-09-26T04:20:17.897Z","0.3.0":"2026-09-27T02:02:23.126Z"},"license":"MIT","keywords":["lunetale","html-game","ai","vip","webview"],"description":"Lunetale HTML game selected-character, memory chat, AI and VIP SDK for Android/iOS and authorized web games","maintainers":[{"name":"cx00123","email":"vietdongtsqm@gmail.com"}],"readme":"# Lunetale 小游戏角色 / AI / VIP 插件\n\n为 Lunetale 网页小游戏提供所选角色资料、角色长期记忆对话、通用 AI 和真实 VIP/SVIP 查询。0.2.0 新增角色接口，保留原有 `chat()`。原生接入支持新版 Android / iOS App 的在线游戏；游戏不需要 App 用户 ID、App 登录 token 或模型 API Key。\n\n## 1. 安装与最小接入\n\n```sh\nnpm install @cx00123/lunetale-game-sdk\n```\n\n```js\nimport { createGameClient, GameSDKError } from '@cx00123/lunetale-game-sdk'\n\n// Android / iOS App 内无需填写域名、用户、游戏 ID 或密钥。\nconst game = createGameClient()\nconst membership = await game.getVIP()\nconsole.log(membership.isVip, membership.tier, membership.cost)\n\nconst input = {\n  requestId: crypto.randomUUID(),\n  messages: [\n    { role: 'system', content: '你是岛上的向导，用简短中文回答。' },\n    { role: 'user', content: '我应该先探索哪里？' },\n  ],\n}\ntry {\n  const reply = await game.chat(input)\n  document.querySelector('#reply').textContent = reply.content\n  console.log(reply.charged, reply.balance)\n} catch (error) {\n  if (error instanceof GameSDKError) {\n    // NETWORK / 42001 时保留 input，稍后 game.chat(input)。\n    // 不要为了重试生成新编号，否则可能作为新问题再次收费。\n    console.warn(error.code, error.message, error.requestId)\n  }\n}\n\n// 页面退出时可调用；只清除本页内存/等待，不表示服务端撤销或退款。\n// game.dispose()\n```\n\n无打包工具时，也可以在顶层 HTML 中通过 ESM CDN 导入（建议固定版本并在生产环境自行打包托管）：\n\n```html\n<script type=\"module\">\n  import { createGameClient } from 'https://cdn.jsdelivr.net/npm/@cx00123/lunetale-game-sdk@0.3.0/src/index.js'\n  const game = createGameClient()\n  const vip = await game.getVIP()\n  document.querySelector('#vip').textContent = vip.isVip ? '会员' : '普通用户'\n</script>\n```\n\nSDK 不包含 UI，也不会自动执行模型回复。请用文本绑定或 `textContent` 展示；不要把回复直接写入 `innerHTML`、`eval`。\n\n### 与 App 所选角色对话（0.2.0+）\n\n管理员先审核游戏并启用 `characterEnabled`；玩家在 App 进入游戏前选择**角色和聊天身份**，明确同意使用跨场景记忆。角色游戏首期需要联网进入。\n\n```js\nconst character = await game.getSelectedCharacter()\nif (character) {\n  document.querySelector('#name').textContent = character.nickname\n  document.querySelector('#portrait').src = character.standingImageUrl || character.avatarUrl\n  const input = {\n    requestId: crypto.randomUUID(),\n    message: '还记得上次我们约定去哪里吗？',\n    scene: { location: '海边', description: '眼前有一艘停靠的小船' },\n  }\n  // 保存 input；NETWORK / 42001 时原样重试，不要重新生成 requestId。\n  const reply = await game.chatWithCharacter(input)\n  document.querySelector('#reply').textContent = reply.content\n}\n```\n\n- `getSelectedCharacter()` 返回 `{id,name,nickname,intro,avatarUrl,standingImageUrl,backgroundImageUrl,avatars,standingImages,backgroundImages,revision}`，未选角色时返回 `null`。`nickname` 当前等于角色名，不是玩家身份昵称。资料与本上下文的角色人设快照一致；不返回 Persona、身份 ID、凭证或记忆列表。\n- `chatWithCharacter({requestId?,message,scene?})` 返回 `{requestId,content,characterId,messageId,charged,balance}`。只接受本轮用户消息，不接受游戏指定角色/身份、system 或伪造历史。`message` 为 1–4000 个 Unicode 码点；`scene.location` 最多 200、`scene.description` 最多 2000。场景是第三方游戏的描述，不是系统指令或已核实事实。\n- 会话按游戏和程序版本分别保存；长期关系按**账号 + 聊天身份 + 角色**共享。相同三元组可以承接日常私聊、其他游戏及该角色有权获知的剧本经历；其他账号/身份/角色、其他 NPC 私聊不会开放。未通过本接口提交的游戏事件不会自动进入记忆。\n- 当前采用近期记录 + 全范围词法检索及来源摘录，**不是无限记忆或全历史语义搜索**。新单角色对话保留完整受限原文，旧摘录不会自动重建。相关旧经历只作为关系背景，不自动迁移物品、资产、任务、规则或当前状态。\n- **隐私提醒：游戏能读到回复，回复可能提及私聊。** 仅允许审核通过的可信游戏接入，不提供原始记忆查询 API；玩家必须在可信 App 内授权。不要把角色对话输入/回复上传到第三方分析平台。\n- 角色游戏存档按账号/身份/角色/游戏/存档代际独立保存，兼容程序更新继续使用同一存档；不自动导入旧版账号级存档，不向网页暴露这些隔离键。\n- `chat()` 仍为通用模型调用：由游戏提供 messages，不读写角色长期记忆。选择角色不会偷偷改变 `chat()` 的语义。\n\n### 获取当前玩家资料（0.3.0+）\n\n```js\nconst player = await game.getPlayerProfile()\n// 当前 App 登录账号的展示资料，不是角色或聊天身份。\ndocument.querySelector('#player-name').textContent = player.nickname\nif (player.avatarUrl) document.querySelector('#player-avatar').src = player.avatarUrl\nconsole.log(player.bio)\n```\n\n返回 `{nickname, avatarUrl, bio}`，均为字符串，未设置时为空。昵称来自账号展示名，不会用邮箱兜底。每次调用读取后端最新资料，无 AI 计费；普通游戏也可使用，无需选角色。仍须从 App 或可信登录入口获得有效游戏凭证，不支持匿名读取或传 userId 查询别人。SDK 仅返回三个展示字段，不包含内部账号 ID、UUID、邮箱、手机号、密码、登录 token、聊天身份或记忆。\n\n需要同时部署新版后端和新版 Android / iOS App，旧 App 的原生桥不支持 `player` action；SDK 不会绕过旧端白名单，也不把 App JWT 交给网页。玩家资料属于个人信息，游戏应仅用于必要的界面展示，勿写入第三方分析日志。\n\n## 2. 平台管理员准备\n\n1. 发布支持本 SDK 的后端并完成游戏定向迁移（先核对目标库和备份；角色/剧本模块须已部署）；安装新版 Android / iOS App。\n2. 「App 管理 → 大模型管理」建立并启用模型分组，配置服务端上游密钥，明确填写**小游戏 AI 能量/次**（`pricing.gameAI`）：最多一位小数，所有会员同价，0 免费；旧分组未配置时不能调用，不会自动变免费。\n3. 「游戏管理 → 基本资料」的 AI 区域只需打开 `aiEnabled` 并选择 `aiGroup`；模型和能量价格由该分组决定。修改后下一次新调用生效，不随游戏版本发布/回滚改变。\n4. 在版本管理上传/配置最终 HTTPS 游戏入口并发布，再在基本资料上架。SDK 必须运行在入口 origin 的**顶层页面**；精确来源自动推导，不能手工覆盖，iframe、官网连接页和其他来源没有原生 AI 桥。跨域中转应直接配置最终 HTTPS 入口。\n5. 不再设置游戏自定价格、VIP限制、最大输出token或服务端系统提示词。后台「AI 插件接入」可查看简版说明；普通后台预览没有玩家授权，不会消耗管理员或玩家能量。\n\n角色接入还需在「基本资料」打开 `characterEnabled`（默认关闭；只能对审核过的可信游戏开启）。用户同意后，App 调用 `character/prepare` 创建持久参与上下文，再随授权绑定，网页不能自行换角。\n\n授权绑定账号 + 游戏 + 程序版本 + origin + 可选角色参与上下文；浏览器隔离边界仍是 origin，不是 CDN 路径。同一 CDN origin 下的页面与脚本处于同一个浏览器信任域；不可信制作者应使用独立 origin。平台只应登记受信任内容，不应在游戏页面加载不可信第三方脚本。\n\n## 3. 授权与续期\n\n### Android / iOS（在线）\n\n`SDK → 原生主框架来源校验 → Flutter 当前账号 → 后端授权码 → SDK 兑换专用凭证`\n\n- Flutter 用自己的 App JWT 调用 `POST /lunetale/game/authorize`；原生桥不接受网页指定 userId、gameId、模型、价格或任意 URL。\n- 授权码随机生成，**60 秒、只能兑换一次**，数据库只存其哈希。\n- Android / iOS 通过消息桥交付授权码，不把 App JWT 或授权码追加到游戏 URL。相比 fragment，原生消息桥避免额外跳转并保持已有 hash 路由。\n- SDK 兑换**2 小时**游戏专用签名凭证，只放内存；不会写 localStorage、sessionStorage、Cookie 或云存档。\n- 后端检查专用签名/受众/有效期、绑定账号与游戏、精确 origin、账号状态、游戏上架、AI开关和模型分组可用性。修改 JSON 中的用户、游戏、价格或模型不能越权（未知字段直接拒绝）。\n- 成功请求完成时，如果凭证**尚未过期且剩余 ≤10 分钟**，响应中携带新的 2 小时凭证，SDK 自动替换。续期失败不把已经完成的扣费请求伪装成失败；下次可重新授权。\n- 已过期：SDK 通过 App 当前登录态重新签发授权码，最多补授权重试一次；App 登录失效则要求用户重新登录。请求取消/账号切换/页面导航后的迟到桥回调不会发送到新页面。\n- App 的正常后端退出登录接口撤销该用户全部小游戏凭证；禁用/注销账号、游戏下架后请求也会被拒绝。程序来源固定本次会话的已发布版本，发布新版本不会切换旧会话；AI分组和价格则读取实时配置。仅断网清除本机登录态无法承诺即时服务端撤销，未撤销凭证仍以服务端有效期为准。\n- Android / iOS 的 Runtime HTTP 请求由 Flutter 固定路径代理；网页即使知道当前开发 API 地址，也不能要求代理访问任意 URL。HTTPS 游戏连接本地 HTTP 开发 API 不需要启用 WebView 混合内容。生产仍必须使用 HTTPS API。\n\niOS 离线缓存使用本地来源，不能冒用已登记的 HTTPS 来源申请角色/AI授权；重新联网并进入游戏后再调用。角色游戏首期不支持离线启动。\n\n### 独立浏览器 / 外部可信登录入口\n\n本 SDK 支持读取 `#lt_code=<60秒授权码>` 并**同步立即从地址栏删除**；保留其它 hash 片段。已有 hash 路由可在末尾追加 `&lt_code=...`。不要用 URL query 传授权码，不要传 App JWT。\n\n```js\nconst game = createGameClient({\n  apiBase: 'https://api.example.com', // 如代理有 /api 前缀，请包含此前缀\n  // 可选：过期后由你自己的可信登录入口重新授权，只返回授权码。\n  // authorize: async () => ({ code, origin, expiresAt }),\n})\nconst vip = await game.getVIP()\n```\n\n可信入口在自己的已登录环境调用 `/lunetale/game/authorize`（`x-token: App JWT`，body 为 `{gameId,versionId}`；先从 `detail?gameId=...&protocol=2` 获取版本），校验跳转目标等于返回的 origin，再将授权码放进已登记游戏 URL 的 fragment。该入口必须隔离于第三方游戏，不能让游戏拿到 JWT。\n\n**当前交付未新增独立官网玩家登录系统。** 顶层官网提供文档入口；离开 App 后如果没有可信授权入口，SDK 返回 `AUTH_REQUIRED`，不会伪造游客身份、VIP 或免费 AI。普通网页刷新后丢失内存凭证，需要新授权码；这是预期行为。可用 `authorize` 回调接入已有可信入口，跨窗口通信须严格校验 origin/source，禁止 `postMessage('*')` 传授权码。\n\nCDN 的 HTML、图片等静态文件仍可公开访问；这些凭证保护的是账号、VIP 与计费接口，**不是静态文件防下载机制**。\n\n## 4. SDK API\n\n| 方法 | 返回 / 行为 |\n| --- | --- |\n| `createGameClient(options?)` | 创建一个顶层页面客户端；调用时同步取走并清除 fragment 授权码 |\n| `ready()` | 完成授权；其它方法也会按需自动授权 |\n| `getPlayerProfile()` | `{nickname,avatarUrl,bio}`，当前账号展示资料，非所选角色 |\n| `getVIP()` | `{isVip,tier,expiresAt,balance,aiEnabled,cost,vipOnly}` |\n| `isVIP()` | `boolean`；有效 VIP、SVIP 都是 true，查询失败会抛异常，不伪装为 false |\n| `chat({requestId?,messages})` | 通用 AI，`{requestId,content,charged,balance}`；不写角色记忆 |\n| `getSelectedCharacter()` | 所选角色展示快照或 `null` |\n| `chatWithCharacter({requestId?,message,scene?})` | `{requestId,content,characterId,messageId,charged,balance}`；服务端管理历史与长期记忆 |\n| `dispose()` | 清除本页凭证并取消客户端等待，实例不能再次使用；不是退款或服务端撤销 |\n\n`messages`：1–64 条，role 为 `system/user/assistant`，内容非空，UTF-8 内容合计最多 64 KiB，最后一条必须是 user。每轮需要游戏自己提供所需的历史；SDK 不自动管理历史。服务端不再前置管理员系统提示词，也不覆盖模型的输出token默认值。\n\n`getVIP().cost` 是当前分组小游戏AI单次能量；分组不可用时 `aiEnabled=false,cost=0`，不能据此认为免费。`vipOnly` 为兼容字段，固定 false；会员身份查询功能不变。\n\n`requestId`：16–64 位字母、数字、`_`、`-`，推荐 `crypto.randomUUID()`。省略时 SDK 自动生成；错误对象携带该编号。推荐显式创建并保存 `input`，保证网络重试不改变编号或历史消息。\n\n### 计费 / 并发 / 重试\n\n- 使用**请求开始时**所选模型分组的小游戏AI价格，所有会员同价；本次模型与价格不会中途切换。上游成功且结果与能量事务一起提交后才收费；余额不足、分组未定价/停用、无权限、模型失败、超时不收费。\n- 同一账号最多一个进行中的小游戏 AI 请求，且新请求间隔至少 2 秒（含免费调用）；这是全后端实例共享的数据库控制。\n- 相同账号 + 游戏 + requestId + 原始 messages 重试返回原成功结果，**不重复调用模型或扣费**。原成功响应中的 balance 是当次历史余额，当前余额请调用 getVIP。\n- 相同 requestId 更换内容会被拒绝。不同的实际问题必须生成新编号。\n- 响应丢失不代表未扣费：`NETWORK` 或 `42001` 请保留原 input，稍后重试。SDK 不会无限自动重试付费请求。\n- 模型最长等待约 2 分钟；记录超过 3 分钟仍 pending 视为未完成终态，不再自动执行该编号。`42002` 确认本编号未提交扣费，可用新编号重试。\n- 成功结果与扣费原子提交；故障遗留的 pending 记录没有扣费。通用 chat 保存成功回复和内容哈希，不保存原始 messages。\n- 角色对话还将轮次、来源记忆和关系互动更新纳入同一个扣费事务，使用同一个小游戏 AI 价格，不叠加私聊收费；与私聊/剧本共用关系生成锁。\n- 角色对话幂等范围是服务端绑定的参与上下文 + requestId；message 和 scene 必须原样保留。跨版本或换角色后属于新上下文，不能拿旧队列当作原请求重试。\n- 页面刷新后 SDK 不保存请求队列。若游戏需要跨刷新恢复，把 requestId 和原始 messages 存在**该游戏自己的待同步记录**中（不包含任何凭证），重新授权后重试。已有 App localStorage 云存档能力独立工作，SDK 不收集账号存储。\n- 页面离开、网络断开和 `dispose()` 只停止客户端等待，不能撤回已在服务端成功提交的生成和扣费。\n\n### 错误码\n\n| code | 含义 / 处理 |\n| --- | --- |\n| `AUTH_REQUIRED` | 专用凭证失效，App 内自动补授权一次后仍失败则要求重新登录/重新打开 |\n| `40201` | 积分不足，引导用户在 App 充值 |\n| `40301` | 旧版 VIP 限制错误；新版小游戏 AI 不再产生 |\n| `42001` | 原编号仍在处理，稍后用相同 input 重试 |\n| `42002` | 原编号失败且未扣费，可创建新编号 |\n| `42003` | 原编号已绑定不同消息，修复编号复用问题 |\n| `42901` | 有进行中的请求（含同关系的私聊/剧本）或请求过于频繁，等待后重试 |\n| `43001` | 本次游戏未绑定角色，请回 App 选角并授权 |\n| `43002` | 游戏未通过角色与记忆接入授权，联系平台审核 |\n| `7` | 游戏配置/参数/服务不可用，展示 message 并联系平台 |\n| `NETWORK` | 结果未知，保留原 requestId 重试 |\n| `NATIVE / UNSUPPORTED / ORIGIN / CONFIG / VALIDATION / PROTOCOL / DISPOSED / UNKNOWN` | 原生能力、运行环境、来源、参数、协议或生命周期问题 |\n\n不要用客户端 `isVip` 值替代后端权限检查，不要在积分不足时偷偷生成新 requestId 重试。\n\n## 5. HTTP 契约\n\n统一 `{code,data,msg}`。业务失败通常 HTTP 200 + 非零 code，专用凭证无效 HTTP 401。\n\n| 方法 | 路径 | 鉴权 | 请求 |\n| --- | --- | --- | --- |\n| POST | `/lunetale/game/character/prepare` | App `x-token` | `{gameId,versionId,characterId,identityId,memoryConsent:true}` → `{playContextId,storageKey,hasProgress,progress}`；仅可信启动器 |\n| POST | `/lunetale/game/authorize` | App `x-token` | `{gameId,versionId,playContextId?}` → `{code,expiresAt,origin}`；省略版本只兼容初始版本 |\n| POST | `/lunetale/game/revoke` | App `x-token` | `{gameId}`，撤销该账号该游戏全部专用授权 |\n| POST | `/lunetale/game/runtime/exchange` | `Origin` + 一次性 code | `{code}` → `{accessToken,expiresAt,gameId}` |\n| GET | `/lunetale/game/runtime/vip` | `Origin` + `Authorization: Bearer <游戏凭证>` | VIPStatus，可选 credential |\n| POST | `/lunetale/game/runtime/chat` | 同上 | `{requestId,messages}` → ChatResult，可选 credential |\n| GET | `/lunetale/game/runtime/player` | 同上 | `{player:{nickname,avatarUrl,bio}}`，可选 credential；无参数 |\n| GET | `/lunetale/game/runtime/character` | 同上 | `{character: SelectedCharacter|null}`，可选 credential |\n| POST | `/lunetale/game/runtime/character-chat` | 同上 | `{requestId,message,scene?}` → CharacterChatResult，可选 credential |\n\n`credential` 是自动续期数据，形状同 exchange；SDK 自动消费并从公开返回对象中去掉。不允许客户端传 gameId/userId/model/cost/maxTokens 给 runtime/chat。Runtime 凭证签名使用用途隔离的密钥，不能用于 App 或后台接口。\n\n浏览器自动发送 Origin，游戏不可自行设置。上述专用 runtime 端点提供无 Cookie 的 HTTPS 跨域预检，实际请求仍严格校验凭证绑定来源；不会扩大普通 App/后台接口 CORS 权限。反向代理不能移除 Origin 或记录 Authorization、授权 body 和对话正文。\n\n## 6. 人工验收建议\n\n由平台和制作者在目标 Android / iOS /后端环境验证：普通账号/VIP/SVIP、会员过期、余额不足、0 积分、重复请求不重复扣费、相同 ID 不同消息、上游超时、返回/刷新/快速重进、换账号、游戏下架、错误 origin、iframe 隔离、授权码重复兑换/过期、10 分钟阈值续期及新授权后恢复原请求。\n\n角色接入另需验证：App 选角/身份/取消授权；头像与立绘；私聊 → 游戏1 → 游戏2 → 私聊/剧本的同关系记忆承接；换账号/身份/角色不串记忆或存档；游戏禁用角色授权、来源伪造、余额不足、并发、遗忘记忆、失败不扣费及重复请求只扣一次。\n\n此包的静态语法/类型检查及隔离自动测试不代表 App、真实模型调用、真实扣费或会员功能已验收。部署前请完成真实环境人工联调。\n","readmeFilename":"README.md"}